diff options
Diffstat (limited to 'src')
402 files changed, 26711 insertions, 7395 deletions
diff --git a/src/common/Makefile.nmake b/src/common/Makefile.nmake index b8c5dd4fea..a1c819fffa 100644 --- a/src/common/Makefile.nmake +++ b/src/common/Makefile.nmake @@ -7,8 +7,8 @@ LIBOR_OBJECTS = address.obj backtrace.obj compat.obj container.obj di_ops.obj \ log.obj memarea.obj mempool.obj procmon.obj sandbox.obj util.obj \ util_codedigest.obj -LIBOR_CRYPTO_OBJECTS = aes.obj crypto.obj crypto_format.obj torgzip.obj tortls.obj \ - crypto_curve25519.obj curve25519-donna.obj +LIBOR_CRYPTO_OBJECTS = aes.obj crypto.obj crypto_format.obj compress.obj compress_zlib.obj \ + tortls.obj crypto_curve25519.obj curve25519-donna.obj LIBOR_EVENT_OBJECTS = compat_libevent.obj diff --git a/src/common/address.c b/src/common/address.c index 2693239146..894e15114c 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -564,8 +564,8 @@ tor_addr_parse_PTR_name(tor_addr_t *result, const char *address, /** Convert <b>addr</b> to an in-addr.arpa name or a .ip6.arpa name, * and store the result in the <b>outlen</b>-byte buffer at - * <b>out</b>. Return the number of chars written to <b>out</b>, not - * including the trailing \0, on success. Returns -1 on failure. */ + * <b>out</b>. Returns a non-negative integer on success. + * Returns -1 on failure. */ int tor_addr_to_PTR_name(char *out, size_t outlen, const tor_addr_t *addr) @@ -1198,7 +1198,7 @@ tor_addr_hash(const tor_addr_t *addr) /* LCOV_EXCL_START */ tor_fragile_assert(); return 0; - /* LCOV_EXCL_END */ + /* LCOV_EXCL_STOP */ } } @@ -1781,9 +1781,10 @@ free_interface_address6_list(smartlist_t *addrs) * Returns NULL on failure. * Use free_interface_address6_list to free the returned list. */ -MOCK_IMPL(smartlist_t *,get_interface_address6_list,(int severity, - sa_family_t family, - int include_internal)) +MOCK_IMPL(smartlist_t *, +get_interface_address6_list,(int severity, + sa_family_t family, + int include_internal)) { smartlist_t *addrs; tor_addr_t addr; @@ -2051,7 +2052,8 @@ parse_port_range(const char *port, uint16_t *port_min_out, /** Given an IPv4 in_addr struct *<b>in</b> (in network order, as usual), * write it as a string into the <b>buf_len</b>-byte buffer in - * <b>buf</b>. + * <b>buf</b>. Returns a non-negative integer on success. + * Returns -1 on failure. */ int tor_inet_ntoa(const struct in_addr *in, char *buf, size_t buf_len) diff --git a/src/common/address.h b/src/common/address.h index 0dc6edae37..ce85b3d81d 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/aes.c b/src/common/aes.c index 35c2d1e3a5..73abef143e 100644 --- a/src/common/aes.c +++ b/src/common/aes.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/aes.h b/src/common/aes.h index 1cda53f2fa..6fd9c3ea16 100644 --- a/src/common/aes.h +++ b/src/common/aes.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /* Implements a minimal interface to counter-mode AES. */ diff --git a/src/common/backtrace.c b/src/common/backtrace.c index 61096952d8..0f0fa857bf 100644 --- a/src/common/backtrace.c +++ b/src/common/backtrace.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/backtrace.h b/src/common/backtrace.h index b53fd2c668..8cd1dcf06c 100644 --- a/src/common/backtrace.h +++ b/src/common/backtrace.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_BACKTRACE_H diff --git a/src/common/compat.c b/src/common/compat.c index 7ec7030ecc..4d110aba35 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -2687,7 +2687,8 @@ static int uname_result_is_set = 0; /** Return a pointer to a description of our platform. */ -MOCK_IMPL(const char *, get_uname, (void)) +MOCK_IMPL(const char *, +get_uname,(void)) { #ifdef HAVE_UNAME struct utsname u; @@ -3260,7 +3261,7 @@ format_win32_error(DWORD err) FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPVOID)&str, 0, NULL); diff --git a/src/common/compat.h b/src/common/compat.h index ee1c9454de..473ad2b957 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_COMPAT_H @@ -414,10 +414,10 @@ struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); #endif #ifndef timercmp -/** Replacement for timersub on platforms that do not have it: returns true +/** Replacement for timercmp on platforms that do not have it: returns true * iff the relational operator "op" makes the expression tv1 op tv2 true. * - * Note that while this definition should work for all boolean opeators, some + * Note that while this definition should work for all boolean operators, some * platforms' native timercmp definitions do not support >=, <=, or ==. So * don't use those. */ diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index 4a3b1af922..31eb4ac496 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2016, The Tor Project, Inc. */ +/* Copyright (c) 2009-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -280,6 +280,15 @@ tor_gettimeofday_cache_set(const struct timeval *tv) tor_assert(tv); memcpy(&cached_time_hires, tv, sizeof(*tv)); } + +/** For testing: called post-fork to make libevent reinitialize + * kernel structures. */ +void +tor_libevent_postfork(void) +{ + int r = event_reinit(tor_libevent_get_base()); + tor_assert(r == 0); +} #endif #endif diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index c2e34764e4..904938415c 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2016, The Tor Project, Inc. */ +/* Copyright (c) 2009-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_COMPAT_LIBEVENT_H @@ -54,6 +54,7 @@ void tor_gettimeofday_cached(struct timeval *tv); void tor_gettimeofday_cache_clear(void); #ifdef TOR_UNIT_TESTS void tor_gettimeofday_cache_set(const struct timeval *tv); +void tor_libevent_postfork(void); #endif #ifdef COMPAT_LIBEVENT_PRIVATE diff --git a/src/common/compat_openssl.h b/src/common/compat_openssl.h index 1bfe188075..2b94fe5b4e 100644 --- a/src/common/compat_openssl.h +++ b/src/common/compat_openssl.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_COMPAT_OPENSSL_H diff --git a/src/common/compat_pthreads.c b/src/common/compat_pthreads.c index c1ae66c1d2..8e4b833573 100644 --- a/src/common/compat_pthreads.c +++ b/src/common/compat_pthreads.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/compat_rust.c b/src/common/compat_rust.c new file mode 100644 index 0000000000..366fd4037b --- /dev/null +++ b/src/common/compat_rust.c @@ -0,0 +1,39 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file rust_compat.c + * \brief Rust FFI compatibility functions and helpers. This file is only built + * if Rust is not used. + **/ + +#include "compat_rust.h" +#include "util.h" + +/** + * Free storage pointed to by <b>str</b>, and itself. + */ +void +rust_str_free(rust_str_t str) +{ + char *s = (char *)str; + tor_free(s); +} + +/** + * Return zero-terminated contained string. + */ +const char * +rust_str_get(const rust_str_t str) +{ + return (const char *)str; +} + +/* If we were using Rust, we'd say so on startup. */ +rust_str_t +rust_welcome_string(void) +{ + char *s = tor_malloc_zero(1); + return (rust_str_t)s; +} + diff --git a/src/common/compat_rust.h b/src/common/compat_rust.h new file mode 100644 index 0000000000..752a29b56c --- /dev/null +++ b/src/common/compat_rust.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file rust_compat.h + * \brief Headers for rust_compat.c + **/ + +#ifndef TOR_RUST_COMPAT_H +#define TOR_RUST_COMPAT_H + +#include "torint.h" + +/** + * Strings allocated in Rust must be freed from Rust code again. Let's make + * it less likely to accidentally mess up and call tor_free() on it, because + * currently it'll just work but might break at any time. + */ +typedef uintptr_t rust_str_t; + +void rust_str_free(rust_str_t); + +const char *rust_str_get(const rust_str_t); + +rust_str_t rust_welcome_string(void); + +#endif + diff --git a/src/common/compat_threads.c b/src/common/compat_threads.c index f4809060d6..c593e9af8d 100644 --- a/src/common/compat_threads.c +++ b/src/common/compat_threads.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -94,51 +94,73 @@ in_main_thread(void) } #if defined(HAVE_EVENTFD) || defined(HAVE_PIPE) -/* As write(), but retry on EINTR */ +/* As write(), but retry on EINTR, and return the negative error code on + * error. */ static int write_ni(int fd, const void *buf, size_t n) { int r; again: r = (int) write(fd, buf, n); - if (r < 0 && errno == EINTR) - goto again; + if (r < 0) { + if (errno == EINTR) + goto again; + else + return -errno; + } return r; } -/* As read(), but retry on EINTR */ +/* As read(), but retry on EINTR, and return the negative error code on error. + */ static int read_ni(int fd, void *buf, size_t n) { int r; again: r = (int) read(fd, buf, n); - if (r < 0 && errno == EINTR) - goto again; + if (r < 0) { + if (errno == EINTR) + goto again; + else + return -errno; + } return r; } #endif -/** As send(), but retry on EINTR. */ +/** As send(), but retry on EINTR, and return the negative error code on + * error. */ static int send_ni(int fd, const void *buf, size_t n, int flags) { int r; again: r = (int) send(fd, buf, n, flags); - if (r < 0 && ERRNO_IS_EINTR(tor_socket_errno(fd))) - goto again; + if (r < 0) { + int error = tor_socket_errno(fd); + if (ERRNO_IS_EINTR(error)) + goto again; + else + return -error; + } return r; } -/** As recv(), but retry on EINTR. */ +/** As recv(), but retry on EINTR, and return the negative error code on + * error. */ static int recv_ni(int fd, void *buf, size_t n, int flags) { int r; again: r = (int) recv(fd, buf, n, flags); - if (r < 0 && ERRNO_IS_EINTR(tor_socket_errno(fd))) - goto again; + if (r < 0) { + int error = tor_socket_errno(fd); + if (ERRNO_IS_EINTR(error)) + goto again; + else + return -error; + } return r; } @@ -149,7 +171,7 @@ eventfd_alert(int fd) { uint64_t u = 1; int r = write_ni(fd, (void*)&u, sizeof(u)); - if (r < 0 && errno != EAGAIN) + if (r < 0 && -r != EAGAIN) return -1; return 0; } @@ -160,8 +182,8 @@ eventfd_drain(int fd) { uint64_t u = 0; int r = read_ni(fd, (void*)&u, sizeof(u)); - if (r < 0 && errno != EAGAIN) - return -1; + if (r < 0 && -r != EAGAIN) + return r; return 0; } #endif @@ -172,8 +194,8 @@ static int pipe_alert(int fd) { ssize_t r = write_ni(fd, "x", 1); - if (r < 0 && errno != EAGAIN) - return -1; + if (r < 0 && -r != EAGAIN) + return (int)r; return 0; } @@ -188,7 +210,7 @@ pipe_drain(int fd) r = read_ni(fd, buf, sizeof(buf)); } while (r > 0); if (r < 0 && errno != EAGAIN) - return -1; + return -errno; /* A value of r = 0 means EOF on the fd so successfully drained. */ return 0; } @@ -200,13 +222,13 @@ static int sock_alert(tor_socket_t fd) { ssize_t r = send_ni(fd, "x", 1, 0); - if (r < 0 && !ERRNO_IS_EAGAIN(tor_socket_errno(fd))) - return -1; + if (r < 0 && !ERRNO_IS_EAGAIN(-r)) + return (int)r; return 0; } /** Drain all the input from a socket <b>fd</b>, and ignore it. Return 0 on - * success, -1 on error. */ + * success, -errno on error. */ static int sock_drain(tor_socket_t fd) { @@ -215,8 +237,8 @@ sock_drain(tor_socket_t fd) do { r = recv_ni(fd, buf, sizeof(buf), 0); } while (r > 0); - if (r < 0 && !ERRNO_IS_EAGAIN(tor_socket_errno(fd))) - return -1; + if (r < 0 && !ERRNO_IS_EAGAIN(-r)) + return (int)r; /* A value of r = 0 means EOF on the fd so successfully drained. */ return 0; } @@ -330,3 +352,49 @@ 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. + * + */ + +/** Initialize a new atomic counter with the value 0 */ +void +atomic_counter_init(atomic_counter_t *counter) +{ + memset(counter, 0, sizeof(*counter)); + tor_mutex_init_nonrecursive(&counter->mutex); +} +/** Clean up all resources held by an atomic counter. */ +void +atomic_counter_destroy(atomic_counter_t *counter) +{ + tor_mutex_uninit(&counter->mutex); + memset(counter, 0, sizeof(*counter)); +} +/** Add a value to an atomic counter. */ +void +atomic_counter_add(atomic_counter_t *counter, size_t add) +{ + tor_mutex_acquire(&counter->mutex); + counter->val += add; + tor_mutex_release(&counter->mutex); +} +/** Subtract a value from an atomic counter. */ +void +atomic_counter_sub(atomic_counter_t *counter, size_t sub) +{ + // this relies on unsigned overflow, but that's fine. + atomic_counter_add(counter, -sub); +} +/** Return the current value of an atomic counter */ +size_t +atomic_counter_get(atomic_counter_t *counter) +{ + size_t val; + tor_mutex_acquire(&counter->mutex); + val = counter->val; + tor_mutex_release(&counter->mutex); + return val; +} + diff --git a/src/common/compat_threads.h b/src/common/compat_threads.h index 171a9f93ff..9fa3d0d0b7 100644 --- a/src/common/compat_threads.h +++ b/src/common/compat_threads.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_COMPAT_THREADS_H @@ -147,5 +147,19 @@ void *tor_threadlocal_get(tor_threadlocal_t *threadlocal); */ void tor_threadlocal_set(tor_threadlocal_t *threadlocal, void *value); +/** + * Atomic counter type; holds a size_t value. + */ +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); + #endif diff --git a/src/common/compat_time.c b/src/common/compat_time.c index d044bbe1d7..2ccaa36e49 100644 --- a/src/common/compat_time.c +++ b/src/common/compat_time.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/compat_time.h b/src/common/compat_time.h index 2262446e57..90194c5ebc 100644 --- a/src/common/compat_time.h +++ b/src/common/compat_time.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/compat_winthreads.c b/src/common/compat_winthreads.c index 735be4ad17..915368b94f 100644 --- a/src/common/compat_winthreads.c +++ b/src/common/compat_winthreads.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/compress.c b/src/common/compress.c new file mode 100644 index 0000000000..beeff5fcb8 --- /dev/null +++ b/src/common/compress.c @@ -0,0 +1,658 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress.c + * \brief Common compression API. + **/ + +#include "orconfig.h" + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include "torint.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include "util.h" +#include "torlog.h" +#include "compress.h" +#include "compress_lzma.h" +#include "compress_none.h" +#include "compress_zlib.h" +#include "compress_zstd.h" + +/** Total number of bytes allocated for compression state overhead. */ +static atomic_counter_t total_compress_allocation; + +/** @{ */ +/* These macros define the maximum allowable compression factor. Anything of + * size greater than CHECK_FOR_COMPRESSION_BOMB_AFTER is not allowed to + * have an uncompression factor (uncompressed size:compressed size ratio) of + * any greater than MAX_UNCOMPRESSION_FACTOR. + * + * Picking a value for MAX_UNCOMPRESSION_FACTOR is a trade-off: we want it to + * be small to limit the attack multiplier, but we also want it to be large + * enough so that no legitimate document --even ones we might invent in the + * future -- ever compresses by a factor of greater than + * MAX_UNCOMPRESSION_FACTOR. Within those parameters, there's a reasonably + * large range of possible values. IMO, anything over 8 is probably safe; IMO + * anything under 50 is probably sufficient. + */ +#define MAX_UNCOMPRESSION_FACTOR 25 +#define CHECK_FOR_COMPRESSION_BOMB_AFTER (1024*64) +/** @} */ + +/** Return true if uncompressing an input of size <b>in_size</b> to an input of + * size at least <b>size_out</b> looks like a compression bomb. */ +int +tor_compress_is_compression_bomb(size_t size_in, size_t size_out) +{ + if (size_in == 0 || size_out < CHECK_FOR_COMPRESSION_BOMB_AFTER) + return 0; + + return (size_out / size_in > MAX_UNCOMPRESSION_FACTOR); +} + +/** Guess the size that <b>in_len</b> will be after compression or + * decompression. */ +static size_t +guess_compress_size(int compress, compress_method_t method, + compression_level_t compression_level, + size_t in_len) +{ + // ignore these for now. + (void)compression_level; + if (method == NO_METHOD) { + /* Guess that we'll need an extra byte, to avoid a needless realloc + * for nul-termination */ + return (in_len < SIZE_MAX) ? in_len + 1 : in_len; + } + + /* Always guess a factor of 2. */ + if (compress) { + in_len /= 2; + } else { + if (in_len < SIZE_T_CEILING/2) + in_len *= 2; + } + return MAX(in_len, 1024); +} + +/** Internal function to implement tor_compress/tor_uncompress, depending on + * whether <b>compress</b> is set. All arguments are as for tor_compress or + * tor_uncompress. */ +static int +tor_compress_impl(int compress, + char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method, + compression_level_t compression_level, + int complete_only, + int protocol_warn_level) +{ + tor_compress_state_t *stream; + int rv; + + stream = tor_compress_new(compress, method, compression_level); + + if (stream == NULL) { + log_warn(LD_GENERAL, "NULL stream while %scompressing", + compress?"":"de"); + log_debug(LD_GENERAL, "method: %d level: %d at len: %lu", + method, compression_level, (unsigned long)in_len); + return -1; + } + + size_t in_len_orig = in_len; + size_t out_remaining, out_alloc; + char *outptr; + + out_remaining = out_alloc = + guess_compress_size(compress, method, compression_level, in_len); + *out = outptr = tor_malloc(out_remaining); + + const int finish = complete_only || compress; + + while (1) { + switch (tor_compress_process(stream, + &outptr, &out_remaining, + &in, &in_len, finish)) { + case TOR_COMPRESS_DONE: + if (in_len == 0 || compress) { + goto done; + } else { + // More data is present, and we're decompressing. So we may need to + // reinitialize the stream if we are handling multiple concatenated + // inputs. + tor_compress_free(stream); + stream = tor_compress_new(compress, method, compression_level); + if (stream == NULL) { + log_warn(LD_GENERAL, "NULL stream while %scompressing", + compress?"":"de"); + goto err; + } + } + break; + case TOR_COMPRESS_OK: + if (compress || complete_only) { + log_fn(protocol_warn_level, LD_PROTOCOL, + "Unexpected %s while %scompressing", + complete_only?"end of input":"result", + compress?"":"de"); + log_debug(LD_GENERAL, "method: %d level: %d at len: %lu", + method, compression_level, (unsigned long)in_len); + goto err; + } else { + if (in_len == 0) { + goto done; + } + } + break; + case TOR_COMPRESS_BUFFER_FULL: { + if (!compress && outptr < *out+out_alloc) { + // A buffer error in this case means that we have a problem + // with our input. + log_fn(protocol_warn_level, LD_PROTOCOL, + "Possible truncated or corrupt compressed data"); + goto err; + } + if (out_alloc >= SIZE_T_CEILING / 2) { + log_warn(LD_GENERAL, "While %scompresing data: ran out of space.", + compress?"":"un"); + goto err; + } + if (!compress && + tor_compress_is_compression_bomb(in_len_orig, out_alloc)) { + // This should already have been caught down in the backend logic. + // LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + goto err; + // LCOV_EXCL_STOP + } + const size_t offset = outptr - *out; + out_alloc *= 2; + *out = tor_realloc(*out, out_alloc); + outptr = *out + offset; + out_remaining = out_alloc - offset; + break; + } + case TOR_COMPRESS_ERROR: + log_fn(protocol_warn_level, LD_GENERAL, + "Error while %scompresing data: bad input?", + compress?"":"un"); + goto err; // bad data. + default: + // LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + goto err; + // LCOV_EXCL_STOP + } + } + done: + *out_len = outptr - *out; + if (compress && tor_compress_is_compression_bomb(*out_len, in_len_orig)) { + log_warn(LD_BUG, "We compressed something and got an insanely high " + "compression factor; other Tors would think this was a " + "compression bomb."); + goto err; + } + if (!compress) { + // NUL-terminate our output. + if (out_alloc == *out_len) + *out = tor_realloc(*out, out_alloc + 1); + (*out)[*out_len] = '\0'; + } + rv = 0; + goto out; + + err: + tor_free(*out); + *out_len = 0; + rv = -1; + goto out; + + out: + tor_compress_free(stream); + return rv; +} + +/** Given <b>in_len</b> bytes at <b>in</b>, compress them into a newly + * allocated buffer, using the method described in <b>method</b>. Store the + * compressed string in *<b>out</b>, and its length in *<b>out_len</b>. + * Return 0 on success, -1 on failure. + */ +int +tor_compress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method) +{ + return tor_compress_impl(1, out, out_len, in, in_len, method, + BEST_COMPRESSION, + 1, LOG_WARN); +} + +/** Given zero or more compressed strings of total length <b>in_len</b> bytes + * at <b>in</b>, uncompress them into a newly allocated buffer, using the + * method described in <b>method</b>. Store the uncompressed string in + * *<b>out</b>, and its length in *<b>out_len</b>. Return 0 on success, -1 on + * failure. + * + * If any bytes are written to <b>out</b>, an extra byte NUL is always + * written at the end, but not counted in <b>out_len</b>. This is a + * safety feature to ensure that the output can be treated as a + * NUL-terminated string -- though of course, callers should check + * out_len anyway. + * + * If <b>complete_only</b> is true, we consider a truncated input as a + * failure; otherwise we decompress as much as we can. Warn about truncated + * or corrupt inputs at <b>protocol_warn_level</b>. + */ +int +tor_uncompress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method, + int complete_only, + int protocol_warn_level) +{ + return tor_compress_impl(0, out, out_len, in, in_len, method, + BEST_COMPRESSION, + complete_only, protocol_warn_level); +} + +/** Try to tell whether the <b>in_len</b>-byte string in <b>in</b> is likely + * to be compressed or not. If it is, return the likeliest compression method. + * Otherwise, return UNKNOWN_METHOD. + */ +compress_method_t +detect_compression_method(const char *in, size_t in_len) +{ + if (in_len > 2 && fast_memeq(in, "\x1f\x8b", 2)) { + return GZIP_METHOD; + } else if (in_len > 2 && (in[0] & 0x0f) == 8 && + (ntohs(get_uint16(in)) % 31) == 0) { + return ZLIB_METHOD; + } else if (in_len > 2 && + fast_memeq(in, "\x5d\x00\x00", 3)) { + return LZMA_METHOD; + } else if (in_len > 3 && + fast_memeq(in, "\x28\xb5\x2f\xfd", 4)) { + return ZSTD_METHOD; + } else { + return UNKNOWN_METHOD; + } +} + +/** Return 1 if a given <b>method</b> is supported; otherwise 0. */ +int +tor_compress_supports_method(compress_method_t method) +{ + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: + return tor_zlib_method_supported(); + case LZMA_METHOD: + return tor_lzma_method_supported(); + case ZSTD_METHOD: + return tor_zstd_method_supported(); + case NO_METHOD: + return 1; + case UNKNOWN_METHOD: + default: + return 0; + } +} + +/** + * Return a bitmask of the supported compression types, where 1<<m is + * set in the bitmask if and only if compression with method <b>m</b> is + * supported. + */ +unsigned +tor_compress_get_supported_method_bitmask(void) +{ + static unsigned supported = 0; + if (supported == 0) { + compress_method_t m; + for (m = NO_METHOD; m <= UNKNOWN_METHOD; ++m) { + if (tor_compress_supports_method(m)) { + supported |= (1u << m); + } + } + } + return supported; +} + +/** Table of compression method names. These should have an "x-" prefix, + * if they are not listed in the IANA content coding registry. */ +static const struct { + const char *name; + compress_method_t method; +} compression_method_names[] = { + { "gzip", GZIP_METHOD }, + { "deflate", ZLIB_METHOD }, + // We call this "x-tor-lzma" rather than "x-lzma", because we impose a + // lower maximum memory usage on the decoding side. + { "x-tor-lzma", LZMA_METHOD }, + { "x-zstd" , ZSTD_METHOD }, + { "identity", NO_METHOD }, + + /* Later entries in this table are not canonical; these are recognized but + * not emitted. */ + { "x-gzip", GZIP_METHOD }, +}; + +/** Return the canonical string representation of the compression method + * <b>method</b>, or NULL if the method isn't recognized. */ +const char * +compression_method_get_name(compress_method_t method) +{ + unsigned i; + for (i = 0; i < ARRAY_LENGTH(compression_method_names); ++i) { + if (method == compression_method_names[i].method) + return compression_method_names[i].name; + } + return NULL; +} + +/** Table of compression human readable method names. */ +static const struct { + compress_method_t method; + const char *name; +} compression_method_human_names[] = { + { NO_METHOD, "uncompressed" }, + { GZIP_METHOD, "gzipped" }, + { ZLIB_METHOD, "deflated" }, + { LZMA_METHOD, "LZMA compressed" }, + { ZSTD_METHOD, "Zstandard compressed" }, + { UNKNOWN_METHOD, "unknown encoding" }, +}; + +/** Return a human readable string representation of the compression method + * <b>method</b>, or NULL if the method isn't recognized. */ +const char * +compression_method_get_human_name(compress_method_t method) +{ + unsigned i; + for (i = 0; i < ARRAY_LENGTH(compression_method_human_names); ++i) { + if (method == compression_method_human_names[i].method) + return compression_method_human_names[i].name; + } + return NULL; +} + +/** Return the compression method represented by the string <b>name</b>, or + * UNKNOWN_METHOD if the string isn't recognized. */ +compress_method_t +compression_method_get_by_name(const char *name) +{ + unsigned i; + for (i = 0; i < ARRAY_LENGTH(compression_method_names); ++i) { + if (!strcmp(compression_method_names[i].name, name)) + return compression_method_names[i].method; + } + return UNKNOWN_METHOD; +} + +/** Return a string representation of the version of the library providing the + * compression method given in <b>method</b>. Returns NULL if <b>method</b> is + * unknown or unsupported. */ +const char * +tor_compress_version_str(compress_method_t method) +{ + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: + return tor_zlib_get_version_str(); + case LZMA_METHOD: + return tor_lzma_get_version_str(); + case ZSTD_METHOD: + return tor_zstd_get_version_str(); + case NO_METHOD: + case UNKNOWN_METHOD: + default: + return NULL; + } +} + +/** Return a string representation of the version of the library, found at + * compile time, providing the compression method given in <b>method</b>. + * Returns NULL if <b>method</b> is unknown or unsupported. */ +const char * +tor_compress_header_version_str(compress_method_t method) +{ + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: + return tor_zlib_get_header_version_str(); + case LZMA_METHOD: + return tor_lzma_get_header_version_str(); + case ZSTD_METHOD: + return tor_zstd_get_header_version_str(); + case NO_METHOD: + case UNKNOWN_METHOD: + default: + return NULL; + } +} + +/** Return the approximate number of bytes allocated for all + * supported compression schemas. */ +size_t +tor_compress_get_total_allocation(void) +{ + return atomic_counter_get(&total_compress_allocation) + + tor_zlib_get_total_allocation() + + tor_lzma_get_total_allocation() + + tor_zstd_get_total_allocation(); +} + +/** Internal state for an incremental compression/decompression. The body of + * this struct is not exposed. */ +struct tor_compress_state_t { + compress_method_t method; /**< The compression method. */ + + union { + tor_zlib_compress_state_t *zlib_state; + tor_lzma_compress_state_t *lzma_state; + tor_zstd_compress_state_t *zstd_state; + } u; /**< Compression backend state. */ +}; + +/** Construct and return a tor_compress_state_t object using <b>method</b>. If + * <b>compress</b>, it's for compression; otherwise it's for decompression. */ +tor_compress_state_t * +tor_compress_new(int compress, compress_method_t method, + compression_level_t compression_level) +{ + tor_compress_state_t *state; + + state = tor_malloc_zero(sizeof(tor_compress_state_t)); + state->method = method; + + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: { + tor_zlib_compress_state_t *zlib_state = + tor_zlib_compress_new(compress, method, compression_level); + + if (zlib_state == NULL) + goto err; + + state->u.zlib_state = zlib_state; + break; + } + case LZMA_METHOD: { + tor_lzma_compress_state_t *lzma_state = + tor_lzma_compress_new(compress, method, compression_level); + + if (lzma_state == NULL) + goto err; + + state->u.lzma_state = lzma_state; + break; + } + case ZSTD_METHOD: { + tor_zstd_compress_state_t *zstd_state = + tor_zstd_compress_new(compress, method, compression_level); + + if (zstd_state == NULL) + goto err; + + state->u.zstd_state = zstd_state; + break; + } + case NO_METHOD: { + break; + } + case UNKNOWN_METHOD: + goto err; + } + + atomic_counter_add(&total_compress_allocation, + sizeof(tor_compress_state_t)); + return state; + + err: + tor_free(state); + return NULL; +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_compress_process(tor_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ + tor_assert(state != NULL); + const size_t in_len_orig = *in_len; + const size_t out_len_orig = *out_len; + tor_compress_output_t rv; + + if (*out_len == 0 && (*in_len > 0 || finish)) { + // If we still have input data, but no space for output data, we might as + // well return early and let the caller do the reallocation of the out + // variable. + return TOR_COMPRESS_BUFFER_FULL; + } + + switch (state->method) { + case GZIP_METHOD: + case ZLIB_METHOD: + rv = tor_zlib_compress_process(state->u.zlib_state, + out, out_len, in, in_len, + finish); + break; + case LZMA_METHOD: + rv =tor_lzma_compress_process(state->u.lzma_state, + out, out_len, in, in_len, + finish); + break; + case ZSTD_METHOD: + rv = tor_zstd_compress_process(state->u.zstd_state, + out, out_len, in, in_len, + finish); + break; + case NO_METHOD: + rv = tor_cnone_compress_process(out, out_len, in, in_len, + finish); + break; + default: + case UNKNOWN_METHOD: + goto err; + } + if (BUG((rv == TOR_COMPRESS_OK) && + *in_len == in_len_orig && + *out_len == out_len_orig)) { + return TOR_COMPRESS_ERROR; + } + + return rv; + err: + return TOR_COMPRESS_ERROR; +} + +/** Deallocate <b>state</b>. */ +void +tor_compress_free(tor_compress_state_t *state) +{ + if (state == NULL) + return; + + switch (state->method) { + case GZIP_METHOD: + case ZLIB_METHOD: + tor_zlib_compress_free(state->u.zlib_state); + break; + case LZMA_METHOD: + tor_lzma_compress_free(state->u.lzma_state); + break; + case ZSTD_METHOD: + tor_zstd_compress_free(state->u.zstd_state); + break; + case NO_METHOD: + break; + case UNKNOWN_METHOD: + break; + } + + atomic_counter_sub(&total_compress_allocation, + sizeof(tor_compress_state_t)); + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_compress_state_size(const tor_compress_state_t *state) +{ + tor_assert(state != NULL); + + size_t size = sizeof(tor_compress_state_t); + + switch (state->method) { + case GZIP_METHOD: + case ZLIB_METHOD: + size += tor_zlib_compress_state_size(state->u.zlib_state); + break; + case LZMA_METHOD: + size += tor_lzma_compress_state_size(state->u.lzma_state); + break; + case ZSTD_METHOD: + size += tor_zstd_compress_state_size(state->u.zstd_state); + break; + case NO_METHOD: + case UNKNOWN_METHOD: + break; + } + + return size; +} + +/** Initialize all compression modules. */ +void +tor_compress_init(void) +{ + atomic_counter_init(&total_compress_allocation); + + tor_zlib_init(); + tor_lzma_init(); + tor_zstd_init(); +} + diff --git a/src/common/compress.h b/src/common/compress.h new file mode 100644 index 0000000000..59c8b7b9b5 --- /dev/null +++ b/src/common/compress.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress.h + * \brief Headers for compress.c + **/ + +#ifndef TOR_COMPRESS_H +#define TOR_COMPRESS_H + +/** Enumeration of what kind of compression to use. Only ZLIB_METHOD and + * GZIP_METHOD is guaranteed to be supported by the compress/uncompress + * functions here. Call tor_compress_supports_method() to check if a given + * compression schema is supported by Tor. */ +typedef enum { + NO_METHOD=0, // This method must be first. + GZIP_METHOD=1, + ZLIB_METHOD=2, + LZMA_METHOD=3, + ZSTD_METHOD=4, + UNKNOWN_METHOD=5, // This method must be last. Add new ones in the middle. +} compress_method_t; + +/** + * Enumeration to define tradeoffs between memory usage and compression level. + * BEST_COMPRESSION saves the most bandwidth; LOW_COMPRESSION saves the most + * memory. + **/ +typedef enum { + BEST_COMPRESSION, HIGH_COMPRESSION, MEDIUM_COMPRESSION, LOW_COMPRESSION +} compression_level_t; + +int tor_compress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method); + +int tor_uncompress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method, + int complete_only, + int protocol_warn_level); + +compress_method_t detect_compression_method(const char *in, size_t in_len); + +int tor_compress_is_compression_bomb(size_t size_in, size_t size_out); + +int tor_compress_supports_method(compress_method_t method); +unsigned tor_compress_get_supported_method_bitmask(void); +const char *compression_method_get_name(compress_method_t method); +const char *compression_method_get_human_name(compress_method_t method); +compress_method_t compression_method_get_by_name(const char *name); + +const char *tor_compress_version_str(compress_method_t method); + +const char *tor_compress_header_version_str(compress_method_t method); + +size_t tor_compress_get_total_allocation(void); + +/** Return values from tor_compress_process; see that function's documentation + * for details. */ +typedef enum { + TOR_COMPRESS_OK, + TOR_COMPRESS_DONE, + TOR_COMPRESS_BUFFER_FULL, + TOR_COMPRESS_ERROR +} tor_compress_output_t; + +/** Internal state for an incremental compression/decompression. */ +typedef struct tor_compress_state_t tor_compress_state_t; + +tor_compress_state_t *tor_compress_new(int compress, + compress_method_t method, + compression_level_t level); + +tor_compress_output_t tor_compress_process(tor_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); +void tor_compress_free(tor_compress_state_t *state); + +size_t tor_compress_state_size(const tor_compress_state_t *state); + +void tor_compress_init(void); + +#endif // TOR_COMPRESS_H. + diff --git a/src/common/compress_lzma.c b/src/common/compress_lzma.c new file mode 100644 index 0000000000..d453d9f718 --- /dev/null +++ b/src/common/compress_lzma.c @@ -0,0 +1,357 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_lzma.c + * \brief Compression backend for LZMA. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "util.h" +#include "torlog.h" +#include "compress.h" +#include "compress_lzma.h" + +#ifdef HAVE_LZMA +#include <lzma.h> +#endif + +/** The maximum amount of memory we allow the LZMA decoder to use, in bytes. */ +#define MEMORY_LIMIT (16 * 1024 * 1024) + +/** Total number of bytes allocated for LZMA state. */ +static atomic_counter_t total_lzma_allocation; + +#ifdef HAVE_LZMA +/** Given <b>level</b> return the memory level. */ +static int +memory_level(compression_level_t level) +{ + switch (level) { + default: + case BEST_COMPRESSION: + case HIGH_COMPRESSION: return 6; + case MEDIUM_COMPRESSION: return 4; + case LOW_COMPRESSION: return 2; + } +} + +/** Convert a given <b>error</b> to a human readable error string. */ +static const char * +lzma_error_str(lzma_ret error) +{ + switch (error) { + case LZMA_OK: + return "Operation completed successfully"; + case LZMA_STREAM_END: + return "End of stream"; + case LZMA_NO_CHECK: + return "Input stream lacks integrity check"; + case LZMA_UNSUPPORTED_CHECK: + return "Unable to calculate integrity check"; + case LZMA_GET_CHECK: + return "Integrity check available"; + case LZMA_MEM_ERROR: + return "Unable to allocate memory"; + case LZMA_MEMLIMIT_ERROR: + return "Memory limit reached"; + case LZMA_FORMAT_ERROR: + return "Unknown file format"; + case LZMA_OPTIONS_ERROR: + return "Unsupported options"; + case LZMA_DATA_ERROR: + return "Corrupt input data"; + case LZMA_BUF_ERROR: + return "Unable to progress"; + case LZMA_PROG_ERROR: + return "Programming error"; + default: + return "Unknown LZMA error"; + } +} +#endif // HAVE_LZMA. + +/** Return 1 if LZMA compression is supported; otherwise 0. */ +int +tor_lzma_method_supported(void) +{ +#ifdef HAVE_LZMA + return 1; +#else + return 0; +#endif +} + +/** Return a string representation of the version of the currently running + * version of liblzma. Returns NULL if LZMA is unsupported. */ +const char * +tor_lzma_get_version_str(void) +{ +#ifdef HAVE_LZMA + return lzma_version_string(); +#else + return NULL; +#endif +} + +/** Return a string representation of the version of liblzma used at + * compilation time. Returns NULL if LZMA is unsupported. */ +const char * +tor_lzma_get_header_version_str(void) +{ +#ifdef HAVE_LZMA + return LZMA_VERSION_STRING; +#else + return NULL; +#endif +} + +/** Internal LZMA state for incremental compression/decompression. + * The body of this struct is not exposed. */ +struct tor_lzma_compress_state_t { +#ifdef HAVE_LZMA + lzma_stream stream; /**< The LZMA stream. */ +#endif + + int compress; /**< True if we are compressing; false if we are inflating */ + + /** Number of bytes read so far. Used to detect compression bombs. */ + size_t input_so_far; + /** Number of bytes written so far. Used to detect compression bombs. */ + size_t output_so_far; + + /** Approximate number of bytes allocated for this object. */ + size_t allocation; +}; + +#ifdef HAVE_LZMA +/** Return an approximate number of bytes stored in memory to hold the LZMA + * encoder/decoder state. */ +static size_t +tor_lzma_state_size_precalc(int compress, compression_level_t level) +{ + uint64_t memory_usage; + + if (compress) + memory_usage = lzma_easy_encoder_memusage(memory_level(level)); + else + memory_usage = lzma_easy_decoder_memusage(memory_level(level)); + + if (memory_usage == UINT64_MAX) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Unsupported compression level passed to LZMA %s", + compress ? "encoder" : "decoder"); + goto err; + // LCOV_EXCL_STOP + } + + if (memory_usage + sizeof(tor_lzma_compress_state_t) > SIZE_MAX) + memory_usage = SIZE_MAX; + else + memory_usage += sizeof(tor_lzma_compress_state_t); + + return (size_t)memory_usage; + + err: + return 0; // LCOV_EXCL_LINE +} +#endif // HAVE_LZMA. + +/** Construct and return a tor_lzma_compress_state_t object using + * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for + * decompression. */ +tor_lzma_compress_state_t * +tor_lzma_compress_new(int compress, + compress_method_t method, + compression_level_t level) +{ + tor_assert(method == LZMA_METHOD); + +#ifdef HAVE_LZMA + tor_lzma_compress_state_t *result; + lzma_ret retval; + lzma_options_lzma stream_options; + + // Note that we do not explicitly initialize the lzma_stream object here, + // since the LZMA_STREAM_INIT "just" initializes all members to 0, which is + // also what `tor_malloc_zero()` does. + result = tor_malloc_zero(sizeof(tor_lzma_compress_state_t)); + result->compress = compress; + result->allocation = tor_lzma_state_size_precalc(compress, level); + + if (compress) { + lzma_lzma_preset(&stream_options, memory_level(level)); + + retval = lzma_alone_encoder(&result->stream, &stream_options); + + if (retval != LZMA_OK) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error from LZMA encoder: %s (%u).", + lzma_error_str(retval), retval); + goto err; + // LCOV_EXCL_STOP + } + } else { + retval = lzma_alone_decoder(&result->stream, MEMORY_LIMIT); + + if (retval != LZMA_OK) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error from LZMA decoder: %s (%u).", + lzma_error_str(retval), retval); + goto err; + // LCOV_EXCL_STOP + } + } + + atomic_counter_add(&total_lzma_allocation, result->allocation); + return result; + + err: + tor_free(result); // LCOV_EXCL_LINE + return NULL; +#else // HAVE_LZMA. + (void)compress; + (void)method; + (void)level; + + return NULL; +#endif // HAVE_LZMA. +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_lzma_compress_process(tor_lzma_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ +#ifdef HAVE_LZMA + lzma_ret retval; + lzma_action action; + + tor_assert(state != NULL); + tor_assert(*in_len <= UINT_MAX); + tor_assert(*out_len <= UINT_MAX); + + state->stream.next_in = (unsigned char *)*in; + state->stream.avail_in = *in_len; + state->stream.next_out = (unsigned char *)*out; + state->stream.avail_out = *out_len; + + action = finish ? LZMA_FINISH : LZMA_RUN; + + retval = lzma_code(&state->stream, action); + + state->input_so_far += state->stream.next_in - ((unsigned char *)*in); + state->output_so_far += state->stream.next_out - ((unsigned char *)*out); + + *out = (char *)state->stream.next_out; + *out_len = state->stream.avail_out; + *in = (const char *)state->stream.next_in; + *in_len = state->stream.avail_in; + + if (! state->compress && + tor_compress_is_compression_bomb(state->input_so_far, + state->output_so_far)) { + log_warn(LD_DIR, "Possible compression bomb; abandoning stream."); + return TOR_COMPRESS_ERROR; + } + + switch (retval) { + case LZMA_OK: + if (state->stream.avail_out == 0 || finish) + return TOR_COMPRESS_BUFFER_FULL; + + return TOR_COMPRESS_OK; + + case LZMA_BUF_ERROR: + if (state->stream.avail_in == 0 && !finish) + return TOR_COMPRESS_OK; + + return TOR_COMPRESS_BUFFER_FULL; + + case LZMA_STREAM_END: + return TOR_COMPRESS_DONE; + + // We list all the possible values of `lzma_ret` here to silence the + // `switch-enum` warning and to detect if a new member was added. + case LZMA_NO_CHECK: + case LZMA_UNSUPPORTED_CHECK: + case LZMA_GET_CHECK: + case LZMA_MEM_ERROR: + case LZMA_MEMLIMIT_ERROR: + case LZMA_FORMAT_ERROR: + case LZMA_OPTIONS_ERROR: + case LZMA_DATA_ERROR: + case LZMA_PROG_ERROR: + default: + log_warn(LD_GENERAL, "LZMA %s didn't finish: %s.", + state->compress ? "compression" : "decompression", + lzma_error_str(retval)); + return TOR_COMPRESS_ERROR; + } +#else // HAVE_LZMA. + (void)state; + (void)out; + (void)out_len; + (void)in; + (void)in_len; + (void)finish; + return TOR_COMPRESS_ERROR; +#endif // HAVE_LZMA. +} + +/** Deallocate <b>state</b>. */ +void +tor_lzma_compress_free(tor_lzma_compress_state_t *state) +{ + if (state == NULL) + return; + + atomic_counter_sub(&total_lzma_allocation, state->allocation); + +#ifdef HAVE_LZMA + lzma_end(&state->stream); +#endif + + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_lzma_compress_state_size(const tor_lzma_compress_state_t *state) +{ + tor_assert(state != NULL); + return state->allocation; +} + +/** Return the approximate number of bytes allocated for all LZMA states. */ +size_t +tor_lzma_get_total_allocation(void) +{ + return atomic_counter_get(&total_lzma_allocation); +} + +/** Initialize the lzma module */ +void +tor_lzma_init(void) +{ + atomic_counter_init(&total_lzma_allocation); +} + diff --git a/src/common/compress_lzma.h b/src/common/compress_lzma.h new file mode 100644 index 0000000000..1433c89f88 --- /dev/null +++ b/src/common/compress_lzma.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_lzma.h + * \brief Header for compress_lzma.c + **/ + +#ifndef TOR_COMPRESS_LZMA_H +#define TOR_COMPRESS_LZMA_H + +int tor_lzma_method_supported(void); + +const char *tor_lzma_get_version_str(void); + +const char *tor_lzma_get_header_version_str(void); + +/** Internal state for an incremental LZMA compression/decompression. */ +typedef struct tor_lzma_compress_state_t tor_lzma_compress_state_t; + +tor_lzma_compress_state_t * +tor_lzma_compress_new(int compress, + compress_method_t method, + compression_level_t compression_level); + +tor_compress_output_t +tor_lzma_compress_process(tor_lzma_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +void tor_lzma_compress_free(tor_lzma_compress_state_t *state); + +size_t tor_lzma_compress_state_size(const tor_lzma_compress_state_t *state); + +size_t tor_lzma_get_total_allocation(void); + +void tor_lzma_init(void); + +#endif // TOR_COMPRESS_LZMA_H. + diff --git a/src/common/compress_none.c b/src/common/compress_none.c new file mode 100644 index 0000000000..34314e4af7 --- /dev/null +++ b/src/common/compress_none.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_none.c + * \brief Compression backend for identity compression. + * + * We actually define this backend so that we can treat the identity transform + * as another case of compression. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "util.h" +#include "torlog.h" +#include "compress.h" +#include "compress_none.h" + +/** Transfer some bytes using the identity transformation. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_cnone_compress_process(char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ + size_t n_to_copy = MIN(*in_len, *out_len); + + memcpy(*out, *in, n_to_copy); + *out += n_to_copy; + *in += n_to_copy; + *out_len -= n_to_copy; + *in_len -= n_to_copy; + if (*in_len == 0) { + return finish ? TOR_COMPRESS_DONE : TOR_COMPRESS_OK; + } else { + return TOR_COMPRESS_BUFFER_FULL; + } +} + diff --git a/src/common/compress_none.h b/src/common/compress_none.h new file mode 100644 index 0000000000..d1ebb4b625 --- /dev/null +++ b/src/common/compress_none.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_none.h + * \brief Header for compress_none.c + **/ + +#ifndef TOR_COMPRESS_NONE_H +#define TOR_COMPRESS_NONE_H + +tor_compress_output_t +tor_cnone_compress_process(char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +#endif // TOR_COMPRESS_NONE_H. + diff --git a/src/common/compress_zlib.c b/src/common/compress_zlib.c new file mode 100644 index 0000000000..284542e885 --- /dev/null +++ b/src/common/compress_zlib.c @@ -0,0 +1,304 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zlib.c + * \brief Compression backend for gzip and zlib. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "util.h" +#include "torlog.h" +#include "compress.h" +#include "compress_zlib.h" + +/* zlib 1.2.4 and 1.2.5 do some "clever" things with macros. Instead of + saying "(defined(FOO) ? FOO : 0)" they like to say "FOO-0", on the theory + that nobody will care if the compile outputs a no-such-identifier warning. + + Sorry, but we like -Werror over here, so I guess we need to define these. + I hope that zlib 1.2.6 doesn't break these too. +*/ +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE 0 +#endif +#ifndef _LFS64_LARGEFILE +#define _LFS64_LARGEFILE 0 +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 0 +#endif +#ifndef off64_t +#define off64_t int64_t +#endif + +#include <zlib.h> + +#if defined ZLIB_VERNUM && ZLIB_VERNUM < 0x1200 +#error "We require zlib version 1.2 or later." +#endif + +static size_t tor_zlib_state_size_precalc(int inflate, + int windowbits, int memlevel); + +/** Total number of bytes allocated for zlib state */ +static atomic_counter_t total_zlib_allocation; + +/** Given <b>level</b> return the memory level. */ +static int +memory_level(compression_level_t level) +{ + switch (level) { + default: + case BEST_COMPRESSION: return 9; + case HIGH_COMPRESSION: return 8; + case MEDIUM_COMPRESSION: return 7; + case LOW_COMPRESSION: return 6; + } +} + +/** Return the 'bits' value to tell zlib to use <b>method</b>.*/ +static inline int +method_bits(compress_method_t method, compression_level_t level) +{ + /* Bits+16 means "use gzip" in zlib >= 1.2 */ + const int flag = method == GZIP_METHOD ? 16 : 0; + switch (level) { + default: + case BEST_COMPRESSION: + case HIGH_COMPRESSION: return flag + 15; + case MEDIUM_COMPRESSION: return flag + 13; + case LOW_COMPRESSION: return flag + 11; + } +} + +/** Return 1 if zlib/gzip compression is supported; otherwise 0. */ +int +tor_zlib_method_supported(void) +{ + /* We currently always support zlib/gzip, but we keep this function around in + * case we some day decide to deprecate zlib/gzip support. + */ + return 1; +} + +/** Return a string representation of the version of the currently running + * version of zlib. */ +const char * +tor_zlib_get_version_str(void) +{ + return zlibVersion(); +} + +/** Return a string representation of the version of the version of zlib +* used at compilation. */ +const char * +tor_zlib_get_header_version_str(void) +{ + return ZLIB_VERSION; +} + +/** Internal zlib state for an incremental compression/decompression. + * The body of this struct is not exposed. */ +struct tor_zlib_compress_state_t { + struct z_stream_s stream; /**< The zlib stream */ + int compress; /**< True if we are compressing; false if we are inflating */ + + /** Number of bytes read so far. Used to detect zlib bombs. */ + size_t input_so_far; + /** Number of bytes written so far. Used to detect zlib bombs. */ + size_t output_so_far; + + /** Approximate number of bytes allocated for this object. */ + size_t allocation; +}; + +/** Return an approximate number of bytes used in RAM to hold a state with + * window bits <b>windowBits</b> and compression level 'memlevel' */ +static size_t +tor_zlib_state_size_precalc(int inflate_, int windowbits, int memlevel) +{ + windowbits &= 15; + +#define A_FEW_KILOBYTES 2048 + + if (inflate_) { + /* From zconf.h: + + "The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects." + */ + return sizeof(tor_zlib_compress_state_t) + sizeof(struct z_stream_s) + + (1 << 15) + A_FEW_KILOBYTES; + } else { + /* Also from zconf.h: + + "The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + ... plus a few kilobytes for small objects." + */ + return sizeof(tor_zlib_compress_state_t) + sizeof(struct z_stream_s) + + (1 << (windowbits + 2)) + (1 << (memlevel + 9)) + A_FEW_KILOBYTES; + } +#undef A_FEW_KILOBYTES +} + +/** Construct and return a tor_zlib_compress_state_t object using + * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for + * decompression. */ +tor_zlib_compress_state_t * +tor_zlib_compress_new(int compress_, + compress_method_t method, + compression_level_t compression_level) +{ + tor_zlib_compress_state_t *out; + int bits, memlevel; + + if (! compress_) { + /* use this setting for decompression, since we might have the + * max number of window bits */ + compression_level = BEST_COMPRESSION; + } + + out = tor_malloc_zero(sizeof(tor_zlib_compress_state_t)); + out->stream.zalloc = Z_NULL; + out->stream.zfree = Z_NULL; + out->stream.opaque = NULL; + out->compress = compress_; + bits = method_bits(method, compression_level); + memlevel = memory_level(compression_level); + if (compress_) { + if (deflateInit2(&out->stream, Z_BEST_COMPRESSION, Z_DEFLATED, + bits, memlevel, + Z_DEFAULT_STRATEGY) != Z_OK) + goto err; // LCOV_EXCL_LINE + } else { + if (inflateInit2(&out->stream, bits) != Z_OK) + goto err; // LCOV_EXCL_LINE + } + out->allocation = tor_zlib_state_size_precalc(!compress_, bits, memlevel); + + atomic_counter_add(&total_zlib_allocation, out->allocation); + + return out; + + err: + tor_free(out); + return NULL; +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_zlib_compress_process(tor_zlib_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ + int err; + tor_assert(state != NULL); + if (*in_len > UINT_MAX || + *out_len > UINT_MAX) { + return TOR_COMPRESS_ERROR; + } + + state->stream.next_in = (unsigned char*) *in; + state->stream.avail_in = (unsigned int)*in_len; + state->stream.next_out = (unsigned char*) *out; + state->stream.avail_out = (unsigned int)*out_len; + + if (state->compress) { + err = deflate(&state->stream, finish ? Z_FINISH : Z_NO_FLUSH); + } else { + err = inflate(&state->stream, finish ? Z_FINISH : Z_SYNC_FLUSH); + } + + state->input_so_far += state->stream.next_in - ((unsigned char*)*in); + state->output_so_far += state->stream.next_out - ((unsigned char*)*out); + + *out = (char*) state->stream.next_out; + *out_len = state->stream.avail_out; + *in = (const char *) state->stream.next_in; + *in_len = state->stream.avail_in; + + if (! state->compress && + tor_compress_is_compression_bomb(state->input_so_far, + state->output_so_far)) { + log_warn(LD_DIR, "Possible zlib bomb; abandoning stream."); + return TOR_COMPRESS_ERROR; + } + + switch (err) + { + case Z_STREAM_END: + return TOR_COMPRESS_DONE; + case Z_BUF_ERROR: + if (state->stream.avail_in == 0 && !finish) + return TOR_COMPRESS_OK; + return TOR_COMPRESS_BUFFER_FULL; + case Z_OK: + if (state->stream.avail_out == 0 || finish) + return TOR_COMPRESS_BUFFER_FULL; + return TOR_COMPRESS_OK; + default: + log_warn(LD_GENERAL, "Gzip returned an error: %s", + state->stream.msg ? state->stream.msg : "<no message>"); + return TOR_COMPRESS_ERROR; + } +} + +/** Deallocate <b>state</b>. */ +void +tor_zlib_compress_free(tor_zlib_compress_state_t *state) +{ + if (state == NULL) + return; + + atomic_counter_sub(&total_zlib_allocation, state->allocation); + + if (state->compress) + deflateEnd(&state->stream); + else + inflateEnd(&state->stream); + + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_zlib_compress_state_size(const tor_zlib_compress_state_t *state) +{ + tor_assert(state != NULL); + return state->allocation; +} + +/** Return the approximate number of bytes allocated for all zlib states. */ +size_t +tor_zlib_get_total_allocation(void) +{ + return atomic_counter_get(&total_zlib_allocation); +} + +/** Set up global state for the zlib module */ +void +tor_zlib_init(void) +{ + atomic_counter_init(&total_zlib_allocation); +} + diff --git a/src/common/compress_zlib.h b/src/common/compress_zlib.h new file mode 100644 index 0000000000..df5c196ac7 --- /dev/null +++ b/src/common/compress_zlib.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zlib.h + * \brief Header for compress_zlib.c + **/ + +#ifndef TOR_COMPRESS_ZLIB_H +#define TOR_COMPRESS_ZLIB_H + +int tor_zlib_method_supported(void); + +const char *tor_zlib_get_version_str(void); + +const char *tor_zlib_get_header_version_str(void); + +/** Internal state for an incremental zlib/gzip compression/decompression. */ +typedef struct tor_zlib_compress_state_t tor_zlib_compress_state_t; + +tor_zlib_compress_state_t * +tor_zlib_compress_new(int compress, + compress_method_t method, + compression_level_t compression_level); + +tor_compress_output_t +tor_zlib_compress_process(tor_zlib_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +void tor_zlib_compress_free(tor_zlib_compress_state_t *state); + +size_t tor_zlib_compress_state_size(const tor_zlib_compress_state_t *state); + +size_t tor_zlib_get_total_allocation(void); + +void tor_zlib_init(void); + +#endif // TOR_COMPRESS_ZLIB_H. + diff --git a/src/common/compress_zstd.c b/src/common/compress_zstd.c new file mode 100644 index 0000000000..63e92ed22d --- /dev/null +++ b/src/common/compress_zstd.c @@ -0,0 +1,442 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zstd.c + * \brief Compression backend for Zstandard. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "util.h" +#include "torlog.h" +#include "compress.h" +#include "compress_zstd.h" + +#ifdef HAVE_ZSTD +#include <zstd.h> +#endif + +/** Total number of bytes allocated for Zstandard state. */ +static atomic_counter_t total_zstd_allocation; + +#ifdef HAVE_ZSTD +/** Given <b>level</b> return the memory level. */ +static int +memory_level(compression_level_t level) +{ + switch (level) { + default: + case BEST_COMPRESSION: + case HIGH_COMPRESSION: return 9; + case MEDIUM_COMPRESSION: return 8; + case LOW_COMPRESSION: return 7; + } +} +#endif // HAVE_ZSTD. + +/** Return 1 if Zstandard compression is supported; otherwise 0. */ +int +tor_zstd_method_supported(void) +{ +#ifdef HAVE_ZSTD + return 1; +#else + return 0; +#endif +} + +/** Return a string representation of the version of the currently running + * version of libzstd. Returns NULL if Zstandard is unsupported. */ +const char * +tor_zstd_get_version_str(void) +{ +#ifdef HAVE_ZSTD + static char version_str[16]; + size_t version_number; + + version_number = ZSTD_versionNumber(); + tor_snprintf(version_str, sizeof(version_str), + "%d.%d.%d", + (int) version_number / 10000 % 100, + (int) version_number / 100 % 100, + (int) version_number % 100); + + return version_str; +#else + return NULL; +#endif +} + +/** Return a string representation of the version of the version of libzstd + * used at compilation time. Returns NULL if Zstandard is unsupported. */ +const char * +tor_zstd_get_header_version_str(void) +{ +#ifdef HAVE_ZSTD + return ZSTD_VERSION_STRING; +#else + return NULL; +#endif +} + +/** Internal Zstandard state for incremental compression/decompression. + * The body of this struct is not exposed. */ +struct tor_zstd_compress_state_t { +#ifdef HAVE_ZSTD + union { + /** Compression stream. Used when <b>compress</b> is true. */ + ZSTD_CStream *compress_stream; + /** Decompression stream. Used when <b>compress</b> is false. */ + ZSTD_DStream *decompress_stream; + } u; /**< Zstandard stream objects. */ +#endif // HAVE_ZSTD. + + int compress; /**< True if we are compressing; false if we are inflating */ + int have_called_end; /**< True if we are compressing and we've called + * ZSTD_endStream */ + + /** Number of bytes read so far. Used to detect compression bombs. */ + size_t input_so_far; + /** Number of bytes written so far. Used to detect compression bombs. */ + size_t output_so_far; + + /** Approximate number of bytes allocated for this object. */ + size_t allocation; +}; + +#ifdef HAVE_ZSTD +/** Return an approximate number of bytes stored in memory to hold the + * Zstandard compression/decompression state. */ +static size_t +tor_zstd_state_size_precalc(int compress, int preset) +{ + tor_assert(preset > 0); + + size_t memory_usage = sizeof(tor_zstd_compress_state_t); + + // The Zstandard library provides a number of functions that would be useful + // here, but they are, unfortunately, still considered experimental and are + // thus only available in libzstd if we link against the library statically. + // + // The code in this function tries to approximate the calculations without + // being able to use the following: + // + // - We do not have access to neither the internal members of ZSTD_CStream + // and ZSTD_DStream and their internal context objects. + // + // - We cannot use ZSTD_sizeof_CStream() and ZSTD_sizeof_DStream() since they + // are unexposed. + // + // In the future it might be useful to check if libzstd have started + // providing these functions in a stable manner and simplify this function. + if (compress) { + // We try to approximate the ZSTD_sizeof_CStream(ZSTD_CStream *stream) + // function here. This function uses the following fields to make its + // estimate: + + // - sizeof(ZSTD_CStream): Around 192 bytes on a 64-bit machine: + memory_usage += 192; + + // - ZSTD_sizeof_CCtx(stream->cctx): This function requires access to + // variables that are not exposed via the public API. We use a _very_ + // simplified function to calculate the estimated amount of bytes used in + // this struct. + // memory_usage += (preset - 0.5) * 1024 * 1024; + memory_usage += (preset * 1024 * 1024) - (512 * 1024); + // - ZSTD_sizeof_CDict(stream->cdictLocal): Unused in Tor: 0 bytes. + // - stream->outBuffSize: 128 KB: + memory_usage += 128 * 1024; + // - stream->inBuffSize: 2048 KB: + memory_usage += 2048 * 1024; + } else { + // We try to approximate the ZSTD_sizeof_DStream(ZSTD_DStream *stream) + // function here. This function uses the following fields to make its + // estimate: + + // - sizeof(ZSTD_DStream): Around 208 bytes on a 64-bit machine: + memory_usage += 208; + // - ZSTD_sizeof_DCtx(stream->dctx): Around 150 KB. + memory_usage += 150 * 1024; + + // - ZSTD_sizeof_DDict(stream->ddictLocal): Unused in Tor: 0 bytes. + // - stream->inBuffSize: 0 KB. + // - stream->outBuffSize: 0 KB. + } + + return memory_usage; +} +#endif // HAVE_ZSTD. + +/** Construct and return a tor_zstd_compress_state_t object using + * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for + * decompression. */ +tor_zstd_compress_state_t * +tor_zstd_compress_new(int compress, + compress_method_t method, + compression_level_t level) +{ + tor_assert(method == ZSTD_METHOD); + +#ifdef HAVE_ZSTD + const int preset = memory_level(level); + tor_zstd_compress_state_t *result; + size_t retval; + + result = tor_malloc_zero(sizeof(tor_zstd_compress_state_t)); + result->compress = compress; + result->allocation = tor_zstd_state_size_precalc(compress, preset); + + if (compress) { + result->u.compress_stream = ZSTD_createCStream(); + + if (result->u.compress_stream == NULL) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error while creating Zstandard compression " + "stream"); + goto err; + // LCOV_EXCL_STOP + } + + retval = ZSTD_initCStream(result->u.compress_stream, preset); + + if (ZSTD_isError(retval)) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Zstandard stream initialization error: %s", + ZSTD_getErrorName(retval)); + goto err; + // LCOV_EXCL_STOP + } + } else { + result->u.decompress_stream = ZSTD_createDStream(); + + if (result->u.decompress_stream == NULL) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error while creating Zstandard decompression " + "stream"); + goto err; + // LCOV_EXCL_STOP + } + + retval = ZSTD_initDStream(result->u.decompress_stream); + + if (ZSTD_isError(retval)) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Zstandard stream initialization error: %s", + ZSTD_getErrorName(retval)); + goto err; + // LCOV_EXCL_STOP + } + } + + atomic_counter_add(&total_zstd_allocation, result->allocation); + return result; + + err: + // LCOV_EXCL_START + if (compress) { + ZSTD_freeCStream(result->u.compress_stream); + } else { + ZSTD_freeDStream(result->u.decompress_stream); + } + + tor_free(result); + return NULL; + // LCOV_EXCL_STOP +#else // HAVE_ZSTD. + (void)compress; + (void)method; + (void)level; + + return NULL; +#endif // HAVE_ZSTD. +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_zstd_compress_process(tor_zstd_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ +#ifdef HAVE_ZSTD + size_t retval; + + tor_assert(state != NULL); + tor_assert(*in_len <= UINT_MAX); + tor_assert(*out_len <= UINT_MAX); + + ZSTD_inBuffer input = { *in, *in_len, 0 }; + ZSTD_outBuffer output = { *out, *out_len, 0 }; + + if (BUG(finish == 0 && state->have_called_end)) { + finish = 1; + } + + if (state->compress) { + if (! state->have_called_end) + retval = ZSTD_compressStream(state->u.compress_stream, + &output, &input); + else + retval = 0; + } else { + retval = ZSTD_decompressStream(state->u.decompress_stream, + &output, &input); + } + + state->input_so_far += input.pos; + state->output_so_far += output.pos; + + *out = (char *)output.dst + output.pos; + *out_len = output.size - output.pos; + *in = (char *)input.src + input.pos; + *in_len = input.size - input.pos; + + if (! state->compress && + tor_compress_is_compression_bomb(state->input_so_far, + state->output_so_far)) { + log_warn(LD_DIR, "Possible compression bomb; abandoning stream."); + return TOR_COMPRESS_ERROR; + } + + if (ZSTD_isError(retval)) { + log_warn(LD_GENERAL, "Zstandard %s didn't finish: %s.", + state->compress ? "compression" : "decompression", + ZSTD_getErrorName(retval)); + return TOR_COMPRESS_ERROR; + } + + if (state->compress && !state->have_called_end) { + retval = ZSTD_flushStream(state->u.compress_stream, &output); + + *out = (char *)output.dst + output.pos; + *out_len = output.size - output.pos; + + if (ZSTD_isError(retval)) { + log_warn(LD_GENERAL, "Zstandard compression unable to flush: %s.", + ZSTD_getErrorName(retval)); + return TOR_COMPRESS_ERROR; + } + + // ZSTD_flushStream returns 0 if the frame is done, or >0 if it + // is incomplete. + if (retval > 0) { + return TOR_COMPRESS_BUFFER_FULL; + } + } + + if (!finish) { + // The caller says we're not done with the input, so no need to write an + // epilogue. + return TOR_COMPRESS_OK; + } else if (state->compress) { + if (*in_len) { + // We say that we're not done with the input, so we can't write an + // epilogue. + return TOR_COMPRESS_OK; + } + + retval = ZSTD_endStream(state->u.compress_stream, &output); + state->have_called_end = 1; + *out = (char *)output.dst + output.pos; + *out_len = output.size - output.pos; + + if (ZSTD_isError(retval)) { + log_warn(LD_GENERAL, "Zstandard compression unable to write " + "epilogue: %s.", + ZSTD_getErrorName(retval)); + return TOR_COMPRESS_ERROR; + } + + // endStream returns the number of bytes that is needed to write the + // epilogue. + if (retval > 0) + return TOR_COMPRESS_BUFFER_FULL; + + return TOR_COMPRESS_DONE; + } else /* if (!state->compress) */ { + // ZSTD_decompressStream returns 0 if the frame is done, or >0 if it + // is incomplete. + // We check this above. + tor_assert_nonfatal(!ZSTD_isError(retval)); + // Start a new frame if this frame is done + if (retval == 0) + return TOR_COMPRESS_DONE; + // Don't check out_len, it might have some space left if the next output + // chunk is larger than the remaining space + else if (*in_len > 0) + return TOR_COMPRESS_BUFFER_FULL; + else + return TOR_COMPRESS_OK; + } + +#else // HAVE_ZSTD. + (void)state; + (void)out; + (void)out_len; + (void)in; + (void)in_len; + (void)finish; + + return TOR_COMPRESS_ERROR; +#endif // HAVE_ZSTD. +} + +/** Deallocate <b>state</b>. */ +void +tor_zstd_compress_free(tor_zstd_compress_state_t *state) +{ + if (state == NULL) + return; + + atomic_counter_sub(&total_zstd_allocation, state->allocation); + +#ifdef HAVE_ZSTD + if (state->compress) { + ZSTD_freeCStream(state->u.compress_stream); + } else { + ZSTD_freeDStream(state->u.decompress_stream); + } +#endif // HAVE_ZSTD. + + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_zstd_compress_state_size(const tor_zstd_compress_state_t *state) +{ + tor_assert(state != NULL); + return state->allocation; +} + +/** Return the approximate number of bytes allocated for all Zstandard + * states. */ +size_t +tor_zstd_get_total_allocation(void) +{ + return atomic_counter_get(&total_zstd_allocation); +} + +/** Initialize the zstd module */ +void +tor_zstd_init(void) +{ + atomic_counter_init(&total_zstd_allocation); +} + diff --git a/src/common/compress_zstd.h b/src/common/compress_zstd.h new file mode 100644 index 0000000000..d3e65c2f16 --- /dev/null +++ b/src/common/compress_zstd.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zstd.h + * \brief Header for compress_zstd.c + **/ + +#ifndef TOR_COMPRESS_ZSTD_H +#define TOR_COMPRESS_ZSTD_H + +int tor_zstd_method_supported(void); + +const char *tor_zstd_get_version_str(void); + +const char *tor_zstd_get_header_version_str(void); + +/** Internal state for an incremental Zstandard compression/decompression. */ +typedef struct tor_zstd_compress_state_t tor_zstd_compress_state_t; + +tor_zstd_compress_state_t * +tor_zstd_compress_new(int compress, + compress_method_t method, + compression_level_t compression_level); + +tor_compress_output_t +tor_zstd_compress_process(tor_zstd_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +void tor_zstd_compress_free(tor_zstd_compress_state_t *state); + +size_t tor_zstd_compress_state_size(const tor_zstd_compress_state_t *state); + +size_t tor_zstd_get_total_allocation(void); + +void tor_zstd_init(void); + +#endif // TOR_COMPRESS_ZSTD_H. + diff --git a/src/common/confline.c b/src/common/confline.c new file mode 100644 index 0000000000..15fd96bf38 --- /dev/null +++ b/src/common/confline.c @@ -0,0 +1,527 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "compat.h" +#include "confline.h" +#include "torlog.h" +#include "util.h" +#include "container.h" + +static int config_get_lines_aux(const char *string, config_line_t **result, + int extended, int allow_include, + int *has_include, int recursion_level, + config_line_t **last); +static smartlist_t *config_get_file_list(const char *path); +static int config_get_included_list(const char *path, int recursion_level, + int extended, config_line_t **list, + config_line_t **list_last); +static int config_process_include(const char *path, int recursion_level, + int extended, config_line_t **list, + config_line_t **list_last); + +/** Helper: allocate a new configuration option mapping 'key' to 'val', + * append it to *<b>lst</b>. */ +void +config_line_append(config_line_t **lst, + const char *key, + const char *val) +{ + tor_assert(lst); + + config_line_t *newline; + + newline = tor_malloc_zero(sizeof(config_line_t)); + newline->key = tor_strdup(key); + newline->value = tor_strdup(val); + newline->next = NULL; + while (*lst) + lst = &((*lst)->next); + + (*lst) = newline; +} + +/** Helper: allocate a new configuration option mapping 'key' to 'val', + * and prepend it to *<b>lst</b> */ +void +config_line_prepend(config_line_t **lst, + const char *key, + const char *val) +{ + tor_assert(lst); + + config_line_t *newline; + + newline = tor_malloc_zero(sizeof(config_line_t)); + newline->key = tor_strdup(key); + newline->value = tor_strdup(val); + newline->next = *lst; + *lst = newline; +} + +/** Return the first line in <b>lines</b> whose key is exactly <b>key</b>, or + * NULL if no such key exists. + * + * (In options parsing, this is for handling commandline-only options only; + * other options should be looked up in the appropriate data structure.) */ +const config_line_t * +config_line_find(const config_line_t *lines, + const char *key) +{ + const config_line_t *cl; + for (cl = lines; cl; cl = cl->next) { + if (!strcmp(cl->key, key)) + return cl; + } + return NULL; +} + +/** Auxiliary function that does all the work of config_get_lines. + * <b>recursion_level</b> is the count of how many nested %includes we have. + * Returns the a pointer to the last element of the <b>result</b> in + * <b>last</b>. */ +static int +config_get_lines_aux(const char *string, config_line_t **result, int extended, + int allow_include, int *has_include, int recursion_level, + config_line_t **last) +{ + config_line_t *list = NULL, **next, *list_last = NULL; + char *k, *v; + const char *parse_err; + int include_used = 0; + + if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) { + log_warn(LD_CONFIG, "Error while parsing configuration: more than %d " + "nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL); + return -1; + } + + next = &list; + do { + k = v = NULL; + string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err); + if (!string) { + log_warn(LD_CONFIG, "Error while parsing configuration: %s", + parse_err?parse_err:"<unknown>"); + config_free_lines(list); + tor_free(k); + tor_free(v); + return -1; + } + if (k && v) { + unsigned command = CONFIG_LINE_NORMAL; + if (extended) { + if (k[0] == '+') { + char *k_new = tor_strdup(k+1); + tor_free(k); + k = k_new; + command = CONFIG_LINE_APPEND; + } else if (k[0] == '/') { + char *k_new = tor_strdup(k+1); + tor_free(k); + k = k_new; + tor_free(v); + v = tor_strdup(""); + command = CONFIG_LINE_CLEAR; + } + } + + if (allow_include && !strcmp(k, "%include")) { + tor_free(k); + include_used = 1; + + config_line_t *include_list; + if (config_process_include(v, recursion_level, extended, &include_list, + &list_last) < 0) { + log_warn(LD_CONFIG, "Error reading included configuration " + "file or directory: \"%s\".", v); + config_free_lines(list); + tor_free(v); + return -1; + } + *next = include_list; + if (list_last) + next = &list_last->next; + tor_free(v); + } else { + /* This list can get long, so we keep a pointer to the end of it + * rather than using config_line_append over and over and getting + * n^2 performance. */ + *next = tor_malloc_zero(sizeof(**next)); + (*next)->key = k; + (*next)->value = v; + (*next)->next = NULL; + (*next)->command = command; + list_last = *next; + next = &((*next)->next); + } + } else { + tor_free(k); + tor_free(v); + } + } while (*string); + + if (last) { + *last = list_last; + } + if (has_include) { + *has_include = include_used; + } + *result = list; + return 0; +} + +/** Helper: parse the config string and strdup into key/value + * strings. Set *result to the list, or NULL if parsing the string + * failed. Set *has_include to 1 if <b>result</b> has values from + * %included files. Return 0 on success, -1 on failure. Warn and ignore any + * misformatted lines. + * + * If <b>extended</b> is set, then treat keys beginning with / and with + as + * indicating "clear" and "append" respectively. */ +int +config_get_lines_include(const char *string, config_line_t **result, + int extended, int *has_include) +{ + return config_get_lines_aux(string, result, extended, 1, has_include, 1, + NULL); +} + +/** Same as config_get_lines_include but does not allow %include */ +int +config_get_lines(const char *string, config_line_t **result, int extended) +{ + return config_get_lines_aux(string, result, extended, 0, NULL, 1, NULL); +} + +/** Adds a list of configuration files present on <b>path</b> to + * <b>file_list</b>. <b>path</b> can be a file or a directory. If it is a file, + * only that file will be added to <b>file_list</b>. If it is a directory, + * all paths for files on that directory root (no recursion) except for files + * whose name starts with a dot will be added to <b>file_list</b>. + * Return 0 on success, -1 on failure. Ignores empty files. + */ +static smartlist_t * +config_get_file_list(const char *path) +{ + smartlist_t *file_list = smartlist_new(); + file_status_t file_type = file_status(path); + if (file_type == FN_FILE) { + smartlist_add_strdup(file_list, path); + return file_list; + } else if (file_type == FN_DIR) { + smartlist_t *all_files = tor_listdir(path); + if (!all_files) { + smartlist_free(file_list); + return NULL; + } + smartlist_sort_strings(all_files); + SMARTLIST_FOREACH_BEGIN(all_files, char *, f) { + if (f[0] == '.') { + tor_free(f); + continue; + } + + char *fullname; + tor_asprintf(&fullname, "%s"PATH_SEPARATOR"%s", path, f); + tor_free(f); + + if (file_status(fullname) != FN_FILE) { + tor_free(fullname); + continue; + } + smartlist_add(file_list, fullname); + } SMARTLIST_FOREACH_END(f); + smartlist_free(all_files); + return file_list; + } else if (file_type == FN_EMPTY) { + return file_list; + } else { + smartlist_free(file_list); + return NULL; + } +} + +/** Creates a list of config lines present on included <b>path</b>. + * Set <b>list</b> to the list and <b>list_last</b> to the last element of + * <b>list</b>. Return 0 on success, -1 on failure. */ +static int +config_get_included_list(const char *path, int recursion_level, int extended, + config_line_t **list, config_line_t **list_last) +{ + char *included_conf = read_file_to_str(path, 0, NULL); + if (!included_conf) { + return -1; + } + + if (config_get_lines_aux(included_conf, list, extended, 1, NULL, + recursion_level+1, list_last) < 0) { + tor_free(included_conf); + return -1; + } + + tor_free(included_conf); + return 0; +} + +/** Process an %include <b>path</b> in a config file. Set <b>list</b> to the + * list of configuration settings obtained and <b>list_last</b> to the last + * element of the same list. Return 0 on success, -1 on failure. */ +static int +config_process_include(const char *path, int recursion_level, int extended, + config_line_t **list, config_line_t **list_last) +{ + config_line_t *ret_list = NULL; + config_line_t **next = &ret_list; +#if 0 + // Disabled -- we already unescape_string() on the result. */ + char *unquoted_path = get_unquoted_path(path); + if (!unquoted_path) { + return -1; + } + + smartlist_t *config_files = config_get_file_list(unquoted_path); + if (!config_files) { + tor_free(unquoted_path); + return -1; + } + tor_free(unquoted_path); +#endif + smartlist_t *config_files = config_get_file_list(path); + if (!config_files) { + return -1; + } + + int rv = -1; + SMARTLIST_FOREACH_BEGIN(config_files, const char *, config_file) { + config_line_t *included_list = NULL; + if (config_get_included_list(config_file, recursion_level, extended, + &included_list, list_last) < 0) { + goto done; + } + + *next = included_list; + if (*list_last) + next = &(*list_last)->next; + + } SMARTLIST_FOREACH_END(config_file); + *list = ret_list; + rv = 0; + + done: + SMARTLIST_FOREACH(config_files, char *, f, tor_free(f)); + smartlist_free(config_files); + return rv; +} + +/** + * Free all the configuration lines on the linked list <b>front</b>. + */ +void +config_free_lines(config_line_t *front) +{ + config_line_t *tmp; + + while (front) { + tmp = front; + front = tmp->next; + + tor_free(tmp->key); + tor_free(tmp->value); + tor_free(tmp); + } +} + +/** Return a newly allocated deep copy of the lines in <b>inp</b>. */ +config_line_t * +config_lines_dup(const config_line_t *inp) +{ + return config_lines_dup_and_filter(inp, NULL); +} + +/** Return a newly allocated deep copy of the lines in <b>inp</b>, + * but only the ones that match <b>key</b>. */ +config_line_t * +config_lines_dup_and_filter(const config_line_t *inp, + const char *key) +{ + config_line_t *result = NULL; + config_line_t **next_out = &result; + while (inp) { + if (key && strcasecmpstart(inp->key, key)) { + inp = inp->next; + continue; + } + *next_out = tor_malloc_zero(sizeof(config_line_t)); + (*next_out)->key = tor_strdup(inp->key); + (*next_out)->value = tor_strdup(inp->value); + inp = inp->next; + next_out = &((*next_out)->next); + } + (*next_out) = NULL; + return result; +} + +/** Return true iff a and b contain identical keys and values in identical + * order. */ +int +config_lines_eq(config_line_t *a, config_line_t *b) +{ + while (a && b) { + if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value)) + return 0; + a = a->next; + b = b->next; + } + if (a || b) + return 0; + return 1; +} + +/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */ +int +config_count_key(const config_line_t *a, const char *key) +{ + int n = 0; + while (a) { + if (!strcasecmp(a->key, key)) { + ++n; + } + a = a->next; + } + return n; +} + +/** Given a string containing part of a configuration file or similar format, + * advance past comments and whitespace and try to parse a single line. If we + * parse a line successfully, set *<b>key_out</b> to a new string holding the + * key portion and *<b>value_out</b> to a new string holding the value portion + * of the line, and return a pointer to the start of the next line. If we run + * out of data, return a pointer to the end of the string. If we encounter an + * error, return NULL and set *<b>err_out</b> (if provided) to an error + * message. + */ +const char * +parse_config_line_from_str_verbose(const char *line, char **key_out, + char **value_out, + const char **err_out) +{ + /* + See torrc_format.txt for a description of the (silly) format this parses. + */ + const char *key, *val, *cp; + int continuation = 0; + + tor_assert(key_out); + tor_assert(value_out); + + *key_out = *value_out = NULL; + key = val = NULL; + /* Skip until the first keyword. */ + while (1) { + while (TOR_ISSPACE(*line)) + ++line; + if (*line == '#') { + while (*line && *line != '\n') + ++line; + } else { + break; + } + } + + if (!*line) { /* End of string? */ + *key_out = *value_out = NULL; + return line; + } + + /* Skip until the next space or \ followed by newline. */ + key = line; + while (*line && !TOR_ISSPACE(*line) && *line != '#' && + ! (line[0] == '\\' && line[1] == '\n')) + ++line; + *key_out = tor_strndup(key, line-key); + + /* Skip until the value. */ + while (*line == ' ' || *line == '\t') + ++line; + + val = line; + + /* Find the end of the line. */ + if (*line == '\"') { // XXX No continuation handling is done here + if (!(line = unescape_string(line, value_out, NULL))) { + if (err_out) + *err_out = "Invalid escape sequence in quoted string"; + return NULL; + } + while (*line == ' ' || *line == '\t') + ++line; + if (*line == '\r' && *(++line) == '\n') + ++line; + if (*line && *line != '#' && *line != '\n') { + if (err_out) + *err_out = "Excess data after quoted string"; + return NULL; + } + } else { + /* Look for the end of the line. */ + while (*line && *line != '\n' && (*line != '#' || continuation)) { + if (*line == '\\' && line[1] == '\n') { + continuation = 1; + line += 2; + } else if (*line == '#') { + do { + ++line; + } while (*line && *line != '\n'); + if (*line == '\n') + ++line; + } else { + ++line; + } + } + + if (*line == '\n') { + cp = line++; + } else { + cp = line; + } + /* Now back cp up to be the last nonspace character */ + while (cp>val && TOR_ISSPACE(*(cp-1))) + --cp; + + tor_assert(cp >= val); + + /* Now copy out and decode the value. */ + *value_out = tor_strndup(val, cp-val); + if (continuation) { + char *v_out, *v_in; + v_out = v_in = *value_out; + while (*v_in) { + if (*v_in == '#') { + do { + ++v_in; + } while (*v_in && *v_in != '\n'); + if (*v_in == '\n') + ++v_in; + } else if (v_in[0] == '\\' && v_in[1] == '\n') { + v_in += 2; + } else { + *v_out++ = *v_in++; + } + } + *v_out = '\0'; + } + } + + if (*line == '#') { + do { + ++line; + } while (*line && *line != '\n'); + } + while (TOR_ISSPACE(*line)) ++line; + + return line; +} + diff --git a/src/common/confline.h b/src/common/confline.h new file mode 100644 index 0000000000..eb863e8fd8 --- /dev/null +++ b/src/common/confline.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_CONFLINE_H +#define TOR_CONFLINE_H + +/** Ordinary configuration line. */ +#define CONFIG_LINE_NORMAL 0 +/** Appends to previous configuration for the same option, even if we + * would ordinary replace it. */ +#define CONFIG_LINE_APPEND 1 +/* Removes all previous configuration for an option. */ +#define CONFIG_LINE_CLEAR 2 + +#define MAX_INCLUDE_RECURSION_LEVEL 31 + +/** A linked list of lines in a config file, or elsewhere */ +typedef struct config_line_t { + char *key; + char *value; + struct config_line_t *next; + + /** What special treatment (if any) does this line require? */ + unsigned int command:2; + /** If true, subsequent assignments to this linelist should replace + * it, not extend it. Set only on the first item in a linelist in an + * or_options_t. */ + unsigned int fragile:1; +} config_line_t; + +void config_line_append(config_line_t **lst, + const char *key, const char *val); +void config_line_prepend(config_line_t **lst, + const char *key, const char *val); +config_line_t *config_lines_dup(const config_line_t *inp); +config_line_t *config_lines_dup_and_filter(const config_line_t *inp, + const char *key); +const config_line_t *config_line_find(const config_line_t *lines, + const char *key); +int config_lines_eq(config_line_t *a, config_line_t *b); +int config_count_key(const config_line_t *a, const char *key); +int config_get_lines(const char *string, config_line_t **result, int extended); +int config_get_lines_include(const char *string, config_line_t **result, + int extended, int *has_include); +void config_free_lines(config_line_t *front); +const char *parse_config_line_from_str_verbose(const char *line, + char **key_out, char **value_out, + const char **err_out); +#endif + diff --git a/src/common/container.c b/src/common/container.c index 1448ab403c..689e7e55e9 100644 --- a/src/common/container.c +++ b/src/common/container.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/container.h b/src/common/container.h index 00c3ca81ad..db68d892f5 100644 --- a/src/common/container.h +++ b/src/common/container.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_CONTAINER_H @@ -224,6 +224,7 @@ char *smartlist_join_strings2(smartlist_t *sl, const char *join, #define SMARTLIST_FOREACH_END(var) \ var = NULL; \ + (void) var ## _sl_idx; \ } STMT_END /** diff --git a/src/common/crypto.c b/src/common/crypto.c index d1357182d2..b82f181a39 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -467,7 +467,7 @@ crypto_new_pk_from_rsa_(RSA *rsa) return env; } -/** Helper, used by tor-checkkey.c and tor-gencert.c. Return the RSA from a +/** Helper, used by tor-gencert.c. Return the RSA from a * crypto_pk_t. */ RSA * crypto_pk_get_rsa_(crypto_pk_t *env) @@ -479,7 +479,7 @@ crypto_pk_get_rsa_(crypto_pk_t *env) * private is set, include the private-key portion of the key. Return a valid * pointer on success, and NULL on failure. */ MOCK_IMPL(EVP_PKEY *, - crypto_pk_get_evp_pkey_,(crypto_pk_t *env, int private)) +crypto_pk_get_evp_pkey_,(crypto_pk_t *env, int private)) { RSA *key = NULL; EVP_PKEY *pkey = NULL; @@ -516,7 +516,7 @@ crypto_dh_get_dh_(crypto_dh_t *dh) * be set. */ MOCK_IMPL(crypto_pk_t *, - crypto_pk_new,(void)) +crypto_pk_new,(void)) { RSA *rsa; @@ -606,7 +606,7 @@ crypto_cipher_free(crypto_cipher_t *env) * Return 0 on success, -1 on failure. */ MOCK_IMPL(int, - crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) +crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) { tor_assert(env); @@ -3469,3 +3469,15 @@ crypto_global_cleanup(void) /** @} */ +#ifdef USE_DMALLOC +/** Tell the crypto library to use Tor's allocation functions rather than + * calling libc's allocation functions directly. Return 0 on success, -1 + * on failure. */ +int +crypto_use_tor_alloc_functions(void) +{ + int r = CRYPTO_set_mem_ex_functions(tor_malloc_, tor_realloc_, tor_free_); + return r ? 0 : -1; +} +#endif + diff --git a/src/common/crypto.h b/src/common/crypto.h index 42345f80e8..c70d91c262 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -131,6 +131,10 @@ int crypto_early_init(void) ATTR_WUR; int crypto_global_init(int hardwareAccel, const char *accelName, const char *accelPath) ATTR_WUR; +#ifdef USE_DMALLOC +int crypto_use_tor_alloc_functions(void); +#endif + void crypto_thread_cleanup(void); int crypto_global_cleanup(void); diff --git a/src/common/crypto_curve25519.c b/src/common/crypto_curve25519.c index 5f328e124c..b99f13a93b 100644 --- a/src/common/crypto_curve25519.c +++ b/src/common/crypto_curve25519.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/crypto_curve25519.h b/src/common/crypto_curve25519.h index 4011820949..e7790edac0 100644 --- a/src/common/crypto_curve25519.h +++ b/src/common/crypto_curve25519.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_CRYPTO_CURVE25519_H diff --git a/src/common/crypto_ed25519.c b/src/common/crypto_ed25519.c index 525d25a3e0..188e18c710 100644 --- a/src/common/crypto_ed25519.c +++ b/src/common/crypto_ed25519.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -32,8 +32,6 @@ #include "ed25519/ref10/ed25519_ref10.h" #include "ed25519/donna/ed25519_donna_tor.h" -#include <openssl/sha.h> - static void pick_ed25519_impl(void); /** An Ed25519 implementation, as a set of function pointers. */ @@ -442,14 +440,16 @@ ed25519_keypair_from_curve25519_keypair(ed25519_keypair_t *out, { const char string[] = "Derive high part of ed25519 key from curve25519 key"; ed25519_public_key_t pubkey_check; - SHA512_CTX ctx; - uint8_t sha512_output[64]; + crypto_digest_t *ctx; + uint8_t sha512_output[DIGEST512_LEN]; memcpy(out->seckey.seckey, inp->seckey.secret_key, 32); - SHA512_Init(&ctx); - SHA512_Update(&ctx, out->seckey.seckey, 32); - SHA512_Update(&ctx, string, sizeof(string)); - SHA512_Final(sha512_output, &ctx); + + ctx = crypto_digest512_new(DIGEST_SHA512); + crypto_digest_add_bytes(ctx, (const char*)out->seckey.seckey, 32); + crypto_digest_add_bytes(ctx, (const char*)string, sizeof(string)); + crypto_digest_get_digest(ctx, (char *)sha512_output, sizeof(sha512_output)); + crypto_digest_free(ctx); memcpy(out->seckey.seckey + 32, sha512_output, 32); ed25519_public_key_generate(&out->pubkey, &out->seckey); diff --git a/src/common/crypto_ed25519.h b/src/common/crypto_ed25519.h index f4a4adad68..77a3313adc 100644 --- a/src/common/crypto_ed25519.h +++ b/src/common/crypto_ed25519.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_CRYPTO_ED25519_H diff --git a/src/common/crypto_format.c b/src/common/crypto_format.c index aa2a9d1fb0..1d090a8770 100644 --- a/src/common/crypto_format.c +++ b/src/common/crypto_format.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/crypto_format.h b/src/common/crypto_format.h index 86c29d319c..390916cf04 100644 --- a/src/common/crypto_format.h +++ b/src/common/crypto_format.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_CRYPTO_FORMAT_H diff --git a/src/common/crypto_pwbox.c b/src/common/crypto_pwbox.c index 31e37c007d..db8892e376 100644 --- a/src/common/crypto_pwbox.c +++ b/src/common/crypto_pwbox.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/crypto_s2k.c b/src/common/crypto_s2k.c index 5dbd2ad91f..076df815a9 100644 --- a/src/common/crypto_s2k.c +++ b/src/common/crypto_s2k.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/crypto_s2k.h b/src/common/crypto_s2k.h index 9b186450b1..04212b868a 100644 --- a/src/common/crypto_s2k.h +++ b/src/common/crypto_s2k.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_CRYPTO_S2K_H_INCLUDED diff --git a/src/common/di_ops.c b/src/common/di_ops.c index 4ed49e1164..e47998107d 100644 --- a/src/common/di_ops.c +++ b/src/common/di_ops.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016, The Tor Project, Inc. */ +/* Copyright (c) 2011-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/di_ops.h b/src/common/di_ops.h index 0a154302bf..e174fcc4e4 100644 --- a/src/common/di_ops.h +++ b/src/common/di_ops.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/handles.h b/src/common/handles.h index 1ee2322579..6d7262ab80 100644 --- a/src/common/handles.h +++ b/src/common/handles.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/include.am b/src/common/include.am index 40c463c9d9..1253888815 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -84,6 +84,7 @@ LIBOR_A_SRC = \ src/common/compat.c \ src/common/compat_threads.c \ src/common/compat_time.c \ + src/common/confline.c \ src/common/container.c \ src/common/log.c \ src/common/memarea.c \ @@ -93,21 +94,31 @@ LIBOR_A_SRC = \ src/common/util_format.c \ src/common/util_process.c \ src/common/sandbox.c \ + src/common/storagedir.c \ src/common/workqueue.c \ $(libor_extra_source) \ $(threads_impl_source) \ $(readpassphrase_source) +if USE_RUST +else +LIBOR_A_SRC += src/common/compat_rust.c +endif + src/common/src_common_libor_testing_a-log.$(OBJEXT) \ src/common/log.$(OBJEXT): micro-revision.i LIBOR_CRYPTO_A_SRC = \ src/common/aes.c \ + src/common/compress.c \ + src/common/compress_lzma.c \ + src/common/compress_none.c \ + src/common/compress_zlib.c \ + src/common/compress_zstd.c \ src/common/crypto.c \ src/common/crypto_pwbox.c \ src/common/crypto_s2k.c \ src/common/crypto_format.c \ - src/common/torgzip.c \ src/common/tortls.c \ src/common/crypto_curve25519.c \ src/common/crypto_ed25519.c @@ -141,8 +152,15 @@ COMMONHEADERS = \ src/common/compat.h \ src/common/compat_libevent.h \ src/common/compat_openssl.h \ + src/common/compat_rust.h \ src/common/compat_threads.h \ src/common/compat_time.h \ + src/common/compress.h \ + src/common/compress_lzma.h \ + src/common/compress_none.h \ + src/common/compress_zlib.h \ + src/common/compress_zstd.h \ + src/common/confline.h \ src/common/container.h \ src/common/crypto.h \ src/common/crypto_curve25519.h \ @@ -157,9 +175,9 @@ COMMONHEADERS = \ src/common/procmon.h \ src/common/pubsub.h \ src/common/sandbox.h \ + src/common/storagedir.h \ src/common/testsupport.h \ src/common/timers.h \ - src/common/torgzip.h \ src/common/torint.h \ src/common/torlog.h \ src/common/tortls.h \ diff --git a/src/common/log.c b/src/common/log.c index 5f7151bf0c..6a5819064a 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -1086,7 +1086,7 @@ add_file_log(const log_severity_list_t *severity, const char *filename, int open_flags = O_WRONLY|O_CREAT; open_flags |= truncate_log ? O_TRUNC : O_APPEND; - fd = tor_open_cloexec(filename, open_flags, 0644); + fd = tor_open_cloexec(filename, open_flags, 0640); if (fd<0) return -1; if (tor_fd_seekend(fd)<0) { @@ -1177,7 +1177,7 @@ static const char *domain_list[] = { "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM", "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV", "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL", - "SCHED", "GUARD", NULL + "SCHED", "GUARD", "CONSDIFF", NULL }; /** Return a bitmask for the log domain for which <b>domain</b> is the name, diff --git a/src/common/memarea.c b/src/common/memarea.c index 7d16b702e3..659d1edf54 100644 --- a/src/common/memarea.c +++ b/src/common/memarea.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2016, The Tor Project, Inc. */ +/* Copyright (c) 2008-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** \file memarea.c @@ -12,6 +12,9 @@ #include "util.h" #include "compat.h" #include "torlog.h" +#include "container.h" + +#ifndef DISABLE_MEMORY_SENTINELS /** If true, we try to detect any attempts to write beyond the length of a * memarea. */ @@ -304,3 +307,91 @@ memarea_assert_ok(memarea_t *area) } } +#else + +struct memarea_t { + smartlist_t *pieces; +}; + +memarea_t * +memarea_new(void) +{ + memarea_t *ma = tor_malloc_zero(sizeof(memarea_t)); + ma->pieces = smartlist_new(); + return ma; +} +void +memarea_drop_all(memarea_t *area) +{ + memarea_clear(area); + smartlist_free(area->pieces); + tor_free(area); +} +void +memarea_clear(memarea_t *area) +{ + SMARTLIST_FOREACH(area->pieces, void *, p, tor_free_(p)); + smartlist_clear(area->pieces); +} +int +memarea_owns_ptr(const memarea_t *area, const void *ptr) +{ + SMARTLIST_FOREACH(area->pieces, const void *, p, if (ptr == p) return 1;); + return 0; +} + +void * +memarea_alloc(memarea_t *area, size_t sz) +{ + void *result = tor_malloc(sz); + smartlist_add(area->pieces, result); + return result; +} + +void * +memarea_alloc_zero(memarea_t *area, size_t sz) +{ + void *result = tor_malloc_zero(sz); + smartlist_add(area->pieces, result); + return result; +} +void * +memarea_memdup(memarea_t *area, const void *s, size_t n) +{ + void *r = memarea_alloc(area, n); + memcpy(r, s, n); + return r; +} +char * +memarea_strdup(memarea_t *area, const char *s) +{ + size_t n = strlen(s); + char *r = memarea_alloc(area, n+1); + memcpy(r, s, n); + r[n] = 0; + return r; +} +char * +memarea_strndup(memarea_t *area, const char *s, size_t n) +{ + size_t ln = strnlen(s, n); + char *r = memarea_alloc(area, ln+1); + memcpy(r, s, ln); + r[ln] = 0; + return r; +} +void +memarea_get_stats(memarea_t *area, + size_t *allocated_out, size_t *used_out) +{ + (void)area; + *allocated_out = *used_out = 128; +} +void +memarea_assert_ok(memarea_t *area) +{ + (void)area; +} + +#endif + diff --git a/src/common/memarea.h b/src/common/memarea.h index 85bca51ad3..85012c1c34 100644 --- a/src/common/memarea.h +++ b/src/common/memarea.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2016, The Tor Project, Inc. */ +/* Copyright (c) 2008-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /* Tor dependencies */ diff --git a/src/common/procmon.c b/src/common/procmon.c index c485c760c7..d49e7f18f5 100644 --- a/src/common/procmon.c +++ b/src/common/procmon.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016, The Tor Project, Inc. */ +/* Copyright (c) 2011-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/procmon.h b/src/common/procmon.h index 49ead24092..b07cff2c4a 100644 --- a/src/common/procmon.h +++ b/src/common/procmon.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016, The Tor Project, Inc. */ +/* Copyright (c) 2011-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/pubsub.c b/src/common/pubsub.c index b3faf40e00..336e8a6e7f 100644 --- a/src/common/pubsub.c +++ b/src/common/pubsub.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/pubsub.h b/src/common/pubsub.h index bbb4f02a42..6f4ce08754 100644 --- a/src/common/pubsub.h +++ b/src/common/pubsub.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/sandbox.c b/src/common/sandbox.c index 2e1519e922..5063717355 100644 --- a/src/common/sandbox.c +++ b/src/common/sandbox.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,8 +19,14 @@ #define _LARGEFILE64_SOURCE #endif -/** Malloc mprotect limit in bytes. */ -#define MALLOC_MP_LIM 1048576 +/** Malloc mprotect limit in bytes. + * + * 28/06/2017: This value was increased from 16 MB to 20 MB after we introduced + * LZMA support in Tor (0.3.1.1-alpha). We limit our LZMA coder to 16 MB, but + * liblzma have a small overhead that we need to compensate for to avoid being + * killed by the sandbox. + */ +#define MALLOC_MP_LIM (20*1024*1024) #include <stdio.h> #include <string.h> diff --git a/src/common/sandbox.h b/src/common/sandbox.h index c5963e3119..a6b83153af 100644 --- a/src/common/sandbox.h +++ b/src/common/sandbox.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/storagedir.c b/src/common/storagedir.c new file mode 100644 index 0000000000..c471ea911f --- /dev/null +++ b/src/common/storagedir.c @@ -0,0 +1,586 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "container.h" +#include "compat.h" +#include "confline.h" +#include "memarea.h" +#include "sandbox.h" +#include "storagedir.h" +#include "torlog.h" +#include "util.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#define FNAME_MIN_NUM 1000 + +/** A storage_dir_t represents a directory full of similar cached + * files. Filenames are decimal integers. Files can be cleaned as needed + * to limit total disk usage. */ +struct storage_dir_t { + /** Directory holding the files for this storagedir. */ + char *directory; + /** Either NULL, or a directory listing of the directory (as a smartlist + * of strings */ + smartlist_t *contents; + /** The largest number of non-temporary files we'll place in the + * directory. */ + int max_files; + /** If true, then 'usage' has been computed. */ + int usage_known; + /** The total number of bytes used in this directory */ + uint64_t usage; +}; + +/** Create or open a new storage directory at <b>dirname</b>, with + * capacity for up to <b>max_files</b> files. + */ +storage_dir_t * +storage_dir_new(const char *dirname, int max_files) +{ + if (check_private_dir(dirname, CPD_CREATE, NULL) < 0) + return NULL; + + storage_dir_t *d = tor_malloc_zero(sizeof(storage_dir_t)); + d->directory = tor_strdup(dirname); + d->max_files = max_files; + return d; +} + +/** + * Drop all in-RAM storage for <b>d</b>. Does not delete any files. + */ +void +storage_dir_free(storage_dir_t *d) +{ + if (d == NULL) + return; + tor_free(d->directory); + if (d->contents) { + SMARTLIST_FOREACH(d->contents, char *, cp, tor_free(cp)); + smartlist_free(d->contents); + } + tor_free(d); +} + +/** + * Tell the sandbox (if any) configured by <b>cfg</b> to allow the + * operations that <b>d</b> will need. + * + * The presence of this function is why we need an upper limit on the + * number of files in a storage_dir_t: we need to approve file operations + * one by one. + */ +int +storage_dir_register_with_sandbox(storage_dir_t *d, sandbox_cfg_t **cfg) +{ + int problems = 0; + int idx; + for (idx = FNAME_MIN_NUM; idx < FNAME_MIN_NUM + d->max_files; ++idx) { + char *path = NULL, *tmppath = NULL; + tor_asprintf(&path, "%s/%d", d->directory, idx); + tor_asprintf(&tmppath, "%s/%d.tmp", d->directory, idx); + + problems += sandbox_cfg_allow_open_filename(cfg, tor_strdup(path)); + problems += sandbox_cfg_allow_open_filename(cfg, tor_strdup(tmppath)); + problems += sandbox_cfg_allow_stat_filename(cfg, tor_strdup(path)); + problems += sandbox_cfg_allow_stat_filename(cfg, tor_strdup(tmppath)); + problems += sandbox_cfg_allow_rename(cfg, + tor_strdup(tmppath), tor_strdup(path)); + + tor_free(path); + tor_free(tmppath); + } + + return problems ? -1 : 0; +} + +/** + * Remove all files in <b>d</b> whose names end with ".tmp". + * + * Requires that the contents field of <b>d</b> is set. + */ +static void +storage_dir_clean_tmpfiles(storage_dir_t *d) +{ + if (!d->contents) + return; + SMARTLIST_FOREACH_BEGIN(d->contents, char *, fname) { + if (strcmpend(fname, ".tmp")) + continue; + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + if (unlink(sandbox_intern_string(path))) { + log_warn(LD_FS, "Unable to unlink %s while cleaning " + "temporary files: %s", escaped(path), strerror(errno)); + tor_free(path); + continue; + } + tor_free(path); + SMARTLIST_DEL_CURRENT(d->contents, fname); + tor_free(fname); + } SMARTLIST_FOREACH_END(fname); + + d->usage_known = 0; +} + +/** + * Re-scan the directory <b>d</b> to learn its contents. + */ +static int +storage_dir_rescan(storage_dir_t *d) +{ + if (d->contents) { + SMARTLIST_FOREACH(d->contents, char *, cp, tor_free(cp)); + smartlist_free(d->contents); + } + d->usage = 0; + d->usage_known = 0; + if (NULL == (d->contents = tor_listdir(d->directory))) { + return -1; + } + storage_dir_clean_tmpfiles(d); + return 0; +} + +/** + * Return a smartlist containing the filenames within <b>d</b>. + */ +const smartlist_t * +storage_dir_list(storage_dir_t *d) +{ + if (! d->contents) + storage_dir_rescan(d); + return d->contents; +} + +/** + * Return the total number of bytes used for storage in <b>d</b>. + */ +uint64_t +storage_dir_get_usage(storage_dir_t *d) +{ + if (d->usage_known) + return d->usage; + + uint64_t total = 0; + SMARTLIST_FOREACH_BEGIN(storage_dir_list(d), const char *, cp) { + char *path = NULL; + struct stat st; + tor_asprintf(&path, "%s/%s", d->directory, cp); + if (stat(sandbox_intern_string(path), &st) == 0) { + total += st.st_size; + } + tor_free(path); + } SMARTLIST_FOREACH_END(cp); + + d->usage = total; + d->usage_known = 1; + return d->usage; +} + +/** 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; +} + +/** Read a file within <b>d</b> into a newly allocated buffer. Set + * *<b>sz_out</b> to its size. */ +uint8_t * +storage_dir_read(storage_dir_t *d, const char *fname, int bin, size_t *sz_out) +{ + const int flags = bin ? RFTS_BIN : 0; + + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + struct stat st; + char *contents = read_file_to_str(path, flags, &st); + if (contents && sz_out) { + // it fits in RAM, so we know its size is less than SIZE_MAX +#if UINT64_MAX > SIZE_MAX + tor_assert((uint64_t)st.st_size <= SIZE_MAX); +#endif + *sz_out = (size_t) st.st_size; + } + + tor_free(path); + return (uint8_t *) contents; +} + +/** Helper: Find an unused filename within the directory */ +static char * +find_unused_fname(storage_dir_t *d) +{ + if (!d->contents) { + if (storage_dir_rescan(d) < 0) + return NULL; + } + + char buf[16]; + int i; + /* Yuck; this is quadratic. Fortunately, that shouldn't matter much, + * since disk writes are more expensive by a lot. */ + for (i = FNAME_MIN_NUM; i < FNAME_MIN_NUM + d->max_files; ++i) { + tor_snprintf(buf, sizeof(buf), "%d", i); + if (!smartlist_contains_string(d->contents, buf)) { + return tor_strdup(buf); + } + } + return NULL; +} + +/** Helper: As storage_dir_save_bytes_to_file, but store a smartlist of + * sized_chunk_t rather than a single byte array. */ +static int +storage_dir_save_chunks_to_file(storage_dir_t *d, + const smartlist_t *chunks, + int binary, + char **fname_out) +{ + uint64_t total_length = 0; + char *fname = find_unused_fname(d); + if (!fname) + return -1; + + SMARTLIST_FOREACH(chunks, const sized_chunk_t *, ch, + total_length += ch->len); + + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + + int r = write_chunks_to_file(path, chunks, binary, 0); + if (r == 0) { + if (d->usage_known) + d->usage += total_length; + if (fname_out) { + *fname_out = tor_strdup(fname); + } + if (d->contents) + smartlist_add(d->contents, tor_strdup(fname)); + } + tor_free(fname); + tor_free(path); + return r; +} + +/** Try to write the <b>length</b> bytes at <b>data</b> into a new file + * in <b>d</b>. On success, return 0 and set *<b>fname_out</b> to a + * newly allocated string containing the filename. On failure, return + * -1. */ +int +storage_dir_save_bytes_to_file(storage_dir_t *d, + const uint8_t *data, + size_t length, + int binary, + char **fname_out) +{ + smartlist_t *chunks = smartlist_new(); + sized_chunk_t chunk = { (const char *)data, length }; + smartlist_add(chunks, &chunk); + int r = storage_dir_save_chunks_to_file(d, chunks, binary, fname_out); + smartlist_free(chunks); + return r; +} + +/** + * As storage_dir_save_bytes_to_file, but saves a NUL-terminated string + * <b>str</b>. + */ +int +storage_dir_save_string_to_file(storage_dir_t *d, + const char *str, + int binary, + char **fname_out) +{ + return storage_dir_save_bytes_to_file(d, + (const uint8_t*)str, strlen(str), binary, fname_out); +} + +/** + * As storage_dir_save_bytes_to_file, but associates the data with the + * key-value pairs in <b>labels</b>. Files stored in this format can be + * recovered with storage_dir_map_labeled() or storage_dir_read_labeled(). + */ +int +storage_dir_save_labeled_to_file(storage_dir_t *d, + const config_line_t *labels, + const uint8_t *data, + size_t length, + char **fname_out) +{ + /* + * The storage format is to prefix the data with the key-value pairs in + * <b>labels</b>, and a single NUL separator. But code outside this module + * MUST NOT rely on that format. + */ + + smartlist_t *chunks = smartlist_new(); + memarea_t *area = memarea_new(); + const config_line_t *line; + for (line = labels; line; line = line->next) { + sized_chunk_t *sz = memarea_alloc(area, sizeof(sized_chunk_t)); + sz->len = strlen(line->key) + 1 + strlen(line->value) + 1; + const size_t allocated = sz->len + 1; + char *bytes = memarea_alloc(area, allocated); + tor_snprintf(bytes, allocated, "%s %s\n", line->key, line->value); + sz->bytes = bytes; + smartlist_add(chunks, sz); + } + + sized_chunk_t *nul = memarea_alloc(area, sizeof(sized_chunk_t)); + nul->len = 1; + nul->bytes = "\0"; + smartlist_add(chunks, nul); + + sized_chunk_t *datachunk = memarea_alloc(area, sizeof(sized_chunk_t)); + datachunk->bytes = (const char *)data; + datachunk->len = length; + smartlist_add(chunks, datachunk); + + int r = storage_dir_save_chunks_to_file(d, chunks, 1, fname_out); + smartlist_free(chunks); + memarea_drop_all(area); + return r; +} + +/** + * Map a file that was created with storage_dir_save_labeled_to_file(). On + * failure, return NULL. On success, write a set of newly allocated labels + * into *<b>labels_out</b>, a pointer to the data into *<b>data_out</b>, and + * 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, + const char *fname, + config_line_t **labels_out, + const uint8_t **data_out, + size_t *sz_out) +{ + tor_mmap_t *m = storage_dir_map(dir, fname); + int errval; + if (! m) { + errval = errno; + goto err; + } + const char *nulp = memchr(m->data, '\0', m->size); + if (! nulp) { + errval = EINVAL; + goto err; + } + 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); + *sz_out = m->size - offset; + + return m; + err: + tor_munmap_file(m); + errno = errval; + return NULL; +} + +/** As storage_dir_map_labeled, but return a new byte array containing the + * data. */ +uint8_t * +storage_dir_read_labeled(storage_dir_t *dir, + const char *fname, + config_line_t **labels_out, + size_t *sz_out) +{ + const uint8_t *data = NULL; + tor_mmap_t *m = storage_dir_map_labeled(dir, fname, labels_out, + &data, sz_out); + if (m == NULL) + return NULL; + uint8_t *result = tor_memdup(data, *sz_out); + tor_munmap_file(m); + return result; +} + +/* Reduce the cached usage amount in <b>d</b> by <b>removed_file_size</b>. + * This function is a no-op if <b>d->usage_known</b> is 0. */ +static void +storage_dir_reduce_usage(storage_dir_t *d, uint64_t removed_file_size) +{ + if (d->usage_known) { + if (! BUG(d->usage < removed_file_size)) { + /* This bug can also be triggered if an external process resized a file + * between the call to storage_dir_get_usage() that last checked + * actual usage (rather than relaying on cached usage), and the call to + * this function. */ + d->usage -= removed_file_size; + } else { + /* If we underflowed the cached directory size, re-check the sizes of all + * the files in the directory. This makes storage_dir_shrink() quadratic, + * but only if a process is continually changing file sizes in the + * storage directory (in which case, we have bigger issues). + * + * We can't just reset usage_known, because storage_dir_shrink() relies + * on knowing the usage. */ + storage_dir_rescan(d); + (void)storage_dir_get_usage(d); + } + } +} + +/** + * Remove the file called <b>fname</b> from <b>d</b>. + */ +void +storage_dir_remove_file(storage_dir_t *d, + const char *fname) +{ + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + const char *ipath = sandbox_intern_string(path); + + uint64_t size = 0; + if (d->usage_known) { + struct stat st; + if (stat(ipath, &st) == 0) { + size = st.st_size; + } + } + if (unlink(ipath) == 0) { + storage_dir_reduce_usage(d, size); + } else { + log_warn(LD_FS, "Unable to unlink %s while removing file: %s", + escaped(path), strerror(errno)); + tor_free(path); + return; + } + if (d->contents) { + smartlist_string_remove(d->contents, fname); + } + + tor_free(path); +} + +/** Helper type: used to sort the members of storage directory by mtime. */ +typedef struct shrinking_dir_entry_t { + time_t mtime; + uint64_t size; + char *path; +} shrinking_dir_entry_t; + +/** Helper: use with qsort to sort shrinking_dir_entry_t structs. */ +static int +shrinking_dir_entry_compare(const void *a_, const void *b_) +{ + const shrinking_dir_entry_t *a = a_; + const shrinking_dir_entry_t *b = b_; + + if (a->mtime < b->mtime) + return -1; + else if (a->mtime > b->mtime) + return 1; + else + return 0; +} + +/** + * Try to free space by removing the oldest files in <b>d</b>. Delete + * until no more than <b>target_size</b> bytes are left, and at least + * <b>min_to_remove</b> files have been removed... or until there is + * nothing left to remove. + * + * Return 0 on success; -1 on failure. + */ +int +storage_dir_shrink(storage_dir_t *d, + uint64_t target_size, + int min_to_remove) +{ + if (d->usage_known && d->usage <= target_size && !min_to_remove) { + /* Already small enough. */ + return 0; + } + + if (storage_dir_rescan(d) < 0) + return -1; + + const uint64_t orig_usage = storage_dir_get_usage(d); + if (orig_usage <= target_size && !min_to_remove) { + /* Okay, small enough after rescan! */ + return 0; + } + + const int n = smartlist_len(d->contents); + shrinking_dir_entry_t *ents = tor_calloc(n, sizeof(shrinking_dir_entry_t)); + SMARTLIST_FOREACH_BEGIN(d->contents, const char *, fname) { + shrinking_dir_entry_t *ent = &ents[fname_sl_idx]; + struct stat st; + tor_asprintf(&ent->path, "%s/%s", d->directory, fname); + if (stat(sandbox_intern_string(ent->path), &st) == 0) { + ent->mtime = st.st_mtime; + ent->size = st.st_size; + } + } SMARTLIST_FOREACH_END(fname); + + qsort(ents, n, sizeof(shrinking_dir_entry_t), shrinking_dir_entry_compare); + + int idx = 0; + while ((d->usage > target_size || min_to_remove > 0) && idx < n) { + if (unlink(sandbox_intern_string(ents[idx].path)) == 0) { + storage_dir_reduce_usage(d, ents[idx].size); + --min_to_remove; + } + ++idx; + } + + for (idx = 0; idx < n; ++idx) { + tor_free(ents[idx].path); + } + tor_free(ents); + + storage_dir_rescan(d); + + return 0; +} + +/** Remove all files in <b>d</b>. */ +int +storage_dir_remove_all(storage_dir_t *d) +{ + return storage_dir_shrink(d, 0, d->max_files); +} + +/** + * Return the largest number of non-temporary files we're willing to + * store in <b>d</b>. + */ +int +storage_dir_get_max_files(storage_dir_t *d) +{ + return d->max_files; +} + diff --git a/src/common/storagedir.h b/src/common/storagedir.h new file mode 100644 index 0000000000..db25057e65 --- /dev/null +++ b/src/common/storagedir.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_STORAGEDIR_H +#define TOR_STORAGEDIR_H + +typedef struct storage_dir_t storage_dir_t; +struct config_line_t; +struct sandbox_cfg_elem; + +storage_dir_t * storage_dir_new(const char *dirname, int n_files); +void storage_dir_free(storage_dir_t *d); +int storage_dir_register_with_sandbox(storage_dir_t *d, + struct sandbox_cfg_elem **cfg); +const smartlist_t *storage_dir_list(storage_dir_t *d); +uint64_t storage_dir_get_usage(storage_dir_t *d); +tor_mmap_t *storage_dir_map(storage_dir_t *d, const char *fname); +uint8_t *storage_dir_read(storage_dir_t *d, const char *fname, int bin, + size_t *sz_out); +int storage_dir_save_bytes_to_file(storage_dir_t *d, + const uint8_t *data, + size_t length, + int binary, + char **fname_out); +int storage_dir_save_string_to_file(storage_dir_t *d, + const char *data, + int binary, + char **fname_out); +int storage_dir_save_labeled_to_file(storage_dir_t *d, + const struct config_line_t *labels, + const uint8_t *data, + size_t length, + char **fname_out); +tor_mmap_t *storage_dir_map_labeled(storage_dir_t *dir, + const char *fname, + struct config_line_t **labels_out, + const uint8_t **data_out, + size_t *size_out); +uint8_t *storage_dir_read_labeled(storage_dir_t *d, const char *fname, + struct config_line_t **labels_out, + size_t *sz_out); +void storage_dir_remove_file(storage_dir_t *d, + const char *fname); +int storage_dir_shrink(storage_dir_t *d, + uint64_t target_size, + int min_to_remove); +int storage_dir_remove_all(storage_dir_t *d); +int storage_dir_get_max_files(storage_dir_t *d); + +#endif + diff --git a/src/common/testsupport.h b/src/common/testsupport.h index 9ad2ba77e0..9fc96199b4 100644 --- a/src/common/testsupport.h +++ b/src/common/testsupport.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_TESTSUPPORT_H diff --git a/src/common/timers.c b/src/common/timers.c index e1ad47b15b..c43c49c083 100644 --- a/src/common/timers.c +++ b/src/common/timers.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -29,6 +29,8 @@ #include "orconfig.h" +#define TOR_TIMERS_PRIVATE + #include "compat.h" #include "compat_libevent.h" #include "timers.h" @@ -148,6 +150,21 @@ libevent_timer_reschedule(void) event_add(global_timer_event, &d); } +/** Run the callback of every timer that has expired, based on the current + * output of monotime_get(). */ +STATIC void +timers_run_pending(void) +{ + monotime_t now; + monotime_get(&now); + timer_advance_to_cur_time(&now); + + tor_timer_t *t; + while ((t = timeouts_get(global_timeouts))) { + t->callback.cb(t, t->callback.arg, &now); + } +} + /** * Invoked when the libevent timer has expired: see which tor_timer_t events * have fired, activate their callbacks, and reschedule the libevent timer. @@ -159,14 +176,7 @@ libevent_timer_callback(evutil_socket_t fd, short what, void *arg) (void)what; (void)arg; - monotime_t now; - monotime_get(&now); - timer_advance_to_cur_time(&now); - - tor_timer_t *t; - while ((t = timeouts_get(global_timeouts))) { - t->callback.cb(t, t->callback.arg, &now); - } + timers_run_pending(); libevent_timer_reschedule(); } diff --git a/src/common/timers.h b/src/common/timers.h index c5246a3335..d9602cd2ae 100644 --- a/src/common/timers.h +++ b/src/common/timers.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_TIMERS_H @@ -22,5 +22,9 @@ void timer_free(tor_timer_t *t); void timers_initialize(void); void timers_shutdown(void); +#ifdef TOR_TIMERS_PRIVATE +STATIC void timers_run_pending(void); +#endif + #endif diff --git a/src/common/torgzip.c b/src/common/torgzip.c deleted file mode 100644 index 03786cc597..0000000000 --- a/src/common/torgzip.c +++ /dev/null @@ -1,586 +0,0 @@ -/* Copyright (c) 2004, Roger Dingledine. - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file torgzip.c - * \brief A simple in-memory gzip implementation. - **/ - -#include "orconfig.h" - -#include <stdlib.h> -#include <stdio.h> -#include <assert.h> -#include <string.h> -#include "torint.h" - -#ifdef HAVE_NETINET_IN_H -#include <netinet/in.h> -#endif - -#include "util.h" -#include "torlog.h" -#include "torgzip.h" - -/* zlib 1.2.4 and 1.2.5 do some "clever" things with macros. Instead of - saying "(defined(FOO) ? FOO : 0)" they like to say "FOO-0", on the theory - that nobody will care if the compile outputs a no-such-identifier warning. - - Sorry, but we like -Werror over here, so I guess we need to define these. - I hope that zlib 1.2.6 doesn't break these too. -*/ -#ifndef _LARGEFILE64_SOURCE -#define _LARGEFILE64_SOURCE 0 -#endif -#ifndef _LFS64_LARGEFILE -#define _LFS64_LARGEFILE 0 -#endif -#ifndef _FILE_OFFSET_BITS -#define _FILE_OFFSET_BITS 0 -#endif -#ifndef off64_t -#define off64_t int64_t -#endif - -#include <zlib.h> - -#if defined ZLIB_VERNUM && ZLIB_VERNUM < 0x1200 -#error "We require zlib version 1.2 or later." -#endif - -static size_t tor_zlib_state_size_precalc(int inflate, - int windowbits, int memlevel); - -/** Total number of bytes allocated for zlib state */ -static size_t total_zlib_allocation = 0; - -/** Return a string representation of the version of the currently running - * version of zlib. */ -const char * -tor_zlib_get_version_str(void) -{ - return zlibVersion(); -} - -/** Return a string representation of the version of the version of zlib -* used at compilation. */ -const char * -tor_zlib_get_header_version_str(void) -{ - return ZLIB_VERSION; -} - -/** Return the 'bits' value to tell zlib to use <b>method</b>.*/ -static inline int -method_bits(compress_method_t method, zlib_compression_level_t level) -{ - /* Bits+16 means "use gzip" in zlib >= 1.2 */ - const int flag = method == GZIP_METHOD ? 16 : 0; - switch (level) { - default: - case HIGH_COMPRESSION: return flag + 15; - case MEDIUM_COMPRESSION: return flag + 13; - case LOW_COMPRESSION: return flag + 11; - } -} - -static inline int -get_memlevel(zlib_compression_level_t level) -{ - switch (level) { - default: - case HIGH_COMPRESSION: return 8; - case MEDIUM_COMPRESSION: return 7; - case LOW_COMPRESSION: return 6; - } -} - -/** @{ */ -/* These macros define the maximum allowable compression factor. Anything of - * size greater than CHECK_FOR_COMPRESSION_BOMB_AFTER is not allowed to - * have an uncompression factor (uncompressed size:compressed size ratio) of - * any greater than MAX_UNCOMPRESSION_FACTOR. - * - * Picking a value for MAX_UNCOMPRESSION_FACTOR is a trade-off: we want it to - * be small to limit the attack multiplier, but we also want it to be large - * enough so that no legitimate document --even ones we might invent in the - * future -- ever compresses by a factor of greater than - * MAX_UNCOMPRESSION_FACTOR. Within those parameters, there's a reasonably - * large range of possible values. IMO, anything over 8 is probably safe; IMO - * anything under 50 is probably sufficient. - */ -#define MAX_UNCOMPRESSION_FACTOR 25 -#define CHECK_FOR_COMPRESSION_BOMB_AFTER (1024*64) -/** @} */ - -/** Return true if uncompressing an input of size <b>in_size</b> to an input - * of size at least <b>size_out</b> looks like a compression bomb. */ -static int -is_compression_bomb(size_t size_in, size_t size_out) -{ - if (size_in == 0 || size_out < CHECK_FOR_COMPRESSION_BOMB_AFTER) - return 0; - - return (size_out / size_in > MAX_UNCOMPRESSION_FACTOR); -} - -/** Given <b>in_len</b> bytes at <b>in</b>, compress them into a newly - * allocated buffer, using the method described in <b>method</b>. Store the - * compressed string in *<b>out</b>, and its length in *<b>out_len</b>. - * Return 0 on success, -1 on failure. - */ -int -tor_gzip_compress(char **out, size_t *out_len, - const char *in, size_t in_len, - compress_method_t method) -{ - struct z_stream_s *stream = NULL; - size_t out_size, old_size; - off_t offset; - - tor_assert(out); - tor_assert(out_len); - tor_assert(in); - tor_assert(in_len < UINT_MAX); - - *out = NULL; - - stream = tor_malloc_zero(sizeof(struct z_stream_s)); - stream->zalloc = Z_NULL; - stream->zfree = Z_NULL; - stream->opaque = NULL; - stream->next_in = (unsigned char*) in; - stream->avail_in = (unsigned int)in_len; - - if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED, - method_bits(method, HIGH_COMPRESSION), - get_memlevel(HIGH_COMPRESSION), - Z_DEFAULT_STRATEGY) != Z_OK) { - //LCOV_EXCL_START -- we can only provoke failure by giving junk arguments. - log_warn(LD_GENERAL, "Error from deflateInit2: %s", - stream->msg?stream->msg:"<no message>"); - goto err; - //LCOV_EXCL_STOP - } - - /* Guess 50% compression. */ - out_size = in_len / 2; - if (out_size < 1024) out_size = 1024; - *out = tor_malloc(out_size); - stream->next_out = (unsigned char*)*out; - stream->avail_out = (unsigned int)out_size; - - while (1) { - switch (deflate(stream, Z_FINISH)) - { - case Z_STREAM_END: - goto done; - case Z_OK: - /* In case zlib doesn't work as I think .... */ - if (stream->avail_out >= stream->avail_in+16) - break; - /* Falls through. */ - case Z_BUF_ERROR: - offset = stream->next_out - ((unsigned char*)*out); - old_size = out_size; - out_size *= 2; - if (out_size < old_size) { - log_warn(LD_GENERAL, "Size overflow in compression."); - goto err; - } - *out = tor_realloc(*out, out_size); - stream->next_out = (unsigned char*)(*out + offset); - if (out_size - offset > UINT_MAX) { - log_warn(LD_BUG, "Ran over unsigned int limit of zlib while " - "uncompressing."); - goto err; - } - stream->avail_out = (unsigned int)(out_size - offset); - break; - default: - log_warn(LD_GENERAL, "Gzip compression didn't finish: %s", - stream->msg ? stream->msg : "<no message>"); - goto err; - } - } - done: - *out_len = stream->total_out; -#if defined(OpenBSD) - /* "Hey Rocky! Watch me change an unsigned field to a signed field in a - * third-party API!" - * "Oh, that trick will just make people do unsafe casts to the unsigned - * type in their cross-platform code!" - * "Don't be foolish. I'm _sure_ they'll have the good sense to make sure - * the newly unsigned field isn't negative." */ - tor_assert(stream->total_out >= 0); -#endif - if (deflateEnd(stream)!=Z_OK) { - // LCOV_EXCL_START -- unreachable if we handled the zlib structure right - tor_assert_nonfatal_unreached(); - log_warn(LD_BUG, "Error freeing gzip structures"); - goto err; - // LCOV_EXCL_STOP - } - tor_free(stream); - - if (is_compression_bomb(*out_len, in_len)) { - log_warn(LD_BUG, "We compressed something and got an insanely high " - "compression factor; other Tors would think this was a zlib bomb."); - goto err; - } - - return 0; - err: - if (stream) { - deflateEnd(stream); - tor_free(stream); - } - tor_free(*out); - return -1; -} - -/** Given zero or more zlib-compressed or gzip-compressed strings of - * total length - * <b>in_len</b> bytes at <b>in</b>, uncompress them into a newly allocated - * buffer, using the method described in <b>method</b>. Store the uncompressed - * string in *<b>out</b>, and its length in *<b>out_len</b>. Return 0 on - * success, -1 on failure. - * - * If <b>complete_only</b> is true, we consider a truncated input as a - * failure; otherwise we decompress as much as we can. Warn about truncated - * or corrupt inputs at <b>protocol_warn_level</b>. - */ -int -tor_gzip_uncompress(char **out, size_t *out_len, - const char *in, size_t in_len, - compress_method_t method, - int complete_only, - int protocol_warn_level) -{ - struct z_stream_s *stream = NULL; - size_t out_size, old_size; - off_t offset; - int r; - - tor_assert(out); - tor_assert(out_len); - tor_assert(in); - tor_assert(in_len < UINT_MAX); - - *out = NULL; - - stream = tor_malloc_zero(sizeof(struct z_stream_s)); - stream->zalloc = Z_NULL; - stream->zfree = Z_NULL; - stream->opaque = NULL; - stream->next_in = (unsigned char*) in; - stream->avail_in = (unsigned int)in_len; - - if (inflateInit2(stream, - method_bits(method, HIGH_COMPRESSION)) != Z_OK) { - // LCOV_EXCL_START -- can only hit this if we give bad inputs. - log_warn(LD_GENERAL, "Error from inflateInit2: %s", - stream->msg?stream->msg:"<no message>"); - goto err; - // LCOV_EXCL_STOP - } - - out_size = in_len * 2; /* guess 50% compression. */ - if (out_size < 1024) out_size = 1024; - if (out_size >= SIZE_T_CEILING || out_size > UINT_MAX) - goto err; - - *out = tor_malloc(out_size); - stream->next_out = (unsigned char*)*out; - stream->avail_out = (unsigned int)out_size; - - while (1) { - switch (inflate(stream, complete_only ? Z_FINISH : Z_SYNC_FLUSH)) - { - case Z_STREAM_END: - if (stream->avail_in == 0) - goto done; - /* There may be more compressed data here. */ - if ((r = inflateEnd(stream)) != Z_OK) { - log_warn(LD_BUG, "Error freeing gzip structures"); - goto err; - } - if (inflateInit2(stream, - method_bits(method,HIGH_COMPRESSION)) != Z_OK) { - log_warn(LD_GENERAL, "Error from second inflateInit2: %s", - stream->msg?stream->msg:"<no message>"); - goto err; - } - break; - case Z_OK: - if (!complete_only && stream->avail_in == 0) - goto done; - /* In case zlib doesn't work as I think.... */ - if (stream->avail_out >= stream->avail_in+16) - break; - /* Falls through. */ - case Z_BUF_ERROR: - if (stream->avail_out > 0) { - log_fn(protocol_warn_level, LD_PROTOCOL, - "possible truncated or corrupt zlib data"); - goto err; - } - offset = stream->next_out - (unsigned char*)*out; - old_size = out_size; - out_size *= 2; - if (out_size < old_size) { - log_warn(LD_GENERAL, "Size overflow in uncompression."); - goto err; - } - if (is_compression_bomb(in_len, out_size)) { - log_warn(LD_GENERAL, "Input looks like a possible zlib bomb; " - "not proceeding."); - goto err; - } - if (out_size >= SIZE_T_CEILING) { - log_warn(LD_BUG, "Hit SIZE_T_CEILING limit while uncompressing."); - goto err; - } - *out = tor_realloc(*out, out_size); - stream->next_out = (unsigned char*)(*out + offset); - if (out_size - offset > UINT_MAX) { - log_warn(LD_BUG, "Ran over unsigned int limit of zlib while " - "uncompressing."); - goto err; - } - stream->avail_out = (unsigned int)(out_size - offset); - break; - default: - log_warn(LD_GENERAL, "Gzip decompression returned an error: %s", - stream->msg ? stream->msg : "<no message>"); - goto err; - } - } - done: - *out_len = stream->next_out - (unsigned char*)*out; - r = inflateEnd(stream); - tor_free(stream); - if (r != Z_OK) { - log_warn(LD_BUG, "Error freeing gzip structures"); - goto err; - } - - /* NUL-terminate output. */ - if (out_size == *out_len) - *out = tor_realloc(*out, out_size + 1); - (*out)[*out_len] = '\0'; - - return 0; - err: - if (stream) { - inflateEnd(stream); - tor_free(stream); - } - if (*out) { - tor_free(*out); - } - return -1; -} - -/** Try to tell whether the <b>in_len</b>-byte string in <b>in</b> is likely - * to be compressed or not. If it is, return the likeliest compression method. - * Otherwise, return UNKNOWN_METHOD. - */ -compress_method_t -detect_compression_method(const char *in, size_t in_len) -{ - if (in_len > 2 && fast_memeq(in, "\x1f\x8b", 2)) { - return GZIP_METHOD; - } else if (in_len > 2 && (in[0] & 0x0f) == 8 && - (ntohs(get_uint16(in)) % 31) == 0) { - return ZLIB_METHOD; - } else { - return UNKNOWN_METHOD; - } -} - -/** Internal state for an incremental zlib compression/decompression. The - * body of this struct is not exposed. */ -struct tor_zlib_state_t { - struct z_stream_s stream; /**< The zlib stream */ - int compress; /**< True if we are compressing; false if we are inflating */ - - /** Number of bytes read so far. Used to detect zlib bombs. */ - size_t input_so_far; - /** Number of bytes written so far. Used to detect zlib bombs. */ - size_t output_so_far; - - /** Approximate number of bytes allocated for this object. */ - size_t allocation; -}; - -/** Construct and return a tor_zlib_state_t object using <b>method</b>. If - * <b>compress</b>, it's for compression; otherwise it's for - * decompression. */ -tor_zlib_state_t * -tor_zlib_new(int compress_, compress_method_t method, - zlib_compression_level_t compression_level) -{ - tor_zlib_state_t *out; - int bits, memlevel; - - if (! compress_) { - /* use this setting for decompression, since we might have the - * max number of window bits */ - compression_level = HIGH_COMPRESSION; - } - - out = tor_malloc_zero(sizeof(tor_zlib_state_t)); - out->stream.zalloc = Z_NULL; - out->stream.zfree = Z_NULL; - out->stream.opaque = NULL; - out->compress = compress_; - bits = method_bits(method, compression_level); - memlevel = get_memlevel(compression_level); - if (compress_) { - if (deflateInit2(&out->stream, Z_BEST_COMPRESSION, Z_DEFLATED, - bits, memlevel, - Z_DEFAULT_STRATEGY) != Z_OK) - goto err; // LCOV_EXCL_LINE - } else { - if (inflateInit2(&out->stream, bits) != Z_OK) - goto err; // LCOV_EXCL_LINE - } - out->allocation = tor_zlib_state_size_precalc(!compress_, bits, memlevel); - - total_zlib_allocation += out->allocation; - - return out; - - err: - tor_free(out); - return NULL; -} - -/** Compress/decompress some bytes using <b>state</b>. Read up to - * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes - * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, - * we've reached the end of the input. - * - * Return TOR_ZLIB_DONE if we've finished the entire compression/decompression. - * Return TOR_ZLIB_OK if we're processed everything from the input. - * Return TOR_ZLIB_BUF_FULL if we're out of space on <b>out</b>. - * Return TOR_ZLIB_ERR if the stream is corrupt. - */ -tor_zlib_output_t -tor_zlib_process(tor_zlib_state_t *state, - char **out, size_t *out_len, - const char **in, size_t *in_len, - int finish) -{ - int err; - tor_assert(*in_len <= UINT_MAX); - tor_assert(*out_len <= UINT_MAX); - state->stream.next_in = (unsigned char*) *in; - state->stream.avail_in = (unsigned int)*in_len; - state->stream.next_out = (unsigned char*) *out; - state->stream.avail_out = (unsigned int)*out_len; - - if (state->compress) { - err = deflate(&state->stream, finish ? Z_FINISH : Z_NO_FLUSH); - } else { - err = inflate(&state->stream, finish ? Z_FINISH : Z_SYNC_FLUSH); - } - - state->input_so_far += state->stream.next_in - ((unsigned char*)*in); - state->output_so_far += state->stream.next_out - ((unsigned char*)*out); - - *out = (char*) state->stream.next_out; - *out_len = state->stream.avail_out; - *in = (const char *) state->stream.next_in; - *in_len = state->stream.avail_in; - - if (! state->compress && - is_compression_bomb(state->input_so_far, state->output_so_far)) { - log_warn(LD_DIR, "Possible zlib bomb; abandoning stream."); - return TOR_ZLIB_ERR; - } - - switch (err) - { - case Z_STREAM_END: - return TOR_ZLIB_DONE; - case Z_BUF_ERROR: - if (state->stream.avail_in == 0 && !finish) - return TOR_ZLIB_OK; - return TOR_ZLIB_BUF_FULL; - case Z_OK: - if (state->stream.avail_out == 0 || finish) - return TOR_ZLIB_BUF_FULL; - return TOR_ZLIB_OK; - default: - log_warn(LD_GENERAL, "Gzip returned an error: %s", - state->stream.msg ? state->stream.msg : "<no message>"); - return TOR_ZLIB_ERR; - } -} - -/** Deallocate <b>state</b>. */ -void -tor_zlib_free(tor_zlib_state_t *state) -{ - if (!state) - return; - - total_zlib_allocation -= state->allocation; - - if (state->compress) - deflateEnd(&state->stream); - else - inflateEnd(&state->stream); - - tor_free(state); -} - -/** Return an approximate number of bytes used in RAM to hold a state with - * window bits <b>windowBits</b> and compression level 'memlevel' */ -static size_t -tor_zlib_state_size_precalc(int inflate_, int windowbits, int memlevel) -{ - windowbits &= 15; - -#define A_FEW_KILOBYTES 2048 - - if (inflate_) { - /* From zconf.h: - - "The memory requirements for inflate are (in bytes) 1 << windowBits - that is, 32K for windowBits=15 (default value) plus a few kilobytes - for small objects." - */ - return sizeof(tor_zlib_state_t) + sizeof(struct z_stream_s) + - (1 << 15) + A_FEW_KILOBYTES; - } else { - /* Also from zconf.h: - - "The memory requirements for deflate are (in bytes): - (1 << (windowBits+2)) + (1 << (memLevel+9)) - ... plus a few kilobytes for small objects." - */ - return sizeof(tor_zlib_state_t) + sizeof(struct z_stream_s) + - (1 << (windowbits + 2)) + (1 << (memlevel + 9)) + A_FEW_KILOBYTES; - } -#undef A_FEW_KILOBYTES -} - -/** Return the approximate number of bytes allocated for <b>state</b>. */ -size_t -tor_zlib_state_size(const tor_zlib_state_t *state) -{ - return state->allocation; -} - -/** Return the approximate number of bytes allocated for all zlib states. */ -size_t -tor_zlib_get_total_allocation(void) -{ - return total_zlib_allocation; -} - diff --git a/src/common/torgzip.h b/src/common/torgzip.h deleted file mode 100644 index 00f62dcb45..0000000000 --- a/src/common/torgzip.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright (c) 2003, Roger Dingledine - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file torgzip.h - * \brief Headers for torgzip.h - **/ - -#ifndef TOR_TORGZIP_H -#define TOR_TORGZIP_H - -/** Enumeration of what kind of compression to use. Only ZLIB_METHOD is - * guaranteed to be supported by the compress/uncompress functions here; - * GZIP_METHOD may be supported if we built against zlib version 1.2 or later - * and is_gzip_supported() returns true. */ -typedef enum { - NO_METHOD=0, GZIP_METHOD=1, ZLIB_METHOD=2, UNKNOWN_METHOD=3 -} compress_method_t; - -/** - * Enumeration to define tradeoffs between memory usage and compression level. - * HIGH_COMPRESSION saves the most bandwidth; LOW_COMPRESSION saves the most - * memory. - **/ -typedef enum { - HIGH_COMPRESSION, MEDIUM_COMPRESSION, LOW_COMPRESSION -} zlib_compression_level_t; - -int -tor_gzip_compress(char **out, size_t *out_len, - const char *in, size_t in_len, - compress_method_t method); -int -tor_gzip_uncompress(char **out, size_t *out_len, - const char *in, size_t in_len, - compress_method_t method, - int complete_only, - int protocol_warn_level); - -int is_gzip_supported(void); - -const char * -tor_zlib_get_version_str(void); - -const char * -tor_zlib_get_header_version_str(void); - -compress_method_t detect_compression_method(const char *in, size_t in_len); - -/** Return values from tor_zlib_process; see that function's documentation for - * details. */ -typedef enum { - TOR_ZLIB_OK, TOR_ZLIB_DONE, TOR_ZLIB_BUF_FULL, TOR_ZLIB_ERR -} tor_zlib_output_t; -/** Internal state for an incremental zlib compression/decompression. */ -typedef struct tor_zlib_state_t tor_zlib_state_t; -tor_zlib_state_t *tor_zlib_new(int compress, compress_method_t method, - zlib_compression_level_t level); - -tor_zlib_output_t tor_zlib_process(tor_zlib_state_t *state, - char **out, size_t *out_len, - const char **in, size_t *in_len, - int finish); -void tor_zlib_free(tor_zlib_state_t *state); - -size_t tor_zlib_state_size(const tor_zlib_state_t *state); -size_t tor_zlib_get_total_allocation(void); - -#endif - diff --git a/src/common/torint.h b/src/common/torint.h index 58c30f41a8..ee31459e94 100644 --- a/src/common/torint.h +++ b/src/common/torint.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/torlog.h b/src/common/torlog.h index bc957858d9..6e374b1c11 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -101,8 +101,10 @@ #define LD_SCHED (1u<<22) /** Guard nodes */ #define LD_GUARD (1u<<23) +/** Generation and application of consensus diffs. */ +#define LD_CONSDIFF (1u<<24) /** Number of logging domains in the code. */ -#define N_LOGGING_DOMAINS 24 +#define N_LOGGING_DOMAINS 25 /** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */ diff --git a/src/common/tortls.c b/src/common/tortls.c index e3e8830b3e..1c47cf9882 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,6 +17,7 @@ #include "orconfig.h" #define TORTLS_PRIVATE +#define TORTLS_OPENSSL_PRIVATE #include <assert.h> #ifdef _WIN32 /*wrkard for dtls1.h >= 0.9.8m of "#include <winsock.h>"*/ @@ -459,11 +460,11 @@ tor_x509_name_new(const char *cname) * Return a certificate on success, NULL on failure. */ MOCK_IMPL(STATIC X509 *, - tor_tls_create_certificate,(crypto_pk_t *rsa, - crypto_pk_t *rsa_sign, - const char *cname, - const char *cname_sign, - unsigned int cert_lifetime)) +tor_tls_create_certificate,(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)) { /* OpenSSL generates self-signed certificates with random 64-bit serial * numbers, so let's do that too. */ @@ -661,7 +662,7 @@ tor_x509_cert_free(tor_x509_cert_t *cert) * Steals a reference to x509_cert. */ MOCK_IMPL(STATIC tor_x509_cert_t *, - tor_x509_cert_new,(X509 *x509_cert)) +tor_x509_cert_new,(X509 *x509_cert)) { tor_x509_cert_t *cert; EVP_PKEY *pkey; @@ -675,12 +676,7 @@ MOCK_IMPL(STATIC tor_x509_cert_t *, length = i2d_X509(x509_cert, &buf); cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); if (length <= 0 || buf == NULL) { - /* LCOV_EXCL_START for the same reason as the exclusion above */ - tor_free(cert); - log_err(LD_CRYPTO, "Couldn't get length of encoded x509 certificate"); - X509_free(x509_cert); - return NULL; - /* LCOV_EXCL_STOP */ + goto err; } cert->encoded_len = (size_t) length; cert->encoded = tor_malloc(length); @@ -695,13 +691,25 @@ MOCK_IMPL(STATIC tor_x509_cert_t *, if ((pkey = X509_get_pubkey(x509_cert)) && (rsa = EVP_PKEY_get1_RSA(pkey))) { crypto_pk_t *pk = crypto_new_pk_from_rsa_(rsa); - crypto_pk_get_common_digests(pk, &cert->pkey_digests); + if (crypto_pk_get_common_digests(pk, &cert->pkey_digests) < 0) { + crypto_pk_free(pk); + EVP_PKEY_free(pkey); + goto err; + } + cert->pkey_digests_set = 1; crypto_pk_free(pk); EVP_PKEY_free(pkey); } return cert; + err: + /* LCOV_EXCL_START for the same reason as the exclusion above */ + tor_free(cert); + log_err(LD_CRYPTO, "Couldn't wrap encoded X509 certificate."); + X509_free(x509_cert); + return NULL; + /* LCOV_EXCL_STOP */ } /** Return a new copy of <b>cert</b>. */ @@ -2284,6 +2292,24 @@ check_cert_lifetime_internal(int severity, const X509 *cert, return 0; } +#ifdef TOR_UNIT_TESTS +/* Testing only: return a new x509 cert with the same contents as <b>inp</b>, + but with the expiration time <b>new_expiration_time</b>, signed with + <b>signing_key</b>. */ +STATIC tor_x509_cert_t * +tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key) +{ + X509 *newc = X509_dup(inp->cert); + X509_time_adj(X509_get_notAfter(newc), 0, &new_expiration_time); + EVP_PKEY *pk = crypto_pk_get_evp_pkey_(signing_key, 1); + tor_assert(X509_sign(newc, pk, EVP_sha256())); + EVP_PKEY_free(pk); + return tor_x509_cert_new(newc); +} +#endif + /** Return the number of bytes available for reading from <b>tls</b>. */ int diff --git a/src/common/tortls.h b/src/common/tortls.h index bb7701cc4b..f430aff70b 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_TORTLS_H @@ -63,12 +63,17 @@ typedef enum { } tor_tls_state_t; #define tor_tls_state_bitfield_t ENUM_BF(tor_tls_state_t) +struct x509_st; +struct ssl_st; +struct ssl_ctx_st; +struct ssl_session_st; + /** Holds a SSL_CTX object and related state used to configure TLS * connections. */ typedef struct tor_tls_context_t { int refcnt; - SSL_CTX *ctx; + struct ssl_ctx_st *ctx; tor_x509_cert_t *my_link_cert; tor_x509_cert_t *my_id_cert; tor_x509_cert_t *my_auth_cert; @@ -78,7 +83,7 @@ typedef struct tor_tls_context_t { /** Structure that we use for a single certificate. */ struct tor_x509_cert_t { - X509 *cert; + struct x509_st *cert; uint8_t *encoded; size_t encoded_len; unsigned pkey_digests_set : 1; @@ -92,7 +97,7 @@ struct tor_x509_cert_t { struct tor_tls_t { uint32_t magic; tor_tls_context_t *context; /** A link to the context object for this tls. */ - SSL *ssl; /**< An OpenSSL SSL object. */ + struct ssl_st *ssl; /**< An OpenSSL SSL object. */ int socket; /**< The underlying file descriptor for this TLS connection. */ char *address; /**< An address to log when describing this connection. */ tor_tls_state_bitfield_t state : 3; /**< The current SSL state, @@ -128,35 +133,45 @@ struct tor_tls_t { STATIC int tor_errno_to_tls_error(int e); STATIC int tor_tls_get_error(tor_tls_t *tls, int r, int extra, const char *doing, int severity, int domain); -STATIC tor_tls_t *tor_tls_get_by_ssl(const SSL *ssl); +STATIC tor_tls_t *tor_tls_get_by_ssl(const struct ssl_st *ssl); STATIC void tor_tls_allocate_tor_tls_object_ex_data_index(void); +#ifdef TORTLS_OPENSSL_PRIVATE STATIC int always_accept_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx); -STATIC int tor_tls_classify_client_ciphers(const SSL *ssl, +STATIC int tor_tls_classify_client_ciphers(const struct ssl_st *ssl, STACK_OF(SSL_CIPHER) *peer_ciphers); -STATIC int tor_tls_client_is_using_v2_ciphers(const SSL *ssl); +#endif +STATIC int tor_tls_client_is_using_v2_ciphers(const struct ssl_st *ssl); MOCK_DECL(STATIC void, try_to_extract_certs_from_tls, - (int severity, tor_tls_t *tls, X509 **cert_out, X509 **id_cert_out)); + (int severity, tor_tls_t *tls, struct x509_st **cert_out, + struct x509_st **id_cert_out)); #ifndef HAVE_SSL_SESSION_GET_MASTER_KEY -STATIC size_t SSL_SESSION_get_master_key(SSL_SESSION *s, uint8_t *out, +STATIC size_t SSL_SESSION_get_master_key(struct ssl_session_st *s, + uint8_t *out, size_t len); #endif -STATIC void tor_tls_debug_state_callback(const SSL *ssl, int type, int val); -STATIC void tor_tls_server_info_callback(const SSL *ssl, int type, int val); -STATIC int tor_tls_session_secret_cb(SSL *ssl, void *secret, +STATIC void tor_tls_debug_state_callback(const struct ssl_st *ssl, + int type, int val); +STATIC void tor_tls_server_info_callback(const struct ssl_st *ssl, + int type, int val); +#ifdef TORTLS_OPENSSL_PRIVATE +STATIC int tor_tls_session_secret_cb(struct ssl_st *ssl, void *secret, int *secret_len, STACK_OF(SSL_CIPHER) *peer_ciphers, CONST_IF_OPENSSL_1_1_API SSL_CIPHER **cipher, void *arg); STATIC int find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher); -MOCK_DECL(STATIC X509*, tor_tls_create_certificate,(crypto_pk_t *rsa, +#endif +MOCK_DECL(STATIC struct x509_st *, tor_tls_create_certificate, + (crypto_pk_t *rsa, crypto_pk_t *rsa_sign, const char *cname, const char *cname_sign, unsigned int cert_lifetime)); STATIC tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, unsigned flags, int is_client); -MOCK_DECL(STATIC tor_x509_cert_t *, tor_x509_cert_new,(X509 *x509_cert)); +MOCK_DECL(STATIC tor_x509_cert_t *, tor_x509_cert_new, + (struct x509_st *x509_cert)); STATIC int tor_tls_context_init_one(tor_tls_context_t **ppcontext, crypto_pk_t *identity, unsigned int key_lifetime, @@ -172,6 +187,11 @@ extern tor_tls_context_t *client_tls_context; extern uint16_t v2_cipher_list[]; extern uint64_t total_bytes_written_over_tls; extern uint64_t total_bytes_written_by_tls; + +STATIC tor_x509_cert_t *tor_x509_cert_replace_expiration( + const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key); #endif #endif /* endif TORTLS_PRIVATE */ diff --git a/src/common/util.c b/src/common/util.c index 4942ee3a9e..5b47028097 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -1953,7 +1953,7 @@ parse_http_time(const char *date, struct tm *tm) /** Given an <b>interval</b> in seconds, try to write it to the * <b>out_len</b>-byte buffer in <b>out</b> in a human-readable form. - * Return 0 on success, -1 on failure. + * Returns a non-negative integer on success, -1 on failure. */ int format_time_interval(char *out, size_t out_len, long interval) @@ -2118,7 +2118,7 @@ read_all(tor_socket_t fd, char *buf, size_t count, int isSocket) return -1; } - while (numread != count) { + while (numread < count) { if (isSocket) result = tor_socket_recv(fd, buf+numread, count-numread, 0); else @@ -3045,135 +3045,39 @@ unescape_string(const char *s, char **result, size_t *size_out) } } -/** Given a string containing part of a configuration file or similar format, - * advance past comments and whitespace and try to parse a single line. If we - * parse a line successfully, set *<b>key_out</b> to a new string holding the - * key portion and *<b>value_out</b> to a new string holding the value portion - * of the line, and return a pointer to the start of the next line. If we run - * out of data, return a pointer to the end of the string. If we encounter an - * error, return NULL and set *<b>err_out</b> (if provided) to an error - * message. - */ -const char * -parse_config_line_from_str_verbose(const char *line, char **key_out, - char **value_out, - const char **err_out) +/** Removes enclosing quotes from <b>path</b> and unescapes quotes between the + * enclosing quotes. Backslashes are not unescaped. Return the unquoted + * <b>path</b> on sucess or 0 if <b>path</b> is not quoted correctly. */ +char * +get_unquoted_path(const char *path) { - /* - See torrc_format.txt for a description of the (silly) format this parses. - */ - const char *key, *val, *cp; - int continuation = 0; - - tor_assert(key_out); - tor_assert(value_out); + size_t len = strlen(path); - *key_out = *value_out = NULL; - key = val = NULL; - /* Skip until the first keyword. */ - while (1) { - while (TOR_ISSPACE(*line)) - ++line; - if (*line == '#') { - while (*line && *line != '\n') - ++line; - } else { - break; - } + if (len == 0) { + return tor_strdup(""); } - if (!*line) { /* End of string? */ - *key_out = *value_out = NULL; - return line; + int has_start_quote = (path[0] == '\"'); + int has_end_quote = (len > 0 && path[len-1] == '\"'); + if (has_start_quote != has_end_quote || (len == 1 && has_start_quote)) { + return NULL; } - /* Skip until the next space or \ followed by newline. */ - key = line; - while (*line && !TOR_ISSPACE(*line) && *line != '#' && - ! (line[0] == '\\' && line[1] == '\n')) - ++line; - *key_out = tor_strndup(key, line-key); - - /* Skip until the value. */ - while (*line == ' ' || *line == '\t') - ++line; - - val = line; - - /* Find the end of the line. */ - if (*line == '\"') { // XXX No continuation handling is done here - if (!(line = unescape_string(line, value_out, NULL))) { - if (err_out) - *err_out = "Invalid escape sequence in quoted string"; - return NULL; - } - while (*line == ' ' || *line == '\t') - ++line; - if (*line == '\r' && *(++line) == '\n') - ++line; - if (*line && *line != '#' && *line != '\n') { - if (err_out) - *err_out = "Excess data after quoted string"; + char *unquoted_path = tor_malloc(len - has_start_quote - has_end_quote + 1); + char *s = unquoted_path; + size_t i; + for (i = has_start_quote; i < len - has_end_quote; i++) { + if (path[i] == '\"' && (i > 0 && path[i-1] == '\\')) { + *(s-1) = path[i]; + } else if (path[i] != '\"') { + *s++ = path[i]; + } else { /* unescaped quote */ + tor_free(unquoted_path); return NULL; } - } else { - /* Look for the end of the line. */ - while (*line && *line != '\n' && (*line != '#' || continuation)) { - if (*line == '\\' && line[1] == '\n') { - continuation = 1; - line += 2; - } else if (*line == '#') { - do { - ++line; - } while (*line && *line != '\n'); - if (*line == '\n') - ++line; - } else { - ++line; - } - } - - if (*line == '\n') { - cp = line++; - } else { - cp = line; - } - /* Now back cp up to be the last nonspace character */ - while (cp>val && TOR_ISSPACE(*(cp-1))) - --cp; - - tor_assert(cp >= val); - - /* Now copy out and decode the value. */ - *value_out = tor_strndup(val, cp-val); - if (continuation) { - char *v_out, *v_in; - v_out = v_in = *value_out; - while (*v_in) { - if (*v_in == '#') { - do { - ++v_in; - } while (*v_in && *v_in != '\n'); - if (*v_in == '\n') - ++v_in; - } else if (v_in[0] == '\\' && v_in[1] == '\n') { - v_in += 2; - } else { - *v_out++ = *v_in++; - } - } - *v_out = '\0'; - } - } - - if (*line == '#') { - do { - ++line; - } while (*line && *line != '\n'); } - while (TOR_ISSPACE(*line)) ++line; - - return line; + *s = '\0'; + return unquoted_path; } /** Expand any homedir prefix on <b>filename</b>; return a newly allocated @@ -4175,10 +4079,10 @@ tor_process_get_stdout_pipe(process_handle_t *process_handle) } #else /* DOCDOC tor_process_get_stdout_pipe */ -FILE * +int tor_process_get_stdout_pipe(process_handle_t *process_handle) { - return process_handle->stdout_handle; + return process_handle->stdout_pipe; } #endif @@ -4609,10 +4513,6 @@ tor_spawn_background(const char *const filename, const char **argv, log_warn(LD_GENERAL, "Failed to set stderror/stdout/stdin pipes " "nonblocking in parent process: %s", strerror(errno)); } - /* Open the buffered IO streams */ - process_handle->stdout_handle = fdopen(process_handle->stdout_pipe, "r"); - process_handle->stderr_handle = fdopen(process_handle->stderr_pipe, "r"); - process_handle->stdin_handle = fdopen(process_handle->stdin_pipe, "r"); *process_handle_out = process_handle; return process_handle->status; @@ -4659,14 +4559,9 @@ tor_process_handle_destroy,(process_handle_t *process_handle, if (process_handle->stdin_pipe) CloseHandle(process_handle->stdin_pipe); #else - if (process_handle->stdout_handle) - fclose(process_handle->stdout_handle); - - if (process_handle->stderr_handle) - fclose(process_handle->stderr_handle); - - if (process_handle->stdin_handle) - fclose(process_handle->stdin_handle); + close(process_handle->stdout_pipe); + close(process_handle->stderr_pipe); + close(process_handle->stdin_pipe); clear_waitpid_callback(process_handle->waitpid_cb); #endif @@ -4952,7 +4847,7 @@ tor_read_all_handle(HANDLE h, char *buf, size_t count, if (count > SIZE_T_CEILING || count > SSIZE_MAX) return -1; - while (numread != count) { + while (numread < count) { /* Check if there is anything to read */ retval = PeekNamedPipe(h, NULL, 0, NULL, &byte_count, NULL); if (!retval) { @@ -4998,19 +4893,19 @@ tor_read_all_handle(HANDLE h, char *buf, size_t count, return (ssize_t)numread; } #else -/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes. If +/** Read from a handle <b>fd</b> into <b>buf</b>, up to <b>count</b> bytes. If * <b>process</b> is NULL, the function will return immediately if there is * nothing more to read. Otherwise data will be read until end of file, or * <b>count</b> bytes are read. Returns the number of bytes read, or -1 on * error. Sets <b>eof</b> to true if <b>eof</b> is not NULL and the end of the * file has been reached. */ ssize_t -tor_read_all_handle(FILE *h, char *buf, size_t count, +tor_read_all_handle(int fd, char *buf, size_t count, const process_handle_t *process, int *eof) { size_t numread = 0; - char *retval; + ssize_t result; if (eof) *eof = 0; @@ -5018,34 +4913,28 @@ tor_read_all_handle(FILE *h, char *buf, size_t count, if (count > SIZE_T_CEILING || count > SSIZE_MAX) return -1; - while (numread != count) { - /* Use fgets because that is what we use in log_from_pipe() */ - retval = fgets(buf+numread, (int)(count-numread), h); - if (NULL == retval) { - if (feof(h)) { - log_debug(LD_GENERAL, "fgets() reached end of file"); - if (eof) - *eof = 1; + while (numread < count) { + result = read(fd, buf+numread, count-numread); + + if (result == 0) { + log_debug(LD_GENERAL, "read() reached end of file"); + if (eof) + *eof = 1; + break; + } else if (result < 0 && errno == EAGAIN) { + if (process) + continue; + else break; - } else { - if (EAGAIN == errno) { - if (process) - continue; - else - break; - } else { - log_warn(LD_GENERAL, "fgets() from handle failed: %s", - strerror(errno)); - return -1; - } - } + } else if (result < 0) { + log_warn(LD_GENERAL, "read() failed: %s", strerror(errno)); + return -1; } - tor_assert(retval != NULL); - tor_assert(strlen(retval) + numread <= count); - numread += strlen(retval); + + numread += result; } - log_debug(LD_GENERAL, "fgets() read %d bytes from handle", (int)numread); + log_debug(LD_GENERAL, "read() read %d bytes from handle", (int)numread); return (ssize_t)numread; } #endif @@ -5059,7 +4948,7 @@ tor_read_all_from_process_stdout(const process_handle_t *process_handle, return tor_read_all_handle(process_handle->stdout_pipe, buf, count, process_handle); #else - return tor_read_all_handle(process_handle->stdout_handle, buf, count, + return tor_read_all_handle(process_handle->stdout_pipe, buf, count, process_handle, NULL); #endif } @@ -5073,7 +4962,7 @@ tor_read_all_from_process_stderr(const process_handle_t *process_handle, return tor_read_all_handle(process_handle->stderr_pipe, buf, count, process_handle); #else - return tor_read_all_handle(process_handle->stderr_handle, buf, count, + return tor_read_all_handle(process_handle->stderr_pipe, buf, count, process_handle, NULL); #endif } @@ -5267,11 +5156,10 @@ log_from_handle(HANDLE *pipe, int severity) #else /** Return a smartlist containing lines outputted from - * <b>handle</b>. Return NULL on error, and set + * <b>fd</b>. Return NULL on error, and set * <b>stream_status_out</b> appropriately. */ MOCK_IMPL(smartlist_t *, -tor_get_lines_from_handle, (FILE *handle, - enum stream_status *stream_status_out)) +tor_get_lines_from_handle, (int fd, enum stream_status *stream_status_out)) { enum stream_status stream_status; char stdout_buf[400]; @@ -5280,13 +5168,13 @@ tor_get_lines_from_handle, (FILE *handle, while (1) { memset(stdout_buf, 0, sizeof(stdout_buf)); - stream_status = get_string_from_pipe(handle, + stream_status = get_string_from_pipe(fd, stdout_buf, sizeof(stdout_buf) - 1); if (stream_status != IO_STREAM_OKAY) goto done; if (!lines) lines = smartlist_new(); - smartlist_add_strdup(lines, stdout_buf); + smartlist_split_string(lines, stdout_buf, "\n", 0, 0); } done: @@ -5294,20 +5182,20 @@ tor_get_lines_from_handle, (FILE *handle, return lines; } -/** Read from stream, and send lines to log at the specified log level. +/** Read from fd, and send lines to log at the specified log level. * Returns 1 if stream is closed normally, -1 if there is a error reading, and * 0 otherwise. Handles lines from tor-fw-helper and * tor_spawn_background() specially. */ static int -log_from_pipe(FILE *stream, int severity, const char *executable, +log_from_pipe(int fd, int severity, const char *executable, int *child_status) { char buf[256]; enum stream_status r; for (;;) { - r = get_string_from_pipe(stream, buf, sizeof(buf) - 1); + r = get_string_from_pipe(fd, buf, sizeof(buf) - 1); if (r == IO_STREAM_CLOSED) { return 1; @@ -5332,7 +5220,7 @@ log_from_pipe(FILE *stream, int severity, const char *executable, } #endif -/** Reads from <b>stream</b> and stores input in <b>buf_out</b> making +/** Reads from <b>fd</b> and stores input in <b>buf_out</b> making * sure it's below <b>count</b> bytes. * If the string has a trailing newline, we strip it off. * @@ -5348,52 +5236,28 @@ log_from_pipe(FILE *stream, int severity, const char *executable, * IO_STREAM_OKAY: If everything went okay and we got a string * in <b>buf_out</b>. */ enum stream_status -get_string_from_pipe(FILE *stream, char *buf_out, size_t count) +get_string_from_pipe(int fd, char *buf_out, size_t count) { - char *retval; - size_t len; + ssize_t ret; tor_assert(count <= INT_MAX); - retval = fgets(buf_out, (int)count, stream); - - if (!retval) { - if (feof(stream)) { - /* Program has closed stream (probably it exited) */ - /* TODO: check error */ - return IO_STREAM_CLOSED; - } else { - if (EAGAIN == errno) { - /* Nothing more to read, try again next time */ - return IO_STREAM_EAGAIN; - } else { - /* There was a problem, abandon this child process */ - return IO_STREAM_TERM; - } - } - } else { - len = strlen(buf_out); - if (len == 0) { - /* this probably means we got a NUL at the start of the string. */ - return IO_STREAM_EAGAIN; - } + ret = read(fd, buf_out, count); - if (buf_out[len - 1] == '\n') { - /* Remove the trailing newline */ - buf_out[len - 1] = '\0'; - } else { - /* No newline; check whether we overflowed the buffer */ - if (!feof(stream)) - log_info(LD_GENERAL, - "Line from stream was truncated: %s", buf_out); - /* TODO: What to do with this error? */ - } + if (ret == 0) + return IO_STREAM_CLOSED; + else if (ret < 0 && errno == EAGAIN) + return IO_STREAM_EAGAIN; + else if (ret < 0) + return IO_STREAM_TERM; - return IO_STREAM_OKAY; - } + if (buf_out[ret - 1] == '\n') { + /* Remove the trailing newline */ + buf_out[ret - 1] = '\0'; + } else + buf_out[ret] = '\0'; - /* We should never get here */ - return IO_STREAM_TERM; + return IO_STREAM_OKAY; } /** Parse a <b>line</b> from tor-fw-helper and issue an appropriate @@ -5630,7 +5494,7 @@ tor_check_port_forwarding(const char *filename, #ifdef _WIN32 stderr_status = log_from_handle(child_handle->stderr_pipe, LOG_INFO); #else - stderr_status = log_from_pipe(child_handle->stderr_handle, + stderr_status = log_from_pipe(child_handle->stderr_pipe, LOG_INFO, filename, &retval); #endif if (handle_fw_helper_output(filename, child_handle) < 0) { diff --git a/src/common/util.h b/src/common/util.h index 13fcc5142d..d56abcee2e 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -163,9 +163,9 @@ int64_t clamp_double_to_int64(double number); void simplify_fraction64(uint64_t *numer, uint64_t *denom); /* Compute the CEIL of <b>a</b> divided by <b>b</b>, for nonnegative <b>a</b> - * and positive <b>b</b>. Works on integer types only. Not defined if a+b can - * overflow. */ -#define CEIL_DIV(a,b) (((a)+(b)-1)/(b)) + * and positive <b>b</b>. Works on integer types only. Not defined if a+(b-1) + * can overflow. */ +#define CEIL_DIV(a,b) (((a)+((b)-1))/(b)) /* Return <b>v</b> if it's between <b>min</b> and <b>max</b>. Otherwise * return <b>min</b> if <b>v</b> is smaller than <b>min</b>, or <b>max</b> if @@ -322,7 +322,7 @@ enum stream_status { const char *stream_status_to_string(enum stream_status stream_status); -enum stream_status get_string_from_pipe(FILE *stream, char *buf, size_t count); +enum stream_status get_string_from_pipe(int fd, char *buf, size_t count); MOCK_DECL(int,tor_unlink,(const char *pathname)); @@ -389,9 +389,7 @@ char *read_file_to_str_until_eof(int fd, size_t max_bytes_to_read, size_t *sz_out) ATTR_MALLOC; const char *unescape_string(const char *s, char **result, size_t *size_out); -const char *parse_config_line_from_str_verbose(const char *line, - char **key_out, char **value_out, - const char **err_out); +char *get_unquoted_path(const char *path); char *expand_filename(const char *filename); MOCK_DECL(struct smartlist_t *, tor_listdir, (const char *dirname)); int path_is_relative(const char *filename); @@ -463,9 +461,6 @@ struct process_handle_t { int stdin_pipe; int stdout_pipe; int stderr_pipe; - FILE *stdin_handle; - FILE *stdout_handle; - FILE *stderr_handle; pid_t pid; /** If the process has not given us a SIGCHLD yet, this has the * waitpid_callback_t that gets invoked once it has. Otherwise this @@ -488,7 +483,7 @@ int tor_split_lines(struct smartlist_t *sl, char *buf, int len); ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count, const process_handle_t *process); #else -ssize_t tor_read_all_handle(FILE *h, char *buf, size_t count, +ssize_t tor_read_all_handle(int fd, char *buf, size_t count, const process_handle_t *process, int *eof); #endif @@ -502,7 +497,7 @@ int tor_process_get_pid(process_handle_t *process_handle); #ifdef _WIN32 HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle); #else -FILE *tor_process_get_stdout_pipe(process_handle_t *process_handle); +int tor_process_get_stdout_pipe(process_handle_t *process_handle); #endif #ifdef _WIN32 @@ -511,7 +506,7 @@ tor_get_lines_from_handle,(HANDLE *handle, enum stream_status *stream_status)); #else MOCK_DECL(struct smartlist_t *, -tor_get_lines_from_handle,(FILE *handle, +tor_get_lines_from_handle,(int fd, enum stream_status *stream_status)); #endif diff --git a/src/common/util_bug.c b/src/common/util_bug.c index c7bfdefe80..3d990e3700 100644 --- a/src/common/util_bug.c +++ b/src/common/util_bug.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/util_bug.h b/src/common/util_bug.h index 0695806911..ae7e7a37fd 100644 --- a/src/common/util_bug.h +++ b/src/common/util_bug.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/util_format.c b/src/common/util_format.c index 7e8ee1b868..1f7b8b03aa 100644 --- a/src/common/util_format.c +++ b/src/common/util_format.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -22,13 +22,16 @@ #include <stdlib.h> /* Return the base32 encoded size in bytes using the source length srclen. - * The NUL terminated byte is added as well since every base32 encoding - * requires enough space for it. */ + * + * (WATCH OUT: This API counts the terminating NUL byte, but + * base64_encode_size does not.) + */ size_t base32_encoded_size(size_t srclen) { size_t enclen; - enclen = CEIL_DIV(srclen*8, 5) + 1; + tor_assert(srclen < SIZE_T_CEILING / 8); + enclen = BASE32_NOPAD_BUFSIZE(srclen); tor_assert(enclen < INT_MAX && enclen > srclen); return enclen; } @@ -41,7 +44,6 @@ base32_encode(char *dest, size_t destlen, const char *src, size_t srclen) size_t nbits = srclen * 8; size_t bit; - tor_assert(srclen < SIZE_T_CEILING/8); /* We need enough space for the encoded data and the extra NUL byte. */ tor_assert(base32_encoded_size(srclen) <= destlen); tor_assert(destlen < SIZE_T_CEILING); @@ -134,6 +136,9 @@ base32_decode(char *dest, size_t destlen, const char *src, size_t srclen) /** Return the Base64 encoded size of <b>srclen</b> bytes of data in * bytes. * + * (WATCH OUT: This API <em>does not</em> count the terminating NUL byte, + * but base32_encoded_size does.) + * * If <b>flags</b>&BASE64_ENCODE_MULTILINE is true, return the size * of the encoded output as multiline output (64 character, `\n' terminated * lines). @@ -142,19 +147,16 @@ size_t base64_encode_size(size_t srclen, int flags) { size_t enclen; + + /* Use INT_MAX for overflow checking because base64_encode() returns int. */ tor_assert(srclen < INT_MAX); + tor_assert(CEIL_DIV(srclen, 3) < INT_MAX / 4); - if (srclen == 0) - return 0; + enclen = BASE64_LEN(srclen); + if (flags & BASE64_ENCODE_MULTILINE) + enclen += CEIL_DIV(enclen, BASE64_OPENSSL_LINELEN); - enclen = ((srclen - 1) / 3) * 4 + 4; - if (flags & BASE64_ENCODE_MULTILINE) { - size_t remainder = enclen % BASE64_OPENSSL_LINELEN; - enclen += enclen / BASE64_OPENSSL_LINELEN; - if (remainder) - enclen++; - } - tor_assert(enclen < INT_MAX && enclen > srclen); + tor_assert(enclen < INT_MAX && (enclen == 0 || enclen > srclen)); return enclen; } @@ -311,39 +313,6 @@ base64_encode_nopad(char *dest, size_t destlen, return (int)(out - dest); } -/** As base64_decode, but do not require any padding on the input */ -int -base64_decode_nopad(uint8_t *dest, size_t destlen, - const char *src, size_t srclen) -{ - if (srclen > SIZE_T_CEILING - 4) - return -1; - char *buf = tor_malloc(srclen + 4); - memcpy(buf, src, srclen+1); - size_t buflen; - switch (srclen % 4) - { - case 0: - default: - buflen = srclen; - break; - case 1: - tor_free(buf); - return -1; - case 2: - memcpy(buf+srclen, "==", 3); - buflen = srclen + 2; - break; - case 3: - memcpy(buf+srclen, "=", 2); - buflen = srclen + 1; - break; - } - int n = base64_decode((char*)dest, destlen, buf, buflen); - tor_free(buf); - return n; -} - #undef BASE64_OPENSSL_LINELEN /** @{ */ @@ -393,15 +362,9 @@ base64_decode(char *dest, size_t destlen, const char *src, size_t srclen) const char *eos = src+srclen; uint32_t n=0; int n_idx=0; - char *dest_orig = dest; + size_t di = 0; - /* Max number of bits == srclen*6. - * Number of bytes required to hold all bits == (srclen*6)/8. - * Yes, we want to round down: anything that hangs over the end of a - * byte is padding. */ - if (!size_mul_check(srclen, 3) || destlen < (srclen*3)/4) - return -1; - if (destlen > SIZE_T_CEILING) + if (destlen > INT_MAX) return -1; /* Make sure we leave no uninitialized data in the destination buffer. */ @@ -429,9 +392,11 @@ base64_decode(char *dest, size_t destlen, const char *src, size_t srclen) n = (n<<6) | v; if ((++n_idx) == 4) { /* We've accumulated 24 bits in n. Flush them. */ - *dest++ = (n>>16); - *dest++ = (n>>8) & 0xff; - *dest++ = (n) & 0xff; + if (destlen < 3 || di > destlen - 3) + return -1; + dest[di++] = (n>>16); + dest[di++] = (n>>8) & 0xff; + dest[di++] = (n) & 0xff; n_idx = 0; n = 0; } @@ -449,18 +414,21 @@ base64_decode(char *dest, size_t destlen, const char *src, size_t srclen) return -1; case 2: /* 12 leftover bits: The last 4 are padding and the first 8 are data. */ - *dest++ = n >> 4; + if (destlen < 1 || di > destlen - 1) + return -1; + dest[di++] = n >> 4; break; case 3: /* 18 leftover bits: The last 2 are padding and the first 16 are data. */ - *dest++ = n >> 10; - *dest++ = n >> 2; + if (destlen < 2 || di > destlen - 2) + return -1; + dest[di++] = n >> 10; + dest[di++] = n >> 2; } - tor_assert((dest-dest_orig) <= (ssize_t)destlen); - tor_assert((dest-dest_orig) <= INT_MAX); + tor_assert(di <= destlen); - return (int)(dest-dest_orig); + return (int)di; } #undef X #undef SP @@ -476,7 +444,8 @@ base16_encode(char *dest, size_t destlen, const char *src, size_t srclen) const char *end; char *cp; - tor_assert(destlen >= srclen*2+1); + tor_assert(srclen < SIZE_T_CEILING / 2 - 1); + tor_assert(destlen >= BASE16_BUFSIZE(srclen)); tor_assert(destlen < SIZE_T_CEILING); /* Make sure we leave no uninitialized data in the destination buffer. */ diff --git a/src/common/util_format.h b/src/common/util_format.h index 20ac711d10..4af8832bbe 100644 --- a/src/common/util_format.h +++ b/src/common/util_format.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001, Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_UTIL_FORMAT_H @@ -10,6 +10,26 @@ #include "testsupport.h" #include "torint.h" +/** @{ */ +/** These macros don't check for overflow. Use them only for constant inputs + * (like array declarations). The *_LEN macros are the raw encoding lengths + * (without terminating NUL), while the *_BUFSIZE macros count the terminating + * NUL. */ +#define BASE64_LEN(n) (CEIL_DIV((n), 3) * 4) +#define BASE32_LEN(n) (CEIL_DIV((n), 5) * 8) +#define BASE16_LEN(n) ((n) * 2) + +#define BASE64_BUFSIZE(n) (BASE64_LEN(n) + 1) +#define BASE32_BUFSIZE(n) (BASE32_LEN(n) + 1) +#define BASE16_BUFSIZE(n) (BASE16_LEN(n) + 1) + +#define BASE64_NOPAD_LEN(n) (CEIL_DIV((n) * 4, 3)) +#define BASE32_NOPAD_LEN(n) (CEIL_DIV((n) * 8, 5)) + +#define BASE64_NOPAD_BUFSIZE(n) (BASE64_NOPAD_LEN(n) + 1) +#define BASE32_NOPAD_BUFSIZE(n) (BASE32_NOPAD_LEN(n) + 1) +/** @} */ + #define BASE64_ENCODE_MULTILINE 1 size_t base64_encode_size(size_t srclen, int flags); int base64_encode(char *dest, size_t destlen, const char *src, size_t srclen, @@ -17,8 +37,6 @@ int base64_encode(char *dest, size_t destlen, const char *src, size_t srclen, int base64_decode(char *dest, size_t destlen, const char *src, size_t srclen); int base64_encode_nopad(char *dest, size_t destlen, const uint8_t *src, size_t srclen); -int base64_decode_nopad(uint8_t *dest, size_t destlen, - const char *src, size_t srclen); /** Characters that can appear (case-insensitively) in a base32 encoding. */ #define BASE32_CHARS "abcdefghijklmnopqrstuvwxyz234567" diff --git a/src/common/util_process.c b/src/common/util_process.c index abda63720c..9e9679b099 100644 --- a/src/common/util_process.c +++ b/src/common/util_process.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/util_process.h b/src/common/util_process.h index d38301a354..c3a63498b5 100644 --- a/src/common/util_process.h +++ b/src/common/util_process.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016, The Tor Project, Inc. */ +/* Copyright (c) 2011-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/common/workqueue.c b/src/common/workqueue.c index e1fb663a2a..42723224d3 100644 --- a/src/common/workqueue.c +++ b/src/common/workqueue.c @@ -25,11 +25,19 @@ #include "orconfig.h" #include "compat.h" #include "compat_threads.h" +#include "crypto.h" #include "util.h" #include "workqueue.h" #include "tor_queue.h" #include "torlog.h" +#define WORKQUEUE_PRIORITY_FIRST WQ_PRI_HIGH +#define WORKQUEUE_PRIORITY_LAST WQ_PRI_LOW +#define WORKQUEUE_N_PRIORITIES (((int) WORKQUEUE_PRIORITY_LAST)+1) + +TOR_TAILQ_HEAD(work_tailq_t, workqueue_entry_s); +typedef struct work_tailq_t work_tailq_t; + struct threadpool_s { /** An array of pointers to workerthread_t: one for each running worker * thread. */ @@ -38,8 +46,12 @@ struct threadpool_s { /** Condition variable that we wait on when we have no work, and which * gets signaled when our queue becomes nonempty. */ tor_cond_t condition; - /** Queue of pending work that we have to do. */ - TOR_TAILQ_HEAD(, workqueue_entry_s) work; + /** Queues of pending work that we have to do. The queue with priority + * <b>p</b> is work[p]. */ + work_tailq_t work[WORKQUEUE_N_PRIORITIES]; + + /** Weak RNG, used to decide when to ignore priority. */ + tor_weak_rng_t weak_rng; /** The current 'update generation' of the threadpool. Any thread that is * at an earlier generation needs to run the update function. */ @@ -66,6 +78,11 @@ struct threadpool_s { void *new_thread_state_arg; }; +/** Used to put a workqueue_priority_t value into a bitfield. */ +#define workqueue_priority_bitfield_t ENUM_BF(workqueue_priority_t) +/** Number of bits needed to hold all legal values of workqueue_priority_t */ +#define WORKQUEUE_PRIORITY_BITS 2 + struct workqueue_entry_s { /** The next workqueue_entry_t that's pending on the same thread or * reply queue. */ @@ -76,6 +93,8 @@ struct workqueue_entry_s { struct threadpool_s *on_pool; /** True iff this entry is waiting for a worker to start processing it. */ uint8_t pending; + /** Priority of this entry. */ + workqueue_priority_bitfield_t priority : WORKQUEUE_PRIORITY_BITS; /** Function to run in the worker thread. */ workqueue_reply_t (*fn)(void *state, void *arg); /** Function to run while processing the reply queue. */ @@ -94,9 +113,7 @@ struct replyqueue_s { alert_sockets_t alert; }; -/** A worker thread represents a single thread in a thread pool. To avoid - * contention, each gets its own queue. This breaks the guarantee that that - * queued work will get executed strictly in order. */ +/** A worker thread represents a single thread in a thread pool. */ typedef struct workerthread_s { /** Which thread it this? In range 0..in_pool->n_threads-1 */ int index; @@ -109,6 +126,8 @@ typedef struct workerthread_s { replyqueue_t *reply_queue; /** The current update generation of this thread */ unsigned generation; + /** One over the probability of taking work from a lower-priority queue. */ + int32_t lower_priority_chance; } workerthread_t; static void queue_reply(replyqueue_t *queue, workqueue_entry_t *work); @@ -125,6 +144,7 @@ workqueue_entry_new(workqueue_reply_t (*fn)(void*, void*), ent->fn = fn; ent->reply_fn = reply_fn; ent->arg = arg; + ent->priority = WQ_PRI_HIGH; return ent; } @@ -161,8 +181,9 @@ workqueue_entry_cancel(workqueue_entry_t *ent) int cancelled = 0; void *result = NULL; tor_mutex_acquire(&ent->on_pool->lock); + workqueue_priority_t prio = ent->priority; if (ent->pending) { - TOR_TAILQ_REMOVE(&ent->on_pool->work, ent, next_work); + TOR_TAILQ_REMOVE(&ent->on_pool->work[prio], ent, next_work); cancelled = 1; result = ent->arg; } @@ -180,8 +201,46 @@ workqueue_entry_cancel(workqueue_entry_t *ent) static int worker_thread_has_work(workerthread_t *thread) { - return !TOR_TAILQ_EMPTY(&thread->in_pool->work) || - thread->generation != thread->in_pool->generation; + unsigned i; + for (i = WORKQUEUE_PRIORITY_FIRST; i <= WORKQUEUE_PRIORITY_LAST; ++i) { + if (!TOR_TAILQ_EMPTY(&thread->in_pool->work[i])) + return 1; + } + return thread->generation != thread->in_pool->generation; +} + +/** Extract the next workqueue_entry_t from the the thread's pool, removing + * it from the relevant queues and marking it as non-pending. + * + * The caller must hold the lock. */ +static workqueue_entry_t * +worker_thread_extract_next_work(workerthread_t *thread) +{ + threadpool_t *pool = thread->in_pool; + work_tailq_t *queue = NULL, *this_queue; + unsigned i; + for (i = WORKQUEUE_PRIORITY_FIRST; i <= WORKQUEUE_PRIORITY_LAST; ++i) { + this_queue = &pool->work[i]; + if (!TOR_TAILQ_EMPTY(this_queue)) { + queue = this_queue; + if (! tor_weak_random_one_in_n(&pool->weak_rng, + thread->lower_priority_chance)) { + /* Usually we'll just break now, so that we can get out of the loop + * and use the queue where we found work. But with a small + * probability, we'll keep looking for lower priority work, so that + * we don't ignore our low-priority queues entirely. */ + break; + } + } + } + + if (queue == NULL) + return NULL; + + workqueue_entry_t *work = TOR_TAILQ_FIRST(queue); + TOR_TAILQ_REMOVE(queue, work, next_work); + work->pending = 0; + return work; } /** @@ -217,9 +276,9 @@ worker_thread_main(void *thread_) tor_mutex_acquire(&pool->lock); continue; } - work = TOR_TAILQ_FIRST(&pool->work); - TOR_TAILQ_REMOVE(&pool->work, work, next_work); - work->pending = 0; + work = worker_thread_extract_next_work(thread); + if (BUG(work == NULL)) + break; tor_mutex_release(&pool->lock); /* We run the work function without holding the thread lock. This @@ -268,12 +327,14 @@ queue_reply(replyqueue_t *queue, workqueue_entry_t *work) /** Allocate and start a new worker thread to use state object <b>state</b>, * and send responses to <b>replyqueue</b>. */ static workerthread_t * -workerthread_new(void *state, threadpool_t *pool, replyqueue_t *replyqueue) +workerthread_new(int32_t lower_priority_chance, + void *state, threadpool_t *pool, replyqueue_t *replyqueue) { workerthread_t *thr = tor_malloc_zero(sizeof(workerthread_t)); thr->state = state; thr->reply_queue = replyqueue; thr->in_pool = pool; + thr->lower_priority_chance = lower_priority_chance; if (spawn_func(worker_thread_main, thr) < 0) { //LCOV_EXCL_START @@ -299,24 +360,34 @@ workerthread_new(void *state, threadpool_t *pool, replyqueue_t *replyqueue) * function's responsibility to free the work object. * * On success, return a workqueue_entry_t object that can be passed to - * workqueue_entry_cancel(). On failure, return NULL. + * workqueue_entry_cancel(). On failure, return NULL. (Failure is not + * currently possible, but callers should check anyway.) + * + * Items are executed in a loose priority order -- each thread will usually + * take from the queued work with the highest prioirity, but will occasionally + * visit lower-priority queues to keep them from starving completely. * - * Note that because each thread has its own work queue, work items may not + * Note that because of priorities and thread behavior, work items may not * be executed strictly in order. */ workqueue_entry_t * -threadpool_queue_work(threadpool_t *pool, - workqueue_reply_t (*fn)(void *, void *), - void (*reply_fn)(void *), - void *arg) +threadpool_queue_work_priority(threadpool_t *pool, + workqueue_priority_t prio, + workqueue_reply_t (*fn)(void *, void *), + void (*reply_fn)(void *), + void *arg) { + tor_assert(((int)prio) >= WORKQUEUE_PRIORITY_FIRST && + ((int)prio) <= WORKQUEUE_PRIORITY_LAST); + workqueue_entry_t *ent = workqueue_entry_new(fn, reply_fn, arg); ent->on_pool = pool; ent->pending = 1; + ent->priority = prio; tor_mutex_acquire(&pool->lock); - TOR_TAILQ_INSERT_TAIL(&pool->work, ent, next_work); + TOR_TAILQ_INSERT_TAIL(&pool->work[prio], ent, next_work); tor_cond_signal_one(&pool->condition); @@ -325,6 +396,16 @@ threadpool_queue_work(threadpool_t *pool, return ent; } +/** As threadpool_queue_work_priority(), but assumes WQ_PRI_HIGH */ +workqueue_entry_t * +threadpool_queue_work(threadpool_t *pool, + workqueue_reply_t (*fn)(void *, void *), + void (*reply_fn)(void *), + void *arg) +{ + return threadpool_queue_work_priority(pool, WQ_PRI_HIGH, fn, reply_fn, arg); +} + /** * Queue a copy of a work item for every thread in a pool. This can be used, * for example, to tell the threads to update some parameter in their states. @@ -388,6 +469,14 @@ threadpool_queue_update(threadpool_t *pool, /** Don't have more than this many threads per pool. */ #define MAX_THREADS 1024 +/** For half of our threads, choose lower priority queues with probability + * 1/N for each of these values. Both are chosen somewhat arbitrarily. If + * CHANCE_PERMISSIVE is too low, then we have a risk of low-priority tasks + * stalling forever. If it's too high, we have a risk of low-priority tasks + * grabbing half of the threads. */ +#define CHANCE_PERMISSIVE 37 +#define CHANCE_STRICT INT32_MAX + /** Launch threads until we have <b>n</b>. */ static int threadpool_start_threads(threadpool_t *pool, int n) @@ -404,8 +493,14 @@ threadpool_start_threads(threadpool_t *pool, int n) sizeof(workerthread_t*), n); while (pool->n_threads < n) { + /* For half of our threads, we'll choose lower priorities permissively; + * for the other half, we'll stick more strictly to higher priorities. + * This keeps slow low-priority tasks from taking over completely. */ + int32_t chance = (pool->n_threads & 1) ? CHANCE_STRICT : CHANCE_PERMISSIVE; + void *state = pool->new_thread_state_fn(pool->new_thread_state_arg); - workerthread_t *thr = workerthread_new(state, pool, pool->reply_queue); + workerthread_t *thr = workerthread_new(chance, + state, pool, pool->reply_queue); if (!thr) { //LCOV_EXCL_START @@ -441,7 +536,15 @@ threadpool_new(int n_threads, pool = tor_malloc_zero(sizeof(threadpool_t)); tor_mutex_init_nonrecursive(&pool->lock); tor_cond_init(&pool->condition); - TOR_TAILQ_INIT(&pool->work); + unsigned i; + for (i = WORKQUEUE_PRIORITY_FIRST; i <= WORKQUEUE_PRIORITY_LAST; ++i) { + TOR_TAILQ_INIT(&pool->work[i]); + } + { + unsigned seed; + crypto_rand((void*)&seed, sizeof(seed)); + tor_init_weak_random(&pool->weak_rng, seed); + } pool->new_thread_state_fn = new_thread_state_fn; pool->new_thread_state_arg = arg; @@ -510,12 +613,13 @@ replyqueue_get_socket(replyqueue_t *rq) void replyqueue_process(replyqueue_t *queue) { - if (queue->alert.drain_fn(queue->alert.read_fd) < 0) { + int r = queue->alert.drain_fn(queue->alert.read_fd); + if (r < 0) { //LCOV_EXCL_START static ratelim_t warn_limit = RATELIM_INIT(7200); log_fn_ratelim(&warn_limit, LOG_WARN, LD_GENERAL, "Failure from drain_fd: %s", - tor_socket_strerror(tor_socket_errno(queue->alert.read_fd))); + tor_socket_strerror(-r)); //LCOV_EXCL_STOP } diff --git a/src/common/workqueue.h b/src/common/workqueue.h index 54276767b0..d2508f5329 100644 --- a/src/common/workqueue.h +++ b/src/common/workqueue.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_WORKQUEUE_H @@ -16,12 +16,26 @@ typedef struct threadpool_s threadpool_t; typedef struct workqueue_entry_s workqueue_entry_t; /** Possible return value from a work function: */ -typedef enum { +typedef enum workqueue_reply_t { WQ_RPL_REPLY = 0, /** indicates success */ WQ_RPL_ERROR = 1, /** indicates fatal error */ WQ_RPL_SHUTDOWN = 2, /** indicates thread is shutting down */ } workqueue_reply_t; +/** Possible priorities for work. Lower numeric values are more important. */ +typedef enum workqueue_priority_t { + WQ_PRI_HIGH = 0, + WQ_PRI_MED = 1, + WQ_PRI_LOW = 2, +} workqueue_priority_t; + +workqueue_entry_t *threadpool_queue_work_priority(threadpool_t *pool, + workqueue_priority_t prio, + workqueue_reply_t (*fn)(void *, + void *), + void (*reply_fn)(void *), + void *arg); + workqueue_entry_t *threadpool_queue_work(threadpool_t *pool, workqueue_reply_t (*fn)(void *, void *), diff --git a/src/config/torrc.sample.in b/src/config/torrc.sample.in index 37777443ac..8f3597f3f6 100644 --- a/src/config/torrc.sample.in +++ b/src/config/torrc.sample.in @@ -209,3 +209,12 @@ ## address manually to your friends, uncomment this line: #PublishServerDescriptor 0 +## Configuration options can be imported from files or folders using the %include +## option with the value being a path. If the path is a file, the options from the +## file will be parsed as if they were written where the %include option is. If +## the path is a folder, all files on that folder will be parsed following lexical +## order. Files starting with a dot are ignored. Files on subfolders are ignored. +## The %include option can be used recursively. +#%include /etc/torrc.d/ +#%include /etc/torrc.custom + diff --git a/src/ext/byteorder.h b/src/ext/byteorder.h new file mode 100644 index 0000000000..c8ba52184b --- /dev/null +++ b/src/ext/byteorder.h @@ -0,0 +1,67 @@ +/* <MIT License> + Copyright (c) 2013-2014 Marek Majkowski <marek@popcount.org> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + </MIT License> + + Original location: + https://github.com/majek/csiphash/ + + Solution inspired by code from: + Samuel Neves (supercop/crypto_auth/siphash24/little) + djb (supercop/crypto_auth/siphash24/little2) + Jean-Philippe Aumasson (https://131002.net/siphash/siphash24.c) +*/ + +/* This code is extracted from csiphash.h */ + +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define _le64toh(x) ((uint64_t)(x)) +#elif defined(_WIN32) +/* Windows is always little endian, unless you're on xbox360 + http://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx */ +# define _le64toh(x) ((uint64_t)(x)) +#elif defined(__APPLE__) +# include <libkern/OSByteOrder.h> +# define _le64toh(x) OSSwapLittleToHostInt64(x) +#elif defined(sun) || defined(__sun) +# include <sys/byteorder.h> +# define _le64toh(x) LE_64(x) + +#else + +/* See: http://sourceforge.net/p/predef/wiki/Endianness/ */ +# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(OpenBSD) +# include <sys/endian.h> +# else +# include <endian.h> +# endif +# if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN +# define _le64toh(x) ((uint64_t)(x)) +# else +# if defined(OpenBSD) +# define _le64toh(x) letoh64(x) +# else +# define _le64toh(x) le64toh(x) +# endif +# endif + +#endif diff --git a/src/ext/csiphash.c b/src/ext/csiphash.c index a8f3d5b8b8..508e4f6ceb 100644 --- a/src/ext/csiphash.c +++ b/src/ext/csiphash.c @@ -35,41 +35,7 @@ #include "util.h" /* for memcpy */ #include <string.h> - -#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ - __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define _le64toh(x) ((uint64_t)(x)) -#elif defined(_WIN32) -/* Windows is always little endian, unless you're on xbox360 - http://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx */ -# define _le64toh(x) ((uint64_t)(x)) -#elif defined(__APPLE__) -# include <libkern/OSByteOrder.h> -# define _le64toh(x) OSSwapLittleToHostInt64(x) -#elif defined(sun) || defined(__sun) -# include <sys/byteorder.h> -# define _le64toh(x) LE_64(x) - -#else - -/* See: http://sourceforge.net/p/predef/wiki/Endianness/ */ -# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(OpenBSD) -# include <sys/endian.h> -# else -# include <endian.h> -# endif -# if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ - __BYTE_ORDER == __LITTLE_ENDIAN -# define _le64toh(x) ((uint64_t)(x)) -# else -# if defined(OpenBSD) -# define _le64toh(x) letoh64(x) -# else -# define _le64toh(x) le64toh(x) -# endif -# endif - -#endif +#include "byteorder.h" #define ROTATE(x, b) (uint64_t)( ((x) << (b)) | ( (x) >> (64 - (b))) ) diff --git a/src/ext/ed25519/donna/ed25519-hash-custom.h b/src/ext/ed25519/donna/ed25519-hash-custom.h index 7dc249129d..609451abd5 100644 --- a/src/ext/ed25519/donna/ed25519-hash-custom.h +++ b/src/ext/ed25519/donna/ed25519-hash-custom.h @@ -9,3 +9,34 @@ void ed25519_hash(uint8_t *hash, const uint8_t *in, size_t inlen); */ +#include "crypto.h" + +typedef struct ed25519_hash_context { + crypto_digest_t *ctx; +} ed25519_hash_context; + + +static void +ed25519_hash_init(ed25519_hash_context *ctx) +{ + ctx->ctx = crypto_digest512_new(DIGEST_SHA512); +} +static void +ed25519_hash_update(ed25519_hash_context *ctx, const uint8_t *in, size_t inlen) +{ + crypto_digest_add_bytes(ctx->ctx, (const char *)in, inlen); +} +static void +ed25519_hash_final(ed25519_hash_context *ctx, uint8_t *hash) +{ + crypto_digest_get_digest(ctx->ctx, (char *)hash, DIGEST512_LEN); + crypto_digest_free(ctx->ctx); + ctx->ctx = NULL; +} +static void +ed25519_hash(uint8_t *hash, const uint8_t *in, size_t inlen) +{ + crypto_digest512((char *)hash, (const char *)in, inlen, + DIGEST_SHA512); +} + diff --git a/src/ext/ed25519/ref10/crypto_hash_sha512.h b/src/ext/ed25519/ref10/crypto_hash_sha512.h index 0278571522..5dad935c79 100644 --- a/src/ext/ed25519/ref10/crypto_hash_sha512.h +++ b/src/ext/ed25519/ref10/crypto_hash_sha512.h @@ -1,30 +1,32 @@ /* Added for Tor. */ -#include <openssl/sha.h> +#include "crypto.h" /* Set 'out' to the 512-bit SHA512 hash of the 'len'-byte string in 'inp' */ #define crypto_hash_sha512(out, inp, len) \ - SHA512((inp), (len), (out)) + crypto_digest512((char *)(out), (const char *)(inp), (len), DIGEST_SHA512) /* Set 'out' to the 512-bit SHA512 hash of the 'len1'-byte string in 'inp1', * concatenated with the 'len2'-byte string in 'inp2'. */ #define crypto_hash_sha512_2(out, inp1, len1, inp2, len2) \ do { \ - SHA512_CTX sha_ctx_; \ - SHA512_Init(&sha_ctx_); \ - SHA512_Update(&sha_ctx_, (inp1), (len1)); \ - SHA512_Update(&sha_ctx_, (inp2), (len2)); \ - SHA512_Final((out), &sha_ctx_); \ - } while(0) + crypto_digest_t *sha_ctx_; \ + sha_ctx_ = crypto_digest512_new(DIGEST_SHA512); \ + crypto_digest_add_bytes(sha_ctx_, (const char *)(inp1), (len1)); \ + crypto_digest_add_bytes(sha_ctx_, (const char *)(inp2), (len2)); \ + crypto_digest_get_digest(sha_ctx_, (char *)out, DIGEST512_LEN); \ + crypto_digest_free(sha_ctx_); \ + } while (0) /* Set 'out' to the 512-bit SHA512 hash of the 'len1'-byte string in 'inp1', * concatenated with the 'len2'-byte string in 'inp2', concatenated with * the 'len3'-byte string in 'len3'. */ #define crypto_hash_sha512_3(out, inp1, len1, inp2, len2, inp3, len3) \ do { \ - SHA512_CTX sha_ctx_; \ - SHA512_Init(&sha_ctx_); \ - SHA512_Update(&sha_ctx_, (inp1), (len1)); \ - SHA512_Update(&sha_ctx_, (inp2), (len2)); \ - SHA512_Update(&sha_ctx_, (inp3), (len3)); \ - SHA512_Final((out), &sha_ctx_); \ + crypto_digest_t *sha_ctx_; \ + sha_ctx_ = crypto_digest512_new(DIGEST_SHA512); \ + crypto_digest_add_bytes(sha_ctx_, (const char *)(inp1), (len1)); \ + crypto_digest_add_bytes(sha_ctx_, (const char *)(inp2), (len2)); \ + crypto_digest_add_bytes(sha_ctx_, (const char *)(inp3), (len3)); \ + crypto_digest_get_digest(sha_ctx_, (char *)out, DIGEST512_LEN); \ + crypto_digest_free(sha_ctx_); \ } while(0) diff --git a/src/ext/ht.h b/src/ext/ht.h index a441d0b685..caa420e9b5 100644 --- a/src/ext/ht.h +++ b/src/ext/ht.h @@ -1,6 +1,6 @@ /* Copyright (c) 2002, Christopher Clark. * Copyright (c) 2005-2006, Nick Mathewson. - * Copyright (c) 2007-2015, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See license at end. */ /* Based on ideas by Christopher Clark and interfaces from Niels Provos. */ diff --git a/src/ext/include.am b/src/ext/include.am index f00f3e031e..7ec2e43312 100644 --- a/src/ext/include.am +++ b/src/ext/include.am @@ -5,6 +5,7 @@ EXTRA_DIST += src/ext/README EXTHEADERS = \ src/ext/ht.h \ + src/ext/byteorder.h \ src/ext/tinytest.h \ src/ext/tor_readpassphrase.h \ src/ext/strlcat.c \ @@ -100,6 +101,7 @@ noinst_LIBRARIES += $(LIBED25519_REF10) src_ext_ed25519_donna_libed25519_donna_a_CFLAGS=\ @CFLAGS_CONSTTIME@ \ -DED25519_CUSTOMRANDOM \ + -DED25519_CUSTOMHASH \ -DED25519_SUFFIX=_donna src_ext_ed25519_donna_libed25519_donna_a_SOURCES= \ diff --git a/src/ext/keccak-tiny/keccak-tiny-unrolled.c b/src/ext/keccak-tiny/keccak-tiny-unrolled.c index d1342c3601..d8d7fe335a 100644 --- a/src/ext/keccak-tiny/keccak-tiny-unrolled.c +++ b/src/ext/keccak-tiny/keccak-tiny-unrolled.c @@ -10,28 +10,21 @@ #include <string.h> #include "crypto.h" +#include "byteorder.h" /******** Endianness conversion helpers ********/ static inline uint64_t loadu64le(const unsigned char *x) { - uint64_t r = 0; - size_t i; - - for (i = 0; i < 8; ++i) { - r |= (uint64_t)x[i] << 8 * i; - } - return r; + uint64_t r; + memcpy(&r, x, sizeof(r)); + return _le64toh(r); } static inline void storeu64le(uint8_t *x, uint64_t u) { - size_t i; - - for(i=0; i<8; ++i) { - x[i] = u; - u >>= 8; - } + uint64_t val = _le64toh(u); + memcpy(x, &val, sizeof(u)); } /******** The Keccak-f[1600] permutation ********/ diff --git a/src/ext/rust b/src/ext/rust new file mode 160000 +Subproject 240296800824e40b10cb8c16da0e71156335394 diff --git a/src/ext/trunnel/trunnel-impl.h b/src/ext/trunnel/trunnel-impl.h index bc805851b1..85c847b3fd 100644 --- a/src/ext/trunnel/trunnel-impl.h +++ b/src/ext/trunnel/trunnel-impl.h @@ -5,7 +5,7 @@ /* trunnel-impl.h -- Implementation helpers for trunnel, included by * generated trunnel files * - * Copyright 2014-2015, The Tor Project, Inc. + * Copyright 2014-2017, The Tor Project, Inc. * See license at the end of this file for copying information. */ diff --git a/src/ext/trunnel/trunnel.c b/src/ext/trunnel/trunnel.c index 8f72351277..6a42417241 100644 --- a/src/ext/trunnel/trunnel.c +++ b/src/ext/trunnel/trunnel.c @@ -4,7 +4,7 @@ */ /* trunnel.c -- Helper functions to implement trunnel. * - * Copyright 2014-2015, The Tor Project, Inc. + * Copyright 2014-2017, The Tor Project, Inc. * See license at the end of this file for copying information. * * See trunnel-impl.h for documentation of these functions. @@ -31,7 +31,7 @@ # define IS_LITTLE_ENDIAN # endif #else -# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(OpenBSD) +# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) # include <sys/endian.h> # else # include <endian.h> diff --git a/src/ext/trunnel/trunnel.h b/src/ext/trunnel/trunnel.h index 85bbcc5451..dd78553c77 100644 --- a/src/ext/trunnel/trunnel.h +++ b/src/ext/trunnel/trunnel.h @@ -5,7 +5,7 @@ /* trunnel.h -- Public declarations for trunnel, to be included * in trunnel header files. - * Copyright 2014-2015, The Tor Project, Inc. + * Copyright 2014-2017, The Tor Project, Inc. * See license at the end of this file for copying information. */ diff --git a/src/include.am b/src/include.am index d12684e187..90ecf90d45 100644 --- a/src/include.am +++ b/src/include.am @@ -2,8 +2,10 @@ include src/ext/include.am include src/trunnel/include.am include src/common/include.am include src/or/include.am +include src/rust/include.am include src/test/include.am include src/tools/include.am include src/win32/include.am include src/config/include.am include src/test/fuzz/include.am +include src/trace/include.am diff --git a/src/or/Makefile.nmake b/src/or/Makefile.nmake index 2ac98cd372..429ae67858 100644 --- a/src/or/Makefile.nmake +++ b/src/or/Makefile.nmake @@ -14,6 +14,7 @@ LIBTOR_OBJECTS = \ addressmap.obj \ buffers.obj \ channel.obj \ + channelpadding.obj \ channeltls.obj \ circpathbias.obj \ circuitbuild.obj \ diff --git a/src/or/addressmap.c b/src/or/addressmap.c index 85a6434f4a..c92af38254 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/addressmap.h b/src/or/addressmap.h index 67648d0518..80f453b4c1 100644 --- a/src/or/addressmap.h +++ b/src/or/addressmap.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_ADDRESSMAP_H diff --git a/src/or/bridges.c b/src/or/bridges.c index 0b4588307c..0818fb0812 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -571,15 +571,23 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) return; } + tor_addr_port_t bridge_addrport; + memcpy(&bridge_addrport.addr, &bridge->addr, sizeof(tor_addr_t)); + bridge_addrport.port = bridge->port; + guard_state = get_guard_state_for_bridge_desc_fetch(bridge->identity); - directory_initiate_command(&bridge->addr, bridge->port, - NULL, 0, /*no dirport*/ - bridge->identity, - DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_BRIDGE, - DIRIND_ONEHOP, "authority.z", NULL, 0, 0, - guard_state); + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_SERVERDESC); + directory_request_set_or_addr_port(req, &bridge_addrport); + directory_request_set_directory_id_digest(req, bridge->identity); + directory_request_set_router_purpose(req, ROUTER_PURPOSE_BRIDGE); + directory_request_set_resource(req, "authority.z"); + if (guard_state) { + directory_request_set_guard_state(req, guard_state); + } + directory_initiate_request(req); + directory_request_free(req); } /** Fetching the bridge descriptor from the bridge authority returned a diff --git a/src/or/bridges.h b/src/or/bridges.h index 27ea5e197c..3bfc782f9a 100644 --- a/src/or/bridges.h +++ b/src/or/bridges.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/buffers.c b/src/or/buffers.c index 201778e301..12a6c0239b 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -83,7 +83,11 @@ static int parse_socks_client(const uint8_t *data, size_t datalen, #define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0]) /* We leave this many NUL bytes at the end of the buffer. */ +#ifdef DISABLE_MEMORY_SENTINELS +#define SENTINEL_LEN 0 +#else #define SENTINEL_LEN 4 +#endif /* Header size plus NUL bytes at the end */ #define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN) @@ -97,18 +101,22 @@ static int parse_socks_client(const uint8_t *data, size_t datalen, #define DEBUG_SENTINEL -#ifdef DEBUG_SENTINEL +#if defined(DEBUG_SENTINEL) && !defined(DISABLE_MEMORY_SENTINELS) #define DBG_S(s) s #else #define DBG_S(s) (void)0 #endif +#ifdef DISABLE_MEMORY_SENTINELS +#define CHUNK_SET_SENTINEL(chunk, alloclen) STMT_NIL +#else #define CHUNK_SET_SENTINEL(chunk, alloclen) do { \ uint8_t *a = (uint8_t*) &(chunk)->mem[(chunk)->memlen]; \ DBG_S(uint8_t *b = &((uint8_t*)(chunk))[(alloclen)-SENTINEL_LEN]); \ DBG_S(tor_assert(a == b)); \ memset(a,0,SENTINEL_LEN); \ } while (0) +#endif /** Return the next character in <b>chunk</b> onto which data can be appended. * If the chunk is full, this might be off the end of chunk->mem. */ @@ -1311,7 +1319,7 @@ fetch_from_buf_http(buf_t *buf, /** * Wait this many seconds before warning the user about using SOCKS unsafely - * again (requires that WarnUnsafeSocks is turned on). */ + * again. */ #define SOCKS_WARN_INTERVAL 5 /** Warn that the user application has made an unsafe socks request using @@ -1323,9 +1331,6 @@ log_unsafe_socks_warning(int socks_protocol, const char *address, { static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); - const or_options_t *options = get_options(); - if (! options->WarnUnsafeSocks) - return; if (safe_socks) { log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, "Your application (using socks%d to port %d) is giving " @@ -2081,13 +2086,13 @@ fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) } /** Compress on uncompress the <b>data_len</b> bytes in <b>data</b> using the - * zlib state <b>state</b>, appending the result to <b>buf</b>. If + * compression state <b>state</b>, appending the result to <b>buf</b>. If * <b>done</b> is true, flush the data in the state and finish the * compression/uncompression. Return -1 on failure, 0 on success. */ int -write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state, - const char *data, size_t data_len, - int done) +write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, + const char *data, size_t data_len, + const int done) { char *next; size_t old_avail, avail; @@ -2101,22 +2106,31 @@ write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state, } next = CHUNK_WRITE_PTR(buf->tail); avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail); - switch (tor_zlib_process(state, &next, &avail, &data, &data_len, done)) { - case TOR_ZLIB_DONE: + switch (tor_compress_process(state, &next, &avail, + &data, &data_len, done)) { + case TOR_COMPRESS_DONE: over = 1; break; - case TOR_ZLIB_ERR: + case TOR_COMPRESS_ERROR: return -1; - case TOR_ZLIB_OK: - if (data_len == 0) + case TOR_COMPRESS_OK: + if (data_len == 0) { + tor_assert_nonfatal(!done); over = 1; + } break; - case TOR_ZLIB_BUF_FULL: + case TOR_COMPRESS_BUFFER_FULL: if (avail) { - /* Zlib says we need more room (ZLIB_BUF_FULL). Start a new chunk - * automatically, whether were going to or not. */ + /* The compression module says we need more room + * (TOR_COMPRESS_BUFFER_FULL). Start a new chunk automatically, + * whether were going to or not. */ need_new_chunk = 1; } + if (data_len == 0 && !done) { + /* We've consumed all the input data, though, so there's no + * point in forging ahead right now. */ + over = 1; + } break; } buf->datalen += old_avail - avail; diff --git a/src/or/buffers.h b/src/or/buffers.h index bb53b3bbff..23b58a571a 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -36,8 +36,8 @@ int flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen); int flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t sz, size_t *buf_flushlen); int write_to_buf(const char *string, size_t string_len, buf_t *buf); -int write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state, - const char *data, size_t data_len, int done); +int write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, + const char *data, size_t data_len, int done); int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); int fetch_from_buf(char *string, size_t string_len, buf_t *buf); int fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto); diff --git a/src/or/channel.c b/src/or/channel.c index 45f1602ab2..2970b96791 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -49,6 +49,7 @@ #include "or.h" #include "channel.h" #include "channeltls.h" +#include "channelpadding.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" @@ -63,6 +64,9 @@ #include "router.h" #include "routerlist.h" #include "scheduler.h" +#include "compat_time.h" +#include "networkstatus.h" +#include "rendservice.h" /* Global lists of channels */ @@ -84,6 +88,28 @@ static smartlist_t *active_listeners = NULL; /* All channel_listener_t instances in LISTENING state */ static smartlist_t *finished_listeners = NULL; +/** Map from channel->global_identifier to channel. Contains the same + * elements as all_channels. */ +static HT_HEAD(channel_gid_map, channel_s) channel_gid_map = HT_INITIALIZER(); + +static unsigned +channel_id_hash(const channel_t *chan) +{ + return (unsigned) chan->global_identifier; +} +static int +channel_id_eq(const channel_t *a, const channel_t *b) +{ + return a->global_identifier == b->global_identifier; +} +HT_PROTOTYPE(channel_gid_map, channel_s, gidmap_node, + channel_id_hash, channel_id_eq) +HT_GENERATE2(channel_gid_map, channel_s, gidmap_node, + channel_id_hash, channel_id_eq, + 0.6, tor_reallocarray_, tor_free_) + +HANDLE_IMPL(channel, channel_s,) + /* Counter for ID numbers */ static uint64_t n_channels_allocated = 0; /* @@ -429,6 +455,7 @@ void channel_register(channel_t *chan) { tor_assert(chan); + tor_assert(chan->global_identifier); /* No-op if already registered */ if (chan->registered) return; @@ -443,6 +470,8 @@ channel_register(channel_t *chan) /* Make sure we have all_channels, then add it */ if (!all_channels) all_channels = smartlist_new(); smartlist_add(all_channels, chan); + channel_t *oldval = HT_REPLACE(channel_gid_map, &channel_gid_map, chan); + tor_assert(! oldval); /* Is it finished? */ if (CHANNEL_FINISHED(chan)) { @@ -498,7 +527,9 @@ channel_unregister(channel_t *chan) } /* Get it out of all_channels */ - if (all_channels) smartlist_remove(all_channels, chan); + if (all_channels) smartlist_remove(all_channels, chan); + channel_t *oldval = HT_REMOVE(channel_gid_map, &channel_gid_map, chan); + tor_assert(oldval == NULL || oldval == chan); /* Mark it as unregistered */ chan->registered = 0; @@ -533,7 +564,7 @@ channel_listener_register(channel_listener_t *chan_l) channel_listener_state_to_string(chan_l->state), chan_l->state); - /* Make sure we have all_channels, then add it */ + /* Make sure we have all_listeners, then add it */ if (!all_listeners) all_listeners = smartlist_new(); smartlist_add(all_listeners, chan_l); @@ -578,7 +609,7 @@ channel_listener_unregister(channel_listener_t *chan_l) if (active_listeners) smartlist_remove(active_listeners, chan_l); } - /* Get it out of all_channels */ + /* Get it out of all_listeners */ if (all_listeners) smartlist_remove(all_listeners, chan_l); /* Mark it as unregistered */ @@ -719,15 +750,13 @@ channel_remove_from_digest_map(channel_t *chan) channel_t * channel_find_by_global_id(uint64_t global_identifier) { + channel_t lookup; channel_t *rv = NULL; - if (all_channels && smartlist_len(all_channels) > 0) { - SMARTLIST_FOREACH_BEGIN(all_channels, channel_t *, curr) { - if (curr->global_identifier == global_identifier) { - rv = curr; - break; - } - } SMARTLIST_FOREACH_END(curr); + lookup.global_identifier = global_identifier; + rv = HT_FIND(channel_gid_map, &channel_gid_map, &lookup); + if (rv) { + tor_assert(rv->global_identifier == global_identifier); } return rv; @@ -809,6 +838,83 @@ channel_next_with_rsa_identity(channel_t *chan) } /** + * Relays run this once an hour to look over our list of channels to other + * relays. It prints out some statistics if there are multiple connections + * to many relays. + * + * This function is similar to connection_or_set_bad_connections(), + * and probably could be adapted to replace it, if it was modified to actually + * take action on any of these connections. + */ +void +channel_check_for_duplicates(void) +{ + channel_idmap_entry_t **iter; + channel_t *chan; + int total_relay_connections = 0, total_relays = 0, total_canonical = 0; + int total_half_canonical = 0; + int total_gt_one_connection = 0, total_gt_two_connections = 0; + int total_gt_four_connections = 0; + + HT_FOREACH(iter, channel_idmap, &channel_identity_map) { + int connections_to_relay = 0; + + /* Only consider relay connections */ + if (!connection_or_digest_is_known_relay((char*)(*iter)->digest)) + continue; + + total_relays++; + + for (chan = TOR_LIST_FIRST(&(*iter)->channel_list); chan; + chan = channel_next_with_rsa_identity(chan)) { + + if (CHANNEL_CONDEMNED(chan) || !CHANNEL_IS_OPEN(chan)) + continue; + + connections_to_relay++; + total_relay_connections++; + + if (chan->is_canonical(chan, 0)) total_canonical++; + + if (!chan->is_canonical_to_peer && chan->is_canonical(chan, 0) + && chan->is_canonical(chan, 1)) { + total_half_canonical++; + } + } + + if (connections_to_relay > 1) total_gt_one_connection++; + if (connections_to_relay > 2) total_gt_two_connections++; + if (connections_to_relay > 4) total_gt_four_connections++; + } + +#define MIN_RELAY_CONNECTIONS_TO_WARN 5 + + /* If we average 1.5 or more connections per relay, something is wrong */ + if (total_relays > MIN_RELAY_CONNECTIONS_TO_WARN && + total_relay_connections >= 1.5*total_relays) { + log_notice(LD_OR, + "Your relay has a very large number of connections to other relays. " + "Is your outbound address the same as your relay address? " + "Found %d connections to %d relays. Found %d current canonical " + "connections, in %d of which we were a non-canonical peer. " + "%d relays had more than 1 connection, %d had more than 2, and " + "%d had more than 4 connections.", + total_relay_connections, total_relays, total_canonical, + total_half_canonical, total_gt_one_connection, + total_gt_two_connections, total_gt_four_connections); + } else { + log_info(LD_OR, "Performed connection pruning. " + "Found %d connections to %d relays. Found %d current canonical " + "connections, in %d of which we were a non-canonical peer. " + "%d relays had more than 1 connection, %d had more than 2, and " + "%d had more than 4 connections.", + total_relay_connections, total_relays, total_canonical, + total_half_canonical, total_gt_one_connection, + total_gt_two_connections, total_gt_four_connections); + } +} + +/** * Initialize a channel * * This function should be called by subclasses to set up some per-channel @@ -822,7 +928,7 @@ channel_init(channel_t *chan) tor_assert(chan); /* Assign an ID and bump the counter */ - chan->global_identifier = n_channels_allocated++; + chan->global_identifier = ++n_channels_allocated; /* Init timestamp */ chan->timestamp_last_had_circuits = time(NULL); @@ -861,7 +967,7 @@ channel_init_listener(channel_listener_t *chan_l) tor_assert(chan_l); /* Assign an ID and bump the counter */ - chan_l->global_identifier = n_channels_allocated++; + chan_l->global_identifier = ++n_channels_allocated; /* Timestamp it */ channel_listener_timestamp_created(chan_l); @@ -898,6 +1004,11 @@ channel_free(channel_t *chan) circuitmux_set_policy(chan->cmux, NULL); } + /* Remove all timers and associated handle entries now */ + timer_free(chan->padding_timer); + channel_handle_free(chan->timer_handle); + channel_handles_clear(chan); + /* Call a free method if there is one */ if (chan->free_fn) chan->free_fn(chan); @@ -976,6 +1087,11 @@ channel_force_free(channel_t *chan) circuitmux_set_policy(chan->cmux, NULL); } + /* Remove all timers and associated handle entries now */ + timer_free(chan->padding_timer); + channel_handle_free(chan->timer_handle); + channel_handles_clear(chan); + /* Call a free method if there is one */ if (chan->free_fn) chan->free_fn(chan); @@ -2580,7 +2696,7 @@ channel_do_open_actions(channel_t *chan) router_set_status(chan->identity_digest, 1); } else { /* only report it to the geoip module if it's not a known router */ - if (!router_get_by_id_digest(chan->identity_digest)) { + if (!connection_or_digest_is_known_relay(chan->identity_digest)) { if (channel_get_addr_if_possible(chan, &remote_addr)) { char *transport_name = NULL; if (chan->get_transport_name(chan, &transport_name) < 0) @@ -2595,6 +2711,32 @@ channel_do_open_actions(channel_t *chan) } } + /* Disable or reduce padding according to user prefs. */ + if (chan->padding_enabled || get_options()->ConnectionPadding == 1) { + if (!get_options()->ConnectionPadding) { + /* Disable if torrc disabled */ + channelpadding_disable_padding_on_channel(chan); + } else if (get_options()->Tor2webMode && + !networkstatus_get_param(NULL, + CHANNELPADDING_TOR2WEB_PARAM, + CHANNELPADDING_TOR2WEB_DEFAULT, 0, 1)) { + /* Disable if we're using tor2web and the consensus disabled padding + * for tor2web */ + channelpadding_disable_padding_on_channel(chan); + } else if (rend_service_allow_non_anonymous_connection(get_options()) && + !networkstatus_get_param(NULL, + CHANNELPADDING_SOS_PARAM, + CHANNELPADDING_SOS_DEFAULT, 0, 1)) { + /* Disable if we're using RSOS and the consensus disabled padding + * for RSOS*/ + channelpadding_disable_padding_on_channel(chan); + } else if (get_options()->ReducedConnectionPadding) { + /* Padding can be forced and/or reduced by clients, regardless of if + * the channel supports it */ + channelpadding_reduce_padding_on_channel(chan); + } + } + circuit_n_chan_done(chan, 1, close_origin_circuits); } @@ -3232,6 +3374,11 @@ channel_free_all(void) /* Geez, anything still left over just won't die ... let it leak then */ HT_CLEAR(channel_idmap, &channel_identity_map); + /* Same with channel_gid_map */ + log_debug(LD_CHANNEL, + "Freeing channel_gid_map"); + HT_CLEAR(channel_gid_map, &channel_gid_map); + log_debug(LD_CHANNEL, "Done cleaning up after channels"); } @@ -3267,22 +3414,20 @@ channel_connect(const tor_addr_t *addr, uint16_t port, */ int -channel_is_better(time_t now, channel_t *a, channel_t *b, - int forgive_new_connections) +channel_is_better(channel_t *a, channel_t *b) { - int a_grace, b_grace; int a_is_canonical, b_is_canonical; - int a_has_circs, b_has_circs; - - /* - * Do not definitively deprecate a new channel with no circuits on it - * until this much time has passed. - */ -#define NEW_CHAN_GRACE_PERIOD (15*60) tor_assert(a); tor_assert(b); + /* If one channel is bad for new circuits, and the other isn't, + * use the one that is still good. */ + if (!channel_is_bad_for_new_circs(a) && channel_is_bad_for_new_circs(b)) + return 1; + if (channel_is_bad_for_new_circs(a) && !channel_is_bad_for_new_circs(b)) + return 0; + /* Check if one is canonical and the other isn't first */ a_is_canonical = channel_is_canonical(a); b_is_canonical = channel_is_canonical(b); @@ -3290,26 +3435,31 @@ channel_is_better(time_t now, channel_t *a, channel_t *b, if (a_is_canonical && !b_is_canonical) return 1; if (!a_is_canonical && b_is_canonical) return 0; + /* Check if we suspect that one of the channels will be preferred + * by the peer */ + if (a->is_canonical_to_peer && !b->is_canonical_to_peer) return 1; + if (!a->is_canonical_to_peer && b->is_canonical_to_peer) return 0; + /* - * Okay, if we're here they tied on canonicity. Next we check if - * they have any circuits, and if one does and the other doesn't, - * we prefer the one that does, unless we are forgiving and the - * one that has no circuits is in its grace period. + * Okay, if we're here they tied on canonicity, the prefer the older + * connection, so that the adversary can't create a new connection + * and try to switch us over to it (which will leak information + * about long-lived circuits). Additionally, switching connections + * too often makes us more vulnerable to attacks like Torscan and + * passive netflow-based equivalents. + * + * Connections will still only live for at most a week, due to + * the check in connection_or_group_set_badness() against + * TIME_BEFORE_OR_CONN_IS_TOO_OLD, which marks old connections as + * unusable for new circuits after 1 week. That check sets + * is_bad_for_new_circs, which is checked in channel_get_for_extend(). + * + * We check channel_is_bad_for_new_circs() above here anyway, for safety. */ + if (channel_when_created(a) < channel_when_created(b)) return 1; + else if (channel_when_created(a) > channel_when_created(b)) return 0; - a_has_circs = (channel_num_circuits(a) > 0); - b_has_circs = (channel_num_circuits(b) > 0); - a_grace = (forgive_new_connections && - (now < channel_when_created(a) + NEW_CHAN_GRACE_PERIOD)); - b_grace = (forgive_new_connections && - (now < channel_when_created(b) + NEW_CHAN_GRACE_PERIOD)); - - if (a_has_circs && !b_has_circs && !b_grace) return 1; - if (!a_has_circs && b_has_circs && !a_grace) return 0; - - /* They tied on circuits too; just prefer whichever is newer */ - - if (channel_when_created(a) > channel_when_created(b)) return 1; + if (channel_num_circuits(a) > channel_num_circuits(b)) return 1; else return 0; } @@ -3334,7 +3484,6 @@ channel_get_for_extend(const char *rsa_id_digest, channel_t *chan, *best = NULL; int n_inprogress_goodaddr = 0, n_old = 0; int n_noncanonical = 0, n_possible = 0; - time_t now = approx_time(); tor_assert(msg_out); tor_assert(launch_out); @@ -3404,7 +3553,7 @@ channel_get_for_extend(const char *rsa_id_digest, continue; } - if (channel_is_better(now, chan, best, 0)) + if (channel_is_better(chan, best)) best = chan; } @@ -4186,8 +4335,12 @@ channel_timestamp_active(channel_t *chan) time_t now = time(NULL); tor_assert(chan); + chan->timestamp_xfer_ms = monotime_coarse_absolute_msec(); chan->timestamp_active = now; + + /* Clear any potential netflow padding timer. We're active */ + chan->next_padding_time_ms = 0; } /** @@ -4270,11 +4423,14 @@ void channel_timestamp_recv(channel_t *chan) { time_t now = time(NULL); - tor_assert(chan); + chan->timestamp_xfer_ms = monotime_coarse_absolute_msec(); chan->timestamp_active = now; chan->timestamp_recv = now; + + /* Clear any potential netflow padding timer. We're active */ + chan->next_padding_time_ms = 0; } /** @@ -4287,11 +4443,15 @@ void channel_timestamp_xmit(channel_t *chan) { time_t now = time(NULL); - tor_assert(chan); + chan->timestamp_xfer_ms = monotime_coarse_absolute_msec(); + chan->timestamp_active = now; chan->timestamp_xmit = now; + + /* Clear any potential netflow padding timer. We're active */ + chan->next_padding_time_ms = 0; } /*************************************************************** diff --git a/src/or/channel.h b/src/or/channel.h index 26aa93b5e2..ea280f2fd2 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -11,6 +11,8 @@ #include "or.h" #include "circuitmux.h" +#include "timers.h" +#include "handles.h" /* Channel handler function pointer typedefs */ typedef void (*channel_listener_fn_ptr)(channel_listener_t *, channel_t *); @@ -22,6 +24,17 @@ TOR_SIMPLEQ_HEAD(chan_cell_queue, cell_queue_entry_s); typedef struct chan_cell_queue chan_cell_queue_t; /** + * This enum is used by channelpadding to decide when to pad channels. + * Don't add values to it without updating the checks in + * channelpadding_decide_to_pad_channel(). + */ +typedef enum { + CHANNEL_USED_NOT_USED_FOR_FULL_CIRCS = 0, + CHANNEL_USED_FOR_FULL_CIRCS, + CHANNEL_USED_FOR_USER_TRAFFIC, +} channel_usage_info_t; + +/** * Channel struct; see the channel_t typedef in or.h. A channel is an * abstract interface for the OR-to-OR connection, similar to connection_or_t, * but without the strong coupling to the underlying TLS implementation. They @@ -34,11 +47,17 @@ struct channel_s { /** Magic number for type-checking cast macros */ uint32_t magic; + /** List entry for hashtable for global-identifier lookup. */ + HT_ENTRY(channel_s) gidmap_node; + + /** Handle entry for handle-based lookup */ + HANDLE_ENTRY(channel, channel_s); + /** Current channel state */ channel_state_t state; /** Globally unique ID number for a channel over the lifetime of a Tor - * process. + * process. This may not be 0. */ uint64_t global_identifier; @@ -48,6 +67,61 @@ struct channel_s { /** has this channel ever been open? */ unsigned int has_been_open:1; + /** + * This field indicates if the other side has enabled or disabled + * padding via either the link protocol version or + * channelpadding_negotiate cells. + * + * Clients can override this with ConnectionPadding in torrc to + * disable or force padding to relays, but relays cannot override the + * client's request. + */ + unsigned int padding_enabled:1; + + /** Cached value of our decision to pad (to avoid expensive + * checks during critical path statistics counting). */ + unsigned int currently_padding:1; + + /** Is there a pending netflow padding callback? */ + unsigned int pending_padding_callback:1; + + /** Is our peer likely to consider this channel canonical? */ + unsigned int is_canonical_to_peer:1; + + /** Has this channel ever been used for non-directory traffic? + * Used to decide what channels to pad, and when. */ + channel_usage_info_t channel_usage; + + /** When should we send a cell for netflow padding, in absolute + * milliseconds since monotime system start. 0 means no padding + * is scheduled. */ + uint64_t next_padding_time_ms; + + /** The callback pointer for the padding callbacks */ + tor_timer_t *padding_timer; + /** The handle to this channel (to free on canceled timers) */ + struct channel_handle_t *timer_handle; + + /** + * These two fields specify the minimum and maximum negotiated timeout + * values for inactivity (send or receive) before we decide to pad a + * channel. These fields can be set either via a PADDING_NEGOTIATE cell, + * or the torrc option ReducedConnectionPadding. The consensus parameters + * nf_ito_low and nf_ito_high are used to ensure that padding can only be + * negotiated to be less frequent than what is specified in the consensus. + * (This is done to prevent wingnut clients from requesting excessive + * padding). + * + * The actual timeout value is randomly chosen between these two values + * as per the table in channelpadding_get_netflow_inactive_timeout_ms(), + * after ensuring that these values do not specify lower timeouts than + * the consensus parameters. + * + * If these are 0, we have not negotiated or specified custom padding + * times, and instead use consensus defaults. */ + uint16_t padding_timeout_low_ms; + uint16_t padding_timeout_high_ms; + /** Why did we close? */ enum { @@ -87,6 +161,18 @@ struct channel_s { time_t timestamp_created; /* Channel created */ time_t timestamp_active; /* Any activity */ + /** + * This is a high-resolution monotonic timestamp that marks when we + * believe the channel has actually sent or received data to/from + * the wire. Right now, it is used to determine when we should send + * a padding cell for channelpadding. + * + * XXX: Are we setting timestamp_xfer_ms in the right places to + * accurately reflect actual network data transfer? Or might this be + * very wrong wrt when bytes actually go on the wire? + */ + uint64_t timestamp_xfer_ms; + /* Methods implemented by the lower layer */ /** Free a channel */ @@ -214,8 +300,8 @@ struct channel_s { unsigned int is_bad_for_new_circs:1; /** True iff we have decided that the other end of this connection - * is a client. Channels with this flag set should never be used - * to satisfy an EXTEND request. */ + * is a client or bridge relay. Connections with this flag set should never + * be used to satisfy an EXTEND request. */ unsigned int is_client:1; /** Set if the channel was initiated remotely (came from a listener) */ @@ -516,9 +602,7 @@ channel_t * channel_get_for_extend(const char *rsa_id_digest, int *launch_out); /* Ask which of two channels is better for circuit-extension purposes */ -int channel_is_better(time_t now, - channel_t *a, channel_t *b, - int forgive_new_connections); +int channel_is_better(channel_t *a, channel_t *b); /** Channel lookups */ @@ -601,6 +685,7 @@ void channel_listener_dump_statistics(channel_listener_t *chan_l, int severity); void channel_listener_dump_transport_statistics(channel_listener_t *chan_l, int severity); +void channel_check_for_duplicates(void); void channel_update_bad_for_new_circs(const char *digest, int force); @@ -630,5 +715,8 @@ int packed_cell_is_destroy(channel_t *chan, const packed_cell_t *packed_cell, circid_t *circid_out); +/* Declare the handle helpers */ +HANDLE_DECL(channel, channel_s,) + #endif diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c new file mode 100644 index 0000000000..ccaf5b4ec8 --- /dev/null +++ b/src/or/channelpadding.c @@ -0,0 +1,793 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/* TOR_CHANNEL_INTERNAL_ define needed for an O(1) implementation of + * channelpadding_channel_to_channelinfo() */ +#define TOR_CHANNEL_INTERNAL_ + +#include "or.h" +#include "channel.h" +#include "channelpadding.h" +#include "channeltls.h" +#include "config.h" +#include "networkstatus.h" +#include "connection.h" +#include "connection_or.h" +#include "main.h" +#include "rephist.h" +#include "router.h" +#include "compat_time.h" +#include <event2/event.h> +#include "rendservice.h" + +STATIC int channelpadding_get_netflow_inactive_timeout_ms(const channel_t *); +STATIC int channelpadding_send_disable_command(channel_t *); +STATIC int64_t channelpadding_compute_time_until_pad_for_netflow(channel_t *); + +/** The total number of pending channelpadding timers */ +static uint64_t total_timers_pending; + +/** These are cached consensus parameters for netflow */ +/** The timeout lower bound that is allowed before sending padding */ +static int consensus_nf_ito_low; +/** The timeout upper bound that is allowed before sending padding */ +static int consensus_nf_ito_high; +/** The timeout lower bound that is allowed before sending reduced padding */ +static int consensus_nf_ito_low_reduced; +/** The timeout upper bound that is allowed before sending reduced padding */ +static int consensus_nf_ito_high_reduced; +/** The connection timeout between relays */ +static int consensus_nf_conntimeout_relays; +/** The connection timeout for client connections */ +static int consensus_nf_conntimeout_clients; +/** Should we pad before circuits are actually used for client data? */ +static int consensus_nf_pad_before_usage; +/** Should we pad relay-to-relay connections? */ +static int consensus_nf_pad_relays; +/** Should we pad tor2web connections? */ +static int consensus_nf_pad_tor2web; +/** Should we pad rosos connections? */ +static int consensus_nf_pad_single_onion; + +#define TOR_MSEC_PER_SEC 1000 +#define TOR_USEC_PER_MSEC 1000 + +/** + * How often do we get called by the connection housekeeping (ie: once + * per second) */ +#define TOR_HOUSEKEEPING_CALLBACK_MSEC 1000 +/** + * Additional extra time buffer on the housekeeping callback, since + * it can be delayed. This extra slack is used to decide if we should + * schedule a timer or wait for the next callback. */ +#define TOR_HOUSEKEEPING_CALLBACK_SLACK_MSEC 100 + +/** + * This macro tells us if either end of the channel is connected to a client. + * (If we're not a server, we're definitely a client. If the channel thinks + * its a client, use that. Then finally verify in the consensus). + */ +#define CHANNEL_IS_CLIENT(chan, options) \ + (!public_server_mode((options)) || (chan)->is_client || \ + !connection_or_digest_is_known_relay((chan)->identity_digest)) + +/** + * This function is called to update cached consensus parameters every time + * there is a consensus update. This allows us to move the consensus param + * search off of the critical path, so it does not need to be evaluated + * for every single connection, every second. + */ +void +channelpadding_new_consensus_params(networkstatus_t *ns) +{ +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW 1500 +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH 9500 +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN 0 +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX 60000 + consensus_nf_ito_low = networkstatus_get_param(ns, "nf_ito_low", + DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW, + DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN, + DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX); + consensus_nf_ito_high = networkstatus_get_param(ns, "nf_ito_high", + DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH, + consensus_nf_ito_low, + DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX); + +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW 9000 +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH 14000 +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN 0 +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX 60000 + consensus_nf_ito_low_reduced = + networkstatus_get_param(ns, "nf_ito_low_reduced", + DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW, + DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN, + DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX); + + consensus_nf_ito_high_reduced = + networkstatus_get_param(ns, "nf_ito_high_reduced", + DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH, + consensus_nf_ito_low_reduced, + DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX); + +#define CONNTIMEOUT_RELAYS_DFLT (60*60) // 1 hour +#define CONNTIMEOUT_RELAYS_MIN 60 +#define CONNTIMEOUT_RELAYS_MAX (7*24*60*60) // 1 week + consensus_nf_conntimeout_relays = + networkstatus_get_param(ns, "nf_conntimeout_relays", + CONNTIMEOUT_RELAYS_DFLT, + CONNTIMEOUT_RELAYS_MIN, + CONNTIMEOUT_RELAYS_MAX); + +#define CIRCTIMEOUT_CLIENTS_DFLT (30*60) // 30 minutes +#define CIRCTIMEOUT_CLIENTS_MIN 60 +#define CIRCTIMEOUT_CLIENTS_MAX (24*60*60) // 24 hours + consensus_nf_conntimeout_clients = + networkstatus_get_param(ns, "nf_conntimeout_clients", + CIRCTIMEOUT_CLIENTS_DFLT, + CIRCTIMEOUT_CLIENTS_MIN, + CIRCTIMEOUT_CLIENTS_MAX); + + consensus_nf_pad_before_usage = + networkstatus_get_param(ns, "nf_pad_before_usage", 1, 0, 1); + + consensus_nf_pad_relays = + networkstatus_get_param(ns, "nf_pad_relays", 0, 0, 1); + + consensus_nf_pad_tor2web = + networkstatus_get_param(ns, + CHANNELPADDING_TOR2WEB_PARAM, + CHANNELPADDING_TOR2WEB_DEFAULT, 0, 1); + + consensus_nf_pad_single_onion = + networkstatus_get_param(ns, + CHANNELPADDING_SOS_PARAM, + CHANNELPADDING_SOS_DEFAULT, 0, 1); +} + +/** + * Get a random netflow inactive timeout keepalive period in milliseconds, + * the range for which is determined by consensus parameters, negotiation, + * configuration, or default values. The consensus parameters enforce the + * minimum possible value, to avoid excessively frequent padding. + * + * The ranges for this value were chosen to be low enough to ensure that + * routers do not emit a new netflow record for a connection due to it + * being idle. + * + * Specific timeout values for major routers are listed in Proposal 251. + * No major router appeared capable of setting an inactive timeout below 10 + * seconds, so we set the defaults below that value, since we can always + * scale back if it ends up being too much padding. + * + * Returns the next timeout period (in milliseconds) after which we should + * send a padding packet, or 0 if padding is disabled. + */ +STATIC int +channelpadding_get_netflow_inactive_timeout_ms(const channel_t *chan) +{ + int low_timeout = consensus_nf_ito_low; + int high_timeout = consensus_nf_ito_high; + int X1, X2; + + if (low_timeout == 0 && low_timeout == high_timeout) + return 0; // No padding + + /* If we have negotiated different timeout values, use those, but + * don't allow them to be lower than the consensus ones */ + if (chan->padding_timeout_low_ms && chan->padding_timeout_high_ms) { + low_timeout = MAX(low_timeout, chan->padding_timeout_low_ms); + high_timeout = MAX(high_timeout, chan->padding_timeout_high_ms); + } + + if (low_timeout == high_timeout) + return low_timeout; // No randomization + + /* + * This MAX() hack is here because we apply the timeout on both the client + * and the server. This creates the situation where the total time before + * sending a packet in either direction is actually + * min(client_timeout,server_timeout). + * + * If X is a random variable uniform from 0..R-1 (where R=high-low), + * then Y=max(X,X) has Prob(Y == i) = (2.0*i + 1)/(R*R). + * + * If we create a third random variable Z=min(Y,Y), then it turns out that + * Exp[Z] ~= Exp[X]. Here's a table: + * + * R Exp[X] Exp[Z] Exp[min(X,X)] Exp[max(X,X)] + * 2000 999.5 1066 666.2 1332.8 + * 3000 1499.5 1599.5 999.5 1999.5 + * 5000 2499.5 2666 1666.2 3332.8 + * 6000 2999.5 3199.5 1999.5 3999.5 + * 7000 3499.5 3732.8 2332.8 4666.2 + * 8000 3999.5 4266.2 2666.2 5332.8 + * 10000 4999.5 5328 3332.8 6666.2 + * 15000 7499.5 7995 4999.5 9999.5 + * 20000 9900.5 10661 6666.2 13332.8 + * + * In other words, this hack makes it so that when both the client and + * the guard are sending this padding, then the averages work out closer + * to the midpoint of the range, making the overhead easier to tune. + * If only one endpoint is padding (for example: if the relay does not + * support padding, but the client has set ConnectionPadding 1; or + * if the relay does support padding, but the client has set + * ReducedConnectionPadding 1), then the defense will still prevent + * record splitting, but with less overhead than the midpoint + * (as seen by the Exp[max(X,X)] column). + * + * To calculate average padding packet frequency (and thus overhead), + * index into the table by picking a row based on R = high-low. Then, + * use the appropriate column (Exp[Z] for two-sided padding, and + * Exp[max(X,X)] for one-sided padding). Finally, take this value + * and add it to the low timeout value. This value is the average + * frequency which padding packets will be sent. + */ + + X1 = crypto_rand_int(high_timeout - low_timeout); + X2 = crypto_rand_int(high_timeout - low_timeout); + return low_timeout + MAX(X1, X2); +} + +/** + * Update this channel's padding settings based on the PADDING_NEGOTIATE + * contents. + * + * Returns -1 on error; 1 on success. + */ +int +channelpadding_update_padding_for_channel(channel_t *chan, + const channelpadding_negotiate_t *pad_vars) +{ + if (pad_vars->version != 0) { + static ratelim_t version_limit = RATELIM_INIT(600); + + log_fn_ratelim(&version_limit,LOG_PROTOCOL_WARN,LD_PROTOCOL, + "Got a PADDING_NEGOTIATE cell with an unknown version. Ignoring."); + return -1; + } + + // We should not allow malicious relays to disable or reduce padding for + // us as clients. In fact, we should only accept this cell at all if we're + // operating as a relay. Bridges should not accept it from relays, either + // (only from their clients). + if ((get_options()->BridgeRelay && + connection_or_digest_is_known_relay(chan->identity_digest)) || + !get_options()->ORPort_set) { + static ratelim_t relay_limit = RATELIM_INIT(600); + + log_fn_ratelim(&relay_limit,LOG_PROTOCOL_WARN,LD_PROTOCOL, + "Got a PADDING_NEGOTIATE from relay at %s (%s). " + "This should not happen.", + chan->get_remote_descr(chan, 0), + hex_str(chan->identity_digest, DIGEST_LEN)); + return -1; + } + + chan->padding_enabled = (pad_vars->command == CHANNELPADDING_COMMAND_START); + + /* Min must not be lower than the current consensus parameter + nf_ito_low. */ + chan->padding_timeout_low_ms = MAX(consensus_nf_ito_low, + pad_vars->ito_low_ms); + + /* Max must not be lower than ito_low_ms */ + chan->padding_timeout_high_ms = MAX(chan->padding_timeout_low_ms, + pad_vars->ito_high_ms); + + log_fn(LOG_INFO,LD_OR, + "Negotiated padding=%d, lo=%d, hi=%d on "U64_FORMAT, + chan->padding_enabled, chan->padding_timeout_low_ms, + chan->padding_timeout_high_ms, + U64_PRINTF_ARG(chan->global_identifier)); + + return 1; +} + +/** + * Sends a CELL_PADDING_NEGOTIATE on the channel to tell the other side not + * to send padding. + * + * Returns -1 on error, 0 on success. + */ +STATIC int +channelpadding_send_disable_command(channel_t *chan) +{ + channelpadding_negotiate_t disable; + cell_t cell; + + tor_assert(BASE_CHAN_TO_TLS(chan)->conn->link_proto >= + MIN_LINK_PROTO_FOR_CHANNEL_PADDING); + + memset(&cell, 0, sizeof(cell_t)); + memset(&disable, 0, sizeof(channelpadding_negotiate_t)); + cell.command = CELL_PADDING_NEGOTIATE; + + channelpadding_negotiate_set_command(&disable, CHANNELPADDING_COMMAND_STOP); + + if (channelpadding_negotiate_encode(cell.payload, CELL_PAYLOAD_SIZE, + &disable) < 0) + return -1; + + if (chan->write_cell(chan, &cell) == 1) + return 0; + else + return -1; +} + +/** + * Sends a CELL_PADDING_NEGOTIATE on the channel to tell the other side to + * resume sending padding at some rate. + * + * Returns -1 on error, 0 on success. + */ +int +channelpadding_send_enable_command(channel_t *chan, uint16_t low_timeout, + uint16_t high_timeout) +{ + channelpadding_negotiate_t enable; + cell_t cell; + + tor_assert(BASE_CHAN_TO_TLS(chan)->conn->link_proto >= + MIN_LINK_PROTO_FOR_CHANNEL_PADDING); + + memset(&cell, 0, sizeof(cell_t)); + memset(&enable, 0, sizeof(channelpadding_negotiate_t)); + cell.command = CELL_PADDING_NEGOTIATE; + + channelpadding_negotiate_set_command(&enable, CHANNELPADDING_COMMAND_START); + channelpadding_negotiate_set_ito_low_ms(&enable, low_timeout); + channelpadding_negotiate_set_ito_high_ms(&enable, high_timeout); + + if (channelpadding_negotiate_encode(cell.payload, CELL_PAYLOAD_SIZE, + &enable) < 0) + return -1; + + if (chan->write_cell(chan, &cell) == 1) + return 0; + else + return -1; +} + +/** + * Sends a CELL_PADDING cell on a channel if it has been idle since + * our callback was scheduled. + * + * This function also clears the pending padding timer and the callback + * flags. + */ +static void +channelpadding_send_padding_cell_for_callback(channel_t *chan) +{ + cell_t cell; + + /* Check that the channel is still valid and open */ + if (!chan || chan->state != CHANNEL_STATE_OPEN) { + if (chan) chan->pending_padding_callback = 0; + log_fn(LOG_INFO,LD_OR, + "Scheduled a netflow padding cell, but connection already closed."); + return; + } + + /* We should have a pending callback flag set. */ + if (BUG(chan->pending_padding_callback == 0)) + return; + + chan->pending_padding_callback = 0; + + if (!chan->next_padding_time_ms || + chan->has_queued_writes(chan)) { + /* We must have been active before the timer fired */ + chan->next_padding_time_ms = 0; + return; + } + + { + uint64_t now = monotime_coarse_absolute_msec(); + + log_fn(LOG_INFO,LD_OR, + "Sending netflow keepalive on "U64_FORMAT" to %s (%s) after " + I64_FORMAT" ms. Delta "I64_FORMAT"ms", + U64_PRINTF_ARG(chan->global_identifier), + safe_str_client(chan->get_remote_descr(chan, 0)), + safe_str_client(hex_str(chan->identity_digest, DIGEST_LEN)), + U64_PRINTF_ARG(now - chan->timestamp_xfer_ms), + U64_PRINTF_ARG(now - chan->next_padding_time_ms)); + } + + /* Clear the timer */ + chan->next_padding_time_ms = 0; + + /* Send the padding cell. This will cause the channel to get a + * fresh timestamp_active */ + memset(&cell, 0, sizeof(cell)); + cell.command = CELL_PADDING; + chan->write_cell(chan, &cell); +} + +/** + * tor_timer callback function for us to send padding on an idle channel. + * + * This function just obtains the channel from the callback handle, ensures + * it is still valid, and then hands it off to + * channelpadding_send_padding_cell_for_callback(), which checks if + * the channel is still idle before sending padding. + */ +static void +channelpadding_send_padding_callback(tor_timer_t *timer, void *args, + const struct monotime_t *when) +{ + channel_t *chan = channel_handle_get((struct channel_handle_t*)args); + (void)timer; (void)when; + + if (chan && CHANNEL_CAN_HANDLE_CELLS(chan)) { + /* Hrmm.. It might be nice to have an equivalent to assert_connection_ok + * for channels. Then we could get rid of the channeltls dependency */ + tor_assert(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn)->magic == + OR_CONNECTION_MAGIC); + assert_connection_ok(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn), approx_time()); + + channelpadding_send_padding_cell_for_callback(chan); + } else { + log_fn(LOG_INFO,LD_OR, + "Channel closed while waiting for timer."); + } + + total_timers_pending--; +} + +/** + * Schedules a callback to send padding on a channel in_ms milliseconds from + * now. + * + * Returns CHANNELPADDING_WONTPAD on error, CHANNELPADDING_PADDING_SENT if we + * sent the packet immediately without a timer, and + * CHANNELPADDING_PADDING_SCHEDULED if we decided to schedule a timer. + */ +static channelpadding_decision_t +channelpadding_schedule_padding(channel_t *chan, int in_ms) +{ + struct timeval timeout; + tor_assert(!chan->pending_padding_callback); + + if (in_ms <= 0) { + chan->pending_padding_callback = 1; + channelpadding_send_padding_cell_for_callback(chan); + return CHANNELPADDING_PADDING_SENT; + } + + timeout.tv_sec = in_ms/TOR_MSEC_PER_SEC; + timeout.tv_usec = (in_ms%TOR_USEC_PER_MSEC)*TOR_USEC_PER_MSEC; + + if (!chan->timer_handle) { + chan->timer_handle = channel_handle_new(chan); + } + + if (chan->padding_timer) { + timer_set_cb(chan->padding_timer, + channelpadding_send_padding_callback, + chan->timer_handle); + } else { + chan->padding_timer = timer_new(channelpadding_send_padding_callback, + chan->timer_handle); + } + timer_schedule(chan->padding_timer, &timeout); + + rep_hist_padding_count_timers(++total_timers_pending); + + chan->pending_padding_callback = 1; + return CHANNELPADDING_PADDING_SCHEDULED; +} + +/** + * Calculates the number of milliseconds from now to schedule a padding cell. + * + * Returns the number of milliseconds from now (relative) to schedule the + * padding callback. If the padding timer is more than 1.1 seconds in the + * future, we return -1, to avoid scheduling excessive callbacks. If padding + * is disabled in the consensus, we return -2. + * + * Side-effects: Updates chan->next_padding_time_ms, storing an (absolute, not + * relative) millisecond representation of when we should send padding, unless + * other activity happens first. This side-effect allows us to avoid + * scheduling a libevent callback until we're within 1.1 seconds of the padding + * time. + */ +#define CHANNELPADDING_TIME_LATER -1 +#define CHANNELPADDING_TIME_DISABLED -2 +STATIC int64_t +channelpadding_compute_time_until_pad_for_netflow(channel_t *chan) +{ + uint64_t long_now = monotime_coarse_absolute_msec(); + + if (!chan->next_padding_time_ms) { + /* If the below line or crypto_rand_int() shows up on a profile, + * we can avoid getting a timeout until we're at least nf_ito_lo + * from a timeout window. That will prevent us from setting timers + * on connections that were active up to 1.5 seconds ago. + * Idle connections should only call this once every 5.5s on average + * though, so that might be a micro-optimization for little gain. */ + int64_t padding_timeout = + channelpadding_get_netflow_inactive_timeout_ms(chan); + + if (!padding_timeout) + return CHANNELPADDING_TIME_DISABLED; + + chan->next_padding_time_ms = padding_timeout + + chan->timestamp_xfer_ms; + } + + /* If the next padding time is beyond the maximum possible consensus value, + * then this indicates a clock jump, so just send padding now. This is + * better than using monotonic time because we want to avoid the situation + * where we wait around forever for monotonic time to move forward after + * a clock jump far into the past. + */ + if (chan->next_padding_time_ms > long_now + + DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX) { + tor_fragile_assert(); + log_warn(LD_BUG, + "Channel padding timeout scheduled "I64_FORMAT"ms in the future. " + "Did the monotonic clock just jump?", + I64_PRINTF_ARG(chan->next_padding_time_ms - long_now)); + return 0; /* Clock jumped: Send padding now */ + } + + /* If the timeout will expire before the next time we're called (1000ms + from now, plus some slack), then calculate the number of milliseconds + from now which we should send padding, so we can schedule a callback + then. + */ + if (long_now + + (TOR_HOUSEKEEPING_CALLBACK_MSEC + TOR_HOUSEKEEPING_CALLBACK_SLACK_MSEC) + >= chan->next_padding_time_ms) { + int64_t ms_until_pad_for_netflow = chan->next_padding_time_ms - + long_now; + /* If the padding time is in the past, that means that libevent delayed + * calling the once-per-second callback due to other work taking too long. + * See https://bugs.torproject.org/22212 and + * https://bugs.torproject.org/16585. This is a systemic problem + * with being single-threaded, but let's emit a notice if this + * is long enough in the past that we might have missed a netflow window, + * and allowed a router to emit a netflow frame, just so we don't forget + * about it entirely.. */ +#define NETFLOW_MISSED_WINDOW (150000 - DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH) + if (ms_until_pad_for_netflow < 0) { + int severity = (ms_until_pad_for_netflow < -NETFLOW_MISSED_WINDOW) + ? LOG_NOTICE : LOG_INFO; + log_fn(severity, LD_OR, + "Channel padding timeout scheduled "I64_FORMAT"ms in the past. ", + I64_PRINTF_ARG(-ms_until_pad_for_netflow)); + return 0; /* Clock jumped: Send padding now */ + } + + return ms_until_pad_for_netflow; + } + return CHANNELPADDING_TIME_LATER; +} + +/** + * Returns a randomized value for channel idle timeout in seconds. + * The channel idle timeout governs how quickly we close a channel + * after its last circuit has disappeared. + * + * There are three classes of channels: + * 1. Client+non-canonical. These live for 3-4.5 minutes + * 2. relay to relay. These live for 45-75 min by default + * 3. Reduced padding clients. These live for 1.5-2.25 minutes. + * + * Also allows the default relay-to-relay value to be controlled by the + * consensus. + */ +unsigned int +channelpadding_get_channel_idle_timeout(const channel_t *chan, + int is_canonical) +{ + const or_options_t *options = get_options(); + unsigned int timeout; + + /* Non-canonical and client channels only last for 3-4.5 min when idle */ + if (!is_canonical || CHANNEL_IS_CLIENT(chan, options)) { +#define CONNTIMEOUT_CLIENTS_BASE 180 // 3 to 4.5 min + timeout = CONNTIMEOUT_CLIENTS_BASE + + crypto_rand_int(CONNTIMEOUT_CLIENTS_BASE/2); + } else { // Canonical relay-to-relay channels + // 45..75min or consensus +/- 25% + timeout = consensus_nf_conntimeout_relays; + timeout = 3*timeout/4 + crypto_rand_int(timeout/2); + } + + /* If ReducedConnectionPadding is set, we want to halve the duration of + * the channel idle timeout, since reducing the additional time that + * a channel stays open will reduce the total overhead for making + * new channels. This reduction in overhead/channel expense + * is important for mobile users. The option cannot be set by relays. + * + * We also don't reduce any values for timeout that the user explicitly + * set. + */ + if (options->ReducedConnectionPadding + && !options->CircuitsAvailableTimeout) { + timeout /= 2; + } + + return timeout; +} + +/** + * This function controls how long we keep idle circuits open, + * and how long we build predicted circuits. This behavior is under + * the control of channelpadding because circuit availability is the + * dominant factor in channel lifespan, which influences total padding + * overhead. + * + * Returns a randomized number of seconds in a range from + * CircuitsAvailableTimeout to 2*CircuitsAvailableTimeout. This value is halved + * if ReducedConnectionPadding is set. The default value of + * CircuitsAvailableTimeout can be controlled by the consensus. + */ +int +channelpadding_get_circuits_available_timeout(void) +{ + const or_options_t *options = get_options(); + int timeout = options->CircuitsAvailableTimeout; + + if (!timeout) { + timeout = consensus_nf_conntimeout_clients; + + /* If ReducedConnectionPadding is set, we want to halve the duration of + * the channel idle timeout, since reducing the additional time that + * a channel stays open will reduce the total overhead for making + * new connections. This reduction in overhead/connection expense + * is important for mobile users. The option cannot be set by relays. + * + * We also don't reduce any values for timeout that the user explicitly + * set. + */ + if (options->ReducedConnectionPadding) { + // half the value to 15..30min by default + timeout /= 2; + } + } + + // 30..60min by default + timeout = timeout + crypto_rand_int(timeout); + + return timeout; +} + +/** + * Calling this function on a channel causes it to tell the other side + * not to send padding, and disables sending padding from this side as well. + */ +void +channelpadding_disable_padding_on_channel(channel_t *chan) +{ + chan->padding_enabled = 0; + + // Send cell to disable padding on the other end + channelpadding_send_disable_command(chan); +} + +/** + * Calling this function on a channel causes it to tell the other side + * not to send padding, and reduces the rate that padding is sent from + * this side. + */ +void +channelpadding_reduce_padding_on_channel(channel_t *chan) +{ + /* Padding can be forced and reduced by clients, regardless of if + * the channel supports it. So we check for support here before + * sending any commands. */ + if (chan->padding_enabled) { + channelpadding_send_disable_command(chan); + } + + chan->padding_timeout_low_ms = consensus_nf_ito_low_reduced; + chan->padding_timeout_high_ms = consensus_nf_ito_high_reduced; + + log_fn(LOG_INFO,LD_OR, + "Reduced padding on channel "U64_FORMAT": lo=%d, hi=%d", + U64_PRINTF_ARG(chan->global_identifier), + chan->padding_timeout_low_ms, chan->padding_timeout_high_ms); +} + +/** + * This function is called once per second by run_connection_housekeeping(), + * but only if the channel is still open, valid, and non-wedged. + * + * It decides if and when we should send a padding cell, and if needed, + * schedules a callback to send that cell at the appropriate time. + * + * Returns an enum that represents the current padding decision state. + * Return value is currently used only by unit tests. + */ +channelpadding_decision_t +channelpadding_decide_to_pad_channel(channel_t *chan) +{ + const or_options_t *options = get_options(); + + /* Only pad open channels */ + if (chan->state != CHANNEL_STATE_OPEN) + return CHANNELPADDING_WONTPAD; + + if (chan->channel_usage == CHANNEL_USED_FOR_FULL_CIRCS) { + if (!consensus_nf_pad_before_usage) + return CHANNELPADDING_WONTPAD; + } else if (chan->channel_usage != CHANNEL_USED_FOR_USER_TRAFFIC) { + return CHANNELPADDING_WONTPAD; + } + + if (chan->pending_padding_callback) + return CHANNELPADDING_PADDING_ALREADY_SCHEDULED; + + /* Don't pad the channel if we didn't negotiate it, but still + * allow clients to force padding if options->ChannelPadding is + * explicitly set to 1. + */ + if (!chan->padding_enabled && options->ConnectionPadding != 1) { + return CHANNELPADDING_WONTPAD; + } + + if (options->Tor2webMode && !consensus_nf_pad_tor2web) { + /* If the consensus just changed values, this channel may still + * think padding is enabled. Negotiate it off. */ + if (chan->padding_enabled) + channelpadding_disable_padding_on_channel(chan); + + return CHANNELPADDING_WONTPAD; + } + + if (rend_service_allow_non_anonymous_connection(options) && + !consensus_nf_pad_single_onion) { + /* If the consensus just changed values, this channel may still + * think padding is enabled. Negotiate it off. */ + if (chan->padding_enabled) + channelpadding_disable_padding_on_channel(chan); + + return CHANNELPADDING_WONTPAD; + } + + if (!chan->has_queued_writes(chan)) { + int is_client_channel = 0; + + if (CHANNEL_IS_CLIENT(chan, options)) { + is_client_channel = 1; + } + + /* If nf_pad_relays=1 is set in the consensus, we pad + * on *all* idle connections, relay-relay or relay-client. + * Otherwise pad only for client+bridge cons */ + if (is_client_channel || consensus_nf_pad_relays) { + int64_t pad_time_ms = + channelpadding_compute_time_until_pad_for_netflow(chan); + + if (pad_time_ms == CHANNELPADDING_TIME_DISABLED) { + return CHANNELPADDING_WONTPAD; + } else if (pad_time_ms == CHANNELPADDING_TIME_LATER) { + chan->currently_padding = 1; + return CHANNELPADDING_PADLATER; + } else { + if (BUG(pad_time_ms > INT_MAX)) { + pad_time_ms = INT_MAX; + } + /* We have to schedule a callback because we're called exactly once per + * second, but we don't want padding packets to go out exactly on an + * integer multiple of seconds. This callback will only be scheduled + * if we're within 1.1 seconds of the padding time. + */ + chan->currently_padding = 1; + return channelpadding_schedule_padding(chan, (int)pad_time_ms); + } + } else { + chan->currently_padding = 0; + return CHANNELPADDING_WONTPAD; + } + } else { + return CHANNELPADDING_PADLATER; + } +} + diff --git a/src/or/channelpadding.h b/src/or/channelpadding.h new file mode 100644 index 0000000000..a227e27d5b --- /dev/null +++ b/src/or/channelpadding.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitbuild.h + * \brief Header file for circuitbuild.c. + **/ +#ifndef TOR_CHANNELPADDING_H +#define TOR_CHANNELPADDING_H + +#include "channelpadding_negotiation.h" + +#define CHANNELPADDING_TOR2WEB_PARAM "nf_pad_tor2web" +#define CHANNELPADDING_TOR2WEB_DEFAULT 1 +#define CHANNELPADDING_SOS_PARAM "nf_pad_single_onion" +#define CHANNELPADDING_SOS_DEFAULT 1 + +typedef enum { + CHANNELPADDING_WONTPAD, + CHANNELPADDING_PADLATER, + CHANNELPADDING_PADDING_SCHEDULED, + CHANNELPADDING_PADDING_ALREADY_SCHEDULED, + CHANNELPADDING_PADDING_SENT, +} channelpadding_decision_t; + +channelpadding_decision_t channelpadding_decide_to_pad_channel(channel_t + *chan); +int channelpadding_update_padding_for_channel(channel_t *, + const channelpadding_negotiate_t + *chan); + +void channelpadding_disable_padding_on_channel(channel_t *chan); +void channelpadding_reduce_padding_on_channel(channel_t *chan); +int channelpadding_send_enable_command(channel_t *chan, uint16_t low_timeout, + uint16_t high_timeout); + +int channelpadding_get_circuits_available_timeout(void); +unsigned int channelpadding_get_channel_idle_timeout(const channel_t *, int); +void channelpadding_new_consensus_params(networkstatus_t *ns); + +#endif + diff --git a/src/or/channeltls.c b/src/or/channeltls.c index dbed95fb43..c6a0bb21e9 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -57,6 +57,9 @@ #include "routerlist.h" #include "scheduler.h" #include "torcert.h" +#include "networkstatus.h" +#include "channelpadding_negotiation.h" +#include "channelpadding.h" /** How many CELL_PADDING cells have we received, ever? */ uint64_t stats_n_padding_cells_processed = 0; @@ -122,6 +125,8 @@ static void channel_tls_process_netinfo_cell(cell_t *cell, static int command_allowed_before_handshake(uint8_t command); static int enter_v3_handshake_with_cell(var_cell_t *cell, channel_tls_t *tlschan); +static void channel_tls_process_padding_negotiate_cell(cell_t *cell, + channel_tls_t *chan); /** * Do parts of channel_tls_t initialization common to channel_tls_connect() @@ -734,6 +739,15 @@ channel_tls_matches_target_method(channel_t *chan, return 0; } + /* real_addr is the address this connection came from. + * base_.addr is updated by connection_or_init_conn_from_address() + * to be the address in the descriptor. It may be tempting to + * allow either address to be allowed, but if we did so, it would + * enable someone who steals a relay's keys to impersonate/MITM it + * from anywhere on the Internet! (Because they could make long-lived + * TLS connections from anywhere to all relays, and wait for them to + * be used for extends). + */ return tor_addr_eq(&(tlschan->conn->real_addr), target); } @@ -1098,9 +1112,16 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn) /* We note that we're on the internet whenever we read a cell. This is * a fast operation. */ entry_guards_note_internet_connectivity(get_guard_selection_info()); + rep_hist_padding_count_read(PADDING_TYPE_TOTAL); + + if (TLS_CHAN_TO_BASE(chan)->currently_padding) + rep_hist_padding_count_read(PADDING_TYPE_ENABLED_TOTAL); switch (cell->command) { case CELL_PADDING: + rep_hist_padding_count_read(PADDING_TYPE_CELL); + if (TLS_CHAN_TO_BASE(chan)->currently_padding) + rep_hist_padding_count_read(PADDING_TYPE_ENABLED_CELL); ++stats_n_padding_cells_processed; /* do nothing */ break; @@ -1111,6 +1132,10 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn) ++stats_n_netinfo_cells_processed; PROCESS_CELL(netinfo, cell, chan); break; + case CELL_PADDING_NEGOTIATE: + ++stats_n_netinfo_cells_processed; + PROCESS_CELL(padding_negotiate, cell, chan); + break; case CELL_CREATE: case CELL_CREATE_FAST: case CELL_CREATED: @@ -1566,9 +1591,12 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) /* We set this after sending the verions cell. */ /*XXXXX symbolic const.*/ - chan->base_.wide_circ_ids = + TLS_CHAN_TO_BASE(chan)->wide_circ_ids = chan->conn->link_proto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; - chan->conn->wide_circ_ids = chan->base_.wide_circ_ids; + chan->conn->wide_circ_ids = TLS_CHAN_TO_BASE(chan)->wide_circ_ids; + + TLS_CHAN_TO_BASE(chan)->padding_enabled = + chan->conn->link_proto >= MIN_LINK_PROTO_FOR_CHANNEL_PADDING; if (send_certs) { if (connection_or_send_certs_cell(chan->conn) < 0) { @@ -1595,6 +1623,43 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) } /** + * Process a 'padding_negotiate' cell + * + * This function is called to handle an incoming PADDING_NEGOTIATE cell; + * enable or disable padding accordingly, and read and act on its timeout + * value contents. + */ +static void +channel_tls_process_padding_negotiate_cell(cell_t *cell, channel_tls_t *chan) +{ + channelpadding_negotiate_t *negotiation; + tor_assert(cell); + tor_assert(chan); + tor_assert(chan->conn); + + if (chan->conn->link_proto < MIN_LINK_PROTO_FOR_CHANNEL_PADDING) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received a PADDING_NEGOTIATE cell on v%d connection; dropping.", + chan->conn->link_proto); + return; + } + + if (channelpadding_negotiate_parse(&negotiation, cell->payload, + CELL_PAYLOAD_SIZE) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received malformed PADDING_NEGOTIATE cell on v%d connection; " + "dropping.", chan->conn->link_proto); + + return; + } + + channelpadding_update_padding_for_channel(TLS_CHAN_TO_BASE(chan), + negotiation); + + channelpadding_negotiate_free(negotiation); +} + +/** * Process a 'netinfo' cell * * This function is called to handle an incoming NETINFO cell; read and act @@ -1611,6 +1676,7 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) const uint8_t *cp, *end; uint8_t n_other_addrs; time_t now = time(NULL); + const routerinfo_t *me = router_get_my_routerinfo(); long apparent_skew = 0; tor_addr_t my_apparent_addr = TOR_ADDR_NULL; @@ -1654,6 +1720,10 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) tor_assert(tor_mem_is_zero( (const char*)(chan->conn->handshake_state-> authenticated_ed25519_peer_id.pubkey), 32)); + /* If the client never authenticated, it's a tor client or bridge + * relay, and we must not use it for EXTEND requests (nor could we, as + * there are no authenticated peer IDs) */ + channel_mark_client(TLS_CHAN_TO_BASE(chan)); channel_set_circid_type(TLS_CHAN_TO_BASE(chan), NULL, chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS); @@ -1689,8 +1759,20 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { tor_addr_from_ipv4n(&my_apparent_addr, get_uint32(my_addr_ptr)); + + if (!get_options()->BridgeRelay && me && + get_uint32(my_addr_ptr) == htonl(me->addr)) { + TLS_CHAN_TO_BASE(chan)->is_canonical_to_peer = 1; + } + } else if (my_addr_type == RESOLVED_TYPE_IPV6 && my_addr_len == 16) { tor_addr_from_ipv6_bytes(&my_apparent_addr, (const char *) my_addr_ptr); + + if (!get_options()->BridgeRelay && me && + !tor_addr_is_null(&me->ipv6_addr) && + tor_addr_eq(&my_apparent_addr, &me->ipv6_addr)) { + TLS_CHAN_TO_BASE(chan)->is_canonical_to_peer = 1; + } } n_other_addrs = (uint8_t) *cp++; @@ -1706,6 +1788,14 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) connection_or_close_for_error(chan->conn, 0); return; } + /* A relay can connect from anywhere and be canonical, so + * long as it tells you from where it came. This may be a bit + * concerning.. Luckily we have another check in + * channel_tls_matches_target_method() to ensure that extends + * only go to the IP they ask for. + * + * XXX: Bleh. That check is not used if the connection is canonical. + */ if (tor_addr_eq(&addr, &(chan->conn->real_addr))) { connection_or_set_canonical(chan->conn, 1); break; @@ -1714,11 +1804,26 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) --n_other_addrs; } + if (me && !TLS_CHAN_TO_BASE(chan)->is_canonical_to_peer && + channel_is_canonical(TLS_CHAN_TO_BASE(chan))) { + const char *descr = + TLS_CHAN_TO_BASE(chan)->get_remote_descr(TLS_CHAN_TO_BASE(chan), 0); + log_info(LD_OR, + "We made a connection to a relay at %s (fp=%s) but we think " + "they will not consider this connection canonical. They " + "think we are at %s, but we think its %s.", + safe_str(descr), + safe_str(hex_str(chan->conn->identity_digest, DIGEST_LEN)), + safe_str(tor_addr_is_null(&my_apparent_addr) ? + "<none>" : fmt_and_decorate_addr(&my_apparent_addr)), + safe_str(fmt_addr32(me->addr))); + } + /* Act on apparent skew. */ /** Warn when we get a netinfo skew with at least this value. */ #define NETINFO_NOTICE_SKEW 3600 if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && - router_get_by_id_digest(chan->conn->identity_digest)) { + connection_or_digest_is_known_relay(chan->conn->identity_digest)) { int trusted = router_digest_is_trusted_dir(chan->conn->identity_digest); clock_skew_warning(TO_CONN(chan->conn), apparent_skew, trusted, LD_GENERAL, "NETINFO cell", "OR"); diff --git a/src/or/channeltls.h b/src/or/channeltls.h index 729e595615..1f9a39d8a4 100644 --- a/src/or/channeltls.h +++ b/src/or/channeltls.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index cdcb6deae4..4c0bd9e455 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circpathbias.h b/src/or/circpathbias.h index ce76689d5f..2a4c316807 100644 --- a/src/or/circpathbias.h +++ b/src/or/circpathbias.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 79962e8dbb..16cef0e56b 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -73,7 +73,6 @@ static int circuit_deliver_create_cell(circuit_t *circ, static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); -static int count_acceptable_nodes(smartlist_t *routers); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); /** This function tries to get a channel to the specified endpoint, @@ -817,12 +816,7 @@ should_use_create_fast_for_circuit(origin_circuit_t *circ) * creating on behalf of others. */ return 0; } - if (options->FastFirstHopPK == -1) { - /* option is "auto", so look at the consensus. */ - return networkstatus_get_param(NULL, "usecreatefast", 1, 0, 1); - } - - return options->FastFirstHopPK; + return networkstatus_get_param(NULL, "usecreatefast", 0, 0, 1); } /** Return true if <b>circ</b> is the type of circuit we want to count @@ -940,9 +934,18 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) memset(&cc, 0, sizeof(cc)); if (circ->build_state->onehop_tunnel) control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); - else + else { control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); + /* If this is not a one-hop tunnel, the channel is being used + * for traffic that wants anonymity and protection from traffic + * analysis (such as netflow record retention). That means we want + * to pad it. + */ + if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) + circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; + } + node = node_get_by_id(circ->base_.n_chan->identity_digest); fast = should_use_create_fast_for_circuit(circ); if (!fast) { @@ -1546,13 +1549,98 @@ onionskin_answer(or_circuit_t *circ, return 0; } -/** Choose a length for a circuit of purpose <b>purpose</b>: three + the - * number of endpoints that would give something away about our destination. +/** Helper for new_route_len(). Choose a circuit length for purpose + * <b>purpose</b>: DEFAULT_ROUTE_LEN (+ 1 if someone else chose the + * exit). If someone else chose the exit, they could be colluding + * with the exit, so add a randomly selected node to preserve + * anonymity. + * + * Here, "exit node" sometimes means an OR acting as an internal + * endpoint, rather than as a relay to an external endpoint. This + * means there need to be at least DEFAULT_ROUTE_LEN routers between + * us and the internal endpoint to preserve the same anonymity + * properties that we would get when connecting to an external + * endpoint. These internal endpoints can include: + * + * - Connections to a directory of hidden services + * (CIRCUIT_PURPOSE_C_GENERAL) + * + * - A client connecting to an introduction point, which the hidden + * service picked (CIRCUIT_PURPOSE_C_INTRODUCING, via + * circuit_get_open_circ_or_launch() which rewrites it from + * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) + * + * - A hidden service connecting to a rendezvous point, which the + * client picked (CIRCUIT_PURPOSE_S_CONNECT_REND, via + * rend_service_receive_introduction() and + * rend_service_relaunch_rendezvous) + * + * There are currently two situations where we picked the exit node + * ourselves, making DEFAULT_ROUTE_LEN a safe circuit length: + * + * - We are a hidden service connecting to an introduction point + * (CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, via + * rend_service_launch_establish_intro()) + * + * - We are a router testing its own reachabiity + * (CIRCUIT_PURPOSE_TESTING, via consider_testing_reachability()) + * + * onion_pick_cpath_exit() bypasses us (by not calling + * new_route_len()) in the one-hop tunnel case, so we don't need to + * handle that. + */ +static int +route_len_for_purpose(uint8_t purpose, extend_info_t *exit_ei) +{ + int routelen = DEFAULT_ROUTE_LEN; + int known_purpose = 0; + + if (!exit_ei) + return routelen; + + switch (purpose) { + /* These two purposes connect to a router that we chose, so + * DEFAULT_ROUTE_LEN is safe. */ + case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: + /* hidden service connecting to introduction point */ + case CIRCUIT_PURPOSE_TESTING: + /* router reachability testing */ + known_purpose = 1; + break; + + /* These three purposes connect to a router that someone else + * might have chosen, so add an extra hop to protect anonymity. */ + case CIRCUIT_PURPOSE_C_GENERAL: + /* connecting to hidden service directory */ + case CIRCUIT_PURPOSE_C_INTRODUCING: + /* client connecting to introduction point */ + case CIRCUIT_PURPOSE_S_CONNECT_REND: + /* hidden service connecting to rendezvous point */ + known_purpose = 1; + routelen++; + break; + + default: + /* Got a purpose not listed above along with a chosen exit. + * Increase the circuit length by one anyway for safety. */ + routelen++; + break; + } + + if (BUG(exit_ei && !known_purpose)) { + log_warn(LD_BUG, "Unhandled purpose %d with a chosen exit; " + "assuming routelen %d.", purpose, routelen); + } + return routelen; +} + +/** Choose a length for a circuit of purpose <b>purpose</b> and check + * if enough routers are available. * * If the routerlist <b>nodes</b> doesn't have enough routers * to handle the desired path length, return -1. */ -static int +STATIC int new_route_len(uint8_t purpose, extend_info_t *exit_ei, smartlist_t *nodes) { int num_acceptable_routers; @@ -1560,11 +1648,7 @@ new_route_len(uint8_t purpose, extend_info_t *exit_ei, smartlist_t *nodes) tor_assert(nodes); - routelen = DEFAULT_ROUTE_LEN; - if (exit_ei && - purpose != CIRCUIT_PURPOSE_TESTING && - purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) - routelen++; + routelen = route_len_for_purpose(purpose, exit_ei); num_acceptable_routers = count_acceptable_nodes(nodes); @@ -1748,15 +1832,16 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) * we'll retry later in this function with need_update and * need_capacity set to 0. */ } - if (!(node->is_valid || options->AllowInvalid_ & ALLOW_INVALID_EXIT)) { + if (!(node->is_valid)) { /* if it's invalid and we don't want it */ n_supported[i] = -1; // log_fn(LOG_DEBUG,"Skipping node %s (index %d) -- invalid router.", // router->nickname, i); continue; /* skip invalid routers */ } - if (options->ExcludeSingleHopRelays && - node_allows_single_hop_exits(node)) { + /* We do not allow relays that allow single hop exits by default. Option + * was deprecated in 0.2.9.2-alpha and removed in 0.3.1.0-alpha. */ + if (node_allows_single_hop_exits(node)) { n_supported[i] = -1; continue; } @@ -1888,7 +1973,6 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags, const or_options_t *options) { const node_t *rp_node = NULL; - const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0; const int need_desc = (flags & CRN_NEED_DESC) != 0; const int pref_addr = (flags & CRN_PREF_ADDR) != 0; const int direct_conn = (flags & CRN_DIRECT_CONN) != 0; @@ -1900,7 +1984,6 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags, /* Add all running nodes to all_live_nodes */ router_add_running_nodes_to_smartlist(all_live_nodes, - allow_invalid, 0, 0, 0, need_desc, pref_addr, @@ -1942,9 +2025,6 @@ pick_rendezvous_node(router_crn_flags_t flags) { const or_options_t *options = get_options(); - if (options->AllowInvalid_ & ALLOW_INVALID_RENDEZVOUS) - flags |= CRN_ALLOW_INVALID; - #ifdef ENABLE_TOR2WEB_MODE /* We want to connect directly to the node if we can */ router_crn_flags_t direct_flags = flags; @@ -2001,8 +2081,6 @@ choose_good_exit_server(uint8_t purpose, switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: - if (options->AllowInvalid_ & ALLOW_INVALID_MIDDLE) - flags |= CRN_ALLOW_INVALID; if (is_internal) /* pick it like a middle hop */ return router_choose_random_node(NULL, options->ExcludeNodes, flags); else @@ -2188,8 +2266,8 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit_ei) /** Return the number of routers in <b>routers</b> that are currently up * and available for building circuits through. */ -static int -count_acceptable_nodes(smartlist_t *nodes) +MOCK_IMPL(STATIC int, +count_acceptable_nodes, (smartlist_t *nodes)) { int num=0; @@ -2200,10 +2278,6 @@ count_acceptable_nodes(smartlist_t *nodes) if (! node->is_running) // log_debug(LD_CIRC,"Nope, the directory says %d is not running.",i); continue; - /* XXX This clause makes us count incorrectly: if AllowInvalidRouters - * allows this node in some places, then we're getting an inaccurate - * count. For now, be conservative and don't count it. But later we - * should try to be smarter. */ if (! node->is_valid) // log_debug(LD_CIRC,"Nope, the directory says %d is not valid.",i); continue; @@ -2274,8 +2348,6 @@ choose_good_middle_server(uint8_t purpose, flags |= CRN_NEED_UPTIME; if (state->need_capacity) flags |= CRN_NEED_CAPACITY; - if (options->AllowInvalid_ & ALLOW_INVALID_MIDDLE) - flags |= CRN_ALLOW_INVALID; choice = router_choose_random_node(excluded, options->ExcludeNodes, flags); smartlist_free(excluded); return choice; @@ -2328,8 +2400,6 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, if (state->need_capacity) flags |= CRN_NEED_CAPACITY; } - if (options->AllowInvalid_ & ALLOW_INVALID_ENTRY) - flags |= CRN_ALLOW_INVALID; choice = router_choose_random_node(excluded, options->ExcludeNodes, flags); smartlist_free(excluded); diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index ddb070b427..45d9b2fb75 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -77,6 +77,9 @@ void circuit_upgrade_circuits_from_guard_wait(void); #ifdef CIRCUITBUILD_PRIVATE STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan); +STATIC int new_route_len(uint8_t purpose, extend_info_t *exit_ei, + smartlist_t *nodes); +MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes)); #if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags, const or_options_t *options); diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 8d6a4a7cc2..6ffaabc16f 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1,7 +1,7 @@ /* Copyright 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -78,6 +78,7 @@ #include "rephist.h" #include "routerlist.h" #include "routerset.h" +#include "channelpadding.h" #include "ht.h" @@ -358,8 +359,8 @@ channel_note_destroy_pending(channel_t *chan, circid_t id) /** Called to indicate that a DESTROY is no longer pending on <b>chan</b> with * circuit ID <b>id</b> -- typically, because it has been sent. */ -MOCK_IMPL(void, channel_note_destroy_not_pending, - (channel_t *chan, circid_t id)) +MOCK_IMPL(void, +channel_note_destroy_not_pending,(channel_t *chan, circid_t id)) { circuit_t *circ = circuit_get_by_circid_channel_even_if_marked(id,chan); if (circ) { @@ -814,6 +815,11 @@ init_circuit_base(circuit_t *circ) circ->global_circuitlist_idx = smartlist_len(circuit_get_global_list()) - 1; } +/** If we haven't yet decided on a good timeout value for circuit + * building, we close idle circuits aggressively so we can get more + * data points. */ +#define IDLE_TIMEOUT_WHILE_LEARNING (1*60) + /** Allocate space for a new circuit, initializing with <b>p_circ_id</b> * and <b>p_conn</b>. Add it to the global circuit list. */ @@ -841,6 +847,41 @@ origin_circuit_new(void) circuit_build_times_update_last_circ(get_circuit_build_times_mutable()); + if (! circuit_build_times_disabled(get_options()) && + circuit_build_times_needs_circuits(get_circuit_build_times())) { + /* Circuits should be shorter lived if we need more of them + * for learning a good build timeout */ + circ->circuit_idle_timeout = IDLE_TIMEOUT_WHILE_LEARNING; + } else { + // This should always be larger than the current port prediction time + // remaining, or else we'll end up with the case where a circuit times out + // and another one is built, effectively doubling the timeout window. + // + // We also randomize it by up to 5% more (ie 5% of 0 to 3600 seconds, + // depending on how much circuit prediction time is remaining) so that + // we don't close a bunch of unused circuits all at the same time. + int prediction_time_remaining = + predicted_ports_prediction_time_remaining(time(NULL)); + circ->circuit_idle_timeout = prediction_time_remaining+1+ + crypto_rand_int(1+prediction_time_remaining/20); + + if (circ->circuit_idle_timeout <= 0) { + log_warn(LD_BUG, + "Circuit chose a negative idle timeout of %d based on " + "%d seconds of predictive building remaining.", + circ->circuit_idle_timeout, + prediction_time_remaining); + circ->circuit_idle_timeout = IDLE_TIMEOUT_WHILE_LEARNING; + } + + log_info(LD_CIRC, + "Circuit " U64_FORMAT " chose an idle timeout of %d based on " + "%d seconds of predictive building remaining.", + U64_PRINTF_ARG(circ->global_identifier), + circ->circuit_idle_timeout, + prediction_time_remaining); + } + return circ; } @@ -943,10 +984,6 @@ circuit_free(circuit_t *circ) crypto_cipher_free(ocirc->n_crypto); crypto_digest_free(ocirc->n_digest); - if (ocirc->hs_token) { - hs_circuitmap_remove_circuit(ocirc); - } - if (ocirc->rend_splice) { or_circuit_t *other = ocirc->rend_splice; tor_assert(other->base_.magic == OR_CIRCUIT_MAGIC); @@ -978,6 +1015,11 @@ circuit_free(circuit_t *circ) /* Remove from map. */ circuit_set_n_circid_chan(circ, 0, NULL); + /* Clear HS circuitmap token from this circ (if any) */ + if (circ->hs_token) { + hs_circuitmap_remove_circuit(circ); + } + /* Clear cell queue _after_ removing it from the map. Otherwise our * "active" checks will be violated. */ cell_queue_clear(&circ->n_chan_cells); @@ -1990,10 +2032,10 @@ single_conn_free_bytes(connection_t *conn) } if (conn->type == CONN_TYPE_DIR) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); - if (dir_conn->zlib_state) { - result += tor_zlib_state_size(dir_conn->zlib_state); - tor_zlib_free(dir_conn->zlib_state); - dir_conn->zlib_state = NULL; + if (dir_conn->compress_state) { + result += tor_compress_state_size(dir_conn->compress_state); + tor_compress_free(dir_conn->compress_state); + dir_conn->compress_state = NULL; } } return result; diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 6abee37dc4..d647062f46 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 036fb9570d..6c825011b6 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index f53abdd8c8..bf93bb8cbf 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index 0219459cdb..c2440b13f0 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h index a7b8961ac6..1f04408789 100644 --- a/src/or/circuitmux_ewma.h +++ b/src/or/circuitmux_ewma.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 6e73372550..51d580a1a4 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h index c748f82d5e..8a1dec4bfd 100644 --- a/src/or/circuitstats.h +++ b/src/or/circuitstats.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuituse.c b/src/or/circuituse.c index c2b450606b..9f9d3abf7c 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -705,18 +705,15 @@ circuit_expire_building(void) } } - /* If this is a hidden service client circuit which is far enough - * along in connecting to its destination, and we haven't already - * flagged it as 'timed out', and the user has not told us to - * close such circs immediately on timeout, flag it as 'timed out' - * so we'll launch another intro or rend circ, but don't mark it - * for close yet. + /* If this is a hidden service client circuit which is far enough along in + * connecting to its destination, and we haven't already flagged it as + * 'timed out', flag it so we'll launch another intro or rend circ, but + * don't mark it for close yet. * * (Circs flagged as 'timed out' are given a much longer timeout * period above, so we won't close them in the next call to * circuit_expire_building.) */ - if (!(options->CloseHSClientCircuitsImmediatelyOnTimeout) && - !(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out)) { + if (!(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out)) { switch (victim->purpose) { case CIRCUIT_PURPOSE_C_REND_READY: /* We only want to spare a rend circ if it has been specified in @@ -750,8 +747,7 @@ circuit_expire_building(void) /* If this is a service-side rendezvous circuit which is far * enough along in connecting to its destination, consider sparing * it. */ - if (!(options->CloseHSServiceRendCircuitsImmediatelyOnTimeout) && - !(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out) && + if (!(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out) && victim->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) { log_info(LD_CIRC,"Marking circ %u (state %d:%s, purpose %d) " "as timed-out HS circ; relaunching rendezvous attempt.", @@ -1240,8 +1236,8 @@ circuit_predict_and_launch_new(void) /** Build a new test circuit every 5 minutes */ #define TESTING_CIRCUIT_INTERVAL 300 -/** This function is called once a second, if router_have_min_dir_info() is - * true. Its job is to make sure all services we offer have enough circuits +/** This function is called once a second, if router_have_minimum_dir_info() + * is true. Its job is to make sure all services we offer have enough circuits * available. Some services just want enough circuits for current tasks, * whereas others want a minimum set of idle circuits hanging around. */ @@ -1383,11 +1379,6 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) tor_fragile_assert(); } -/** If we haven't yet decided on a good timeout value for circuit - * building, we close idles circuits aggressively so we can get more - * data points. */ -#define IDLE_TIMEOUT_WHILE_LEARNING (10*60) - /** Find each circuit that has been unused for too long, or dirty * for too long and has no streams on it: mark it for close. */ @@ -1397,21 +1388,15 @@ circuit_expire_old_circuits_clientside(void) struct timeval cutoff, now; tor_gettimeofday(&now); - cutoff = now; last_expired_clientside_circuits = now.tv_sec; - if (! circuit_build_times_disabled(get_options()) && - circuit_build_times_needs_circuits(get_circuit_build_times())) { - /* Circuits should be shorter lived if we need more of them - * for learning a good build timeout */ - cutoff.tv_sec -= IDLE_TIMEOUT_WHILE_LEARNING; - } else { - cutoff.tv_sec -= get_options()->CircuitIdleTimeout; - } - SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (circ->marked_for_close || !CIRCUIT_IS_ORIGIN(circ)) continue; + + cutoff = now; + cutoff.tv_sec -= TO_ORIGIN_CIRCUIT(circ)->circuit_idle_timeout; + /* If the circuit has been dirty for too long, and there are no streams * on it, mark it for close. */ @@ -1437,8 +1422,10 @@ circuit_expire_old_circuits_clientside(void) (circ->purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && circ->purpose <= CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) || circ->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) { - log_debug(LD_CIRC, - "Closing circuit that has been unused for %ld msec.", + log_info(LD_CIRC, + "Closing circuit "U64_FORMAT + " that has been unused for %ld msec.", + U64_PRINTF_ARG(TO_ORIGIN_CIRCUIT(circ)->global_identifier), tv_mdiff(&circ->timestamp_began, &now)); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } else if (!TO_ORIGIN_CIRCUIT(circ)->is_ancient) { diff --git a/src/or/circuituse.h b/src/or/circuituse.h index e5f8700ea2..ad4c214a3b 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/command.c b/src/or/command.c index 5866c386e4..c667cbbe52 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -326,10 +326,19 @@ command_process_create_cell(cell_t *cell, channel_t *chan) return; } + if (connection_or_digest_is_known_relay(chan->identity_digest)) { + rep_hist_note_circuit_handshake_requested(create_cell->handshake_type); + // Needed for chutney: Sometimes relays aren't in the consensus yet, and + // get marked as clients. This resets their channels once they appear. + // Probably useful for normal operation wrt relay flapping, too. + chan->is_client = 0; + } else { + channel_mark_client(chan); + } + if (create_cell->handshake_type != ONION_HANDSHAKE_TYPE_FAST) { /* hand it off to the cpuworkers, and then return. */ - if (connection_or_digest_is_known_relay(chan->identity_digest)) - rep_hist_note_circuit_handshake_requested(create_cell->handshake_type); + if (assign_onionskin_to_cpuworker(circ, create_cell) < 0) { log_debug(LD_GENERAL,"Failed to hand off onionskin. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_RESOURCELIMIT); @@ -344,8 +353,14 @@ command_process_create_cell(cell_t *cell, channel_t *chan) int len; created_cell_t created_cell; - /* Make sure we never try to use the OR connection on which we - * received this cell to satisfy an EXTEND request, */ + /* If the client used CREATE_FAST, it's probably a tor client or bridge + * relay, and we must not use it for EXTEND requests (in most cases, we + * won't have an authenticated peer ID for the extend). + * Public relays on 0.2.9 and later will use CREATE_FAST if they have no + * ntor onion key for this relay, but that should be a rare occurrence. + * Clients on 0.3.1 and later avoid using CREATE_FAST as much as they can, + * even during bootstrap, so the CREATE_FAST check is most accurate for + * earlier tor client versions. */ channel_mark_client(chan); memset(&created_cell, 0, sizeof(created_cell)); diff --git a/src/or/command.h b/src/or/command.h index 12cda6a463..5079d42e75 100644 --- a/src/or/command.h +++ b/src/or/command.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/config.c b/src/or/config.c index 16f189afd6..2f92591b19 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -69,10 +69,12 @@ #include "circuitmux.h" #include "circuitmux_ewma.h" #include "circuitstats.h" +#include "compress.h" #include "config.h" #include "connection.h" #include "connection_edge.h" #include "connection_or.h" +#include "consdiffmgr.h" #include "control.h" #include "confparse.h" #include "cpuworker.h" @@ -99,7 +101,6 @@ #include "statefile.h" #include "transports.h" #include "ext_orport.h" -#include "torgzip.h" #ifdef _WIN32 #include <shlobj.h> #endif @@ -205,10 +206,10 @@ static config_var_t option_vars_[] = { V(AccountingStart, STRING, NULL), V(Address, STRING, NULL), V(AllowDotExit, BOOL, "0"), - V(AllowInvalidNodes, CSV, "middle,rendezvous"), + OBSOLETE("AllowInvalidNodes"), V(AllowNonRFC953Hostnames, BOOL, "0"), - V(AllowSingleHopCircuits, BOOL, "0"), - V(AllowSingleHopExits, BOOL, "0"), + OBSOLETE("AllowSingleHopCircuits"), + OBSOLETE("AllowSingleHopExits"), V(AlternateBridgeAuthority, LINELIST, NULL), V(AlternateDirAuthority, LINELIST, NULL), OBSOLETE("AlternateHSAuthority"), @@ -242,9 +243,11 @@ static config_var_t option_vars_[] = { V(BridgeRecordUsageByCountry, BOOL, "1"), V(BridgeRelay, BOOL, "0"), V(CellStatistics, BOOL, "0"), + V(PaddingStatistics, BOOL, "1"), V(LearnCircuitBuildTimeout, BOOL, "1"), V(CircuitBuildTimeout, INTERVAL, "0"), - V(CircuitIdleTimeout, INTERVAL, "1 hour"), + OBSOLETE("CircuitIdleTimeout"), + V(CircuitsAvailableTimeout, INTERVAL, "0"), V(CircuitStreamTimeout, INTERVAL, "0"), V(CircuitPriorityHalflife, DOUBLE, "-100.0"), /*negative:'Use default'*/ V(ClientDNSRejectInternalAddresses, BOOL,"1"), @@ -261,7 +264,7 @@ static config_var_t option_vars_[] = { V(ConstrainedSockets, BOOL, "0"), V(ConstrainedSockSize, MEMUNIT, "8192"), V(ContactInfo, STRING, NULL), - V(ControlListenAddress, LINELIST, NULL), + OBSOLETE("ControlListenAddress"), VPORT(ControlPort), V(ControlPortFileGroupReadable,BOOL, "0"), V(ControlPortWriteToFile, FILENAME, NULL), @@ -278,7 +281,7 @@ static config_var_t option_vars_[] = { V(DisableNetwork, BOOL, "0"), V(DirAllowPrivateAddresses, BOOL, "0"), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "30 minutes"), - V(DirListenAddress, LINELIST, NULL), + OBSOLETE("DirListenAddress"), V(DirPolicy, LINELIST, NULL), VPORT(DirPort), V(DirPortFrontPage, FILENAME, NULL), @@ -292,7 +295,7 @@ static config_var_t option_vars_[] = { OBSOLETE("DisableV2DirectoryInfo_"), OBSOLETE("DynamicDHGroups"), VPORT(DNSPort), - V(DNSListenAddress, LINELIST, NULL), + OBSOLETE("DNSListenAddress"), V(DownloadExtraInfo, BOOL, "0"), V(TestingEnableConnBwEvent, BOOL, "0"), V(TestingEnableCellStatsEvent, BOOL, "0"), @@ -303,7 +306,7 @@ static config_var_t option_vars_[] = { V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"), V(ExcludeNodes, ROUTERSET, NULL), V(ExcludeExitNodes, ROUTERSET, NULL), - V(ExcludeSingleHopRelays, BOOL, "1"), + OBSOLETE("ExcludeSingleHopRelays"), V(ExitNodes, ROUTERSET, NULL), V(ExitPolicy, LINELIST, NULL), V(ExitPolicyRejectPrivate, BOOL, "1"), @@ -323,7 +326,7 @@ static config_var_t option_vars_[] = { OBSOLETE("FallbackNetworkstatusFile"), V(FascistFirewall, BOOL, "0"), V(FirewallPorts, CSV, ""), - V(FastFirstHopPK, AUTOBOOL, "auto"), + OBSOLETE("FastFirstHopPK"), V(FetchDirInfoEarly, BOOL, "0"), V(FetchDirInfoExtraEarly, BOOL, "0"), V(FetchServerDescriptors, BOOL, "1"), @@ -360,8 +363,8 @@ static config_var_t option_vars_[] = { VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"), V(HidServAuth, LINELIST, NULL), - V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"), - V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"), + OBSOLETE("CloseHSClientCircuitsImmediatelyOnTimeout"), + OBSOLETE("CloseHSServiceRendCircuitsImmediatelyOnTimeout"), V(HiddenServiceSingleHopMode, BOOL, "0"), V(HiddenServiceNonAnonymousMode,BOOL, "0"), V(HTTPProxy, STRING, NULL), @@ -390,25 +393,26 @@ static config_var_t option_vars_[] = { V(MaxAdvertisedBandwidth, MEMUNIT, "1 GB"), V(MaxCircuitDirtiness, INTERVAL, "10 minutes"), V(MaxClientCircuitsPending, UINT, "32"), + V(MaxConsensusAgeForDiffs, INTERVAL, "0 seconds"), VAR("MaxMemInQueues", MEMUNIT, MaxMemInQueues_raw, "0"), OBSOLETE("MaxOnionsPending"), V(MaxOnionQueueDelay, MSEC_INTERVAL, "1750 msec"), V(MaxUnparseableDescSizeToLog, MEMUNIT, "10 MB"), V(MinMeasuredBWsForAuthToIgnoreAdvertised, INT, "500"), - V(MyFamily, STRING, NULL), + VAR("MyFamily", LINELIST, MyFamily_lines, NULL), V(NewCircuitPeriod, INTERVAL, "30 seconds"), OBSOLETE("NamingAuthoritativeDirectory"), - V(NATDListenAddress, LINELIST, NULL), + OBSOLETE("NATDListenAddress"), VPORT(NATDPort), V(Nickname, STRING, NULL), - V(PredictedPortsRelevanceTime, INTERVAL, "1 hour"), - V(WarnUnsafeSocks, BOOL, "1"), + OBSOLETE("PredictedPortsRelevanceTime"), + OBSOLETE("WarnUnsafeSocks"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), V(NumCPUs, UINT, "0"), V(NumDirectoryGuards, UINT, "0"), V(NumEntryGuards, UINT, "0"), V(OfflineMasterKey, BOOL, "0"), - V(ORListenAddress, LINELIST, NULL), + OBSOLETE("ORListenAddress"), VPORT(ORPort), V(OutboundBindAddress, LINELIST, NULL), V(OutboundBindAddressOR, LINELIST, NULL), @@ -458,6 +462,8 @@ static config_var_t option_vars_[] = { V(RecommendedClientVersions, LINELIST, NULL), V(RecommendedServerVersions, LINELIST, NULL), V(RecommendedPackages, LINELIST, NULL), + V(ReducedConnectionPadding, BOOL, "0"), + V(ConnectionPadding, AUTOBOOL, "auto"), V(RefuseUnknownExits, AUTOBOOL, "auto"), V(RejectPlaintextPorts, CSV, ""), V(RelayBandwidthBurst, MEMUNIT, "0"), @@ -481,7 +487,7 @@ static config_var_t option_vars_[] = { V(SchedulerHighWaterMark__, MEMUNIT, "101 MB"), V(SchedulerMaxFlushCells__, UINT, "1000"), V(ShutdownWaitLength, INTERVAL, "30 seconds"), - V(SocksListenAddress, LINELIST, NULL), + OBSOLETE("SocksListenAddress"), V(SocksPolicy, LINELIST, NULL), VPORT(SocksPort), V(SocksTimeout, INTERVAL, "2 minutes"), @@ -494,10 +500,10 @@ static config_var_t option_vars_[] = { V(TokenBucketRefillInterval, MSEC_INTERVAL, "100 msec"), V(Tor2webMode, BOOL, "0"), V(Tor2webRendezvousPoints, ROUTERSET, NULL), - V(TLSECGroup, STRING, NULL), + OBSOLETE("TLSECGroup"), V(TrackHostExits, CSV, NULL), V(TrackHostExitsExpire, INTERVAL, "30 minutes"), - V(TransListenAddress, LINELIST, NULL), + OBSOLETE("TransListenAddress"), VPORT(TransPort), V(TransProxyType, STRING, "default"), OBSOLETE("TunnelDirConns"), @@ -553,11 +559,13 @@ static config_var_t option_vars_[] = { "10800, 21600, 43200"), /* With the ClientBootstrapConsensus*Download* below: * Clients with only authorities will try: - * - 3 authorities over 10 seconds, then wait 60 minutes. + * - at least 3 authorities over 10 seconds, then exponentially backoff, + * with the next attempt 3-21 seconds later, * Clients with authorities and fallbacks will try: - * - 2 authorities and 4 fallbacks over 21 seconds, then wait 60 minutes. + * - at least 2 authorities and 4 fallbacks over 21 seconds, then + * exponentially backoff, with the next attempts 4-33 seconds later, * Clients will also retry when an application request arrives. - * After a number of failed reqests, clients retry every 3 days + 1 hour. + * After a number of failed requests, clients retry every 3 days + 1 hour. * * Clients used to try 2 authorities over 10 seconds, then wait for * 60 minutes or an application request. @@ -662,35 +670,8 @@ static const config_deprecation_t option_deprecation_notes_[] = { /* Deprecated since 0.2.9.2-alpha... */ { "AllowDotExit", "Unrestricted use of the .exit notation can be used for " "a wide variety of application-level attacks." }, - { "AllowInvalidNodes", "There is no reason to enable this option; at best " - "it will make you easier to track." }, - { "AllowSingleHopCircuits", "Almost no relays actually allow single-hop " - "exits, making this option pointless." }, - { "AllowSingleHopExits", "Turning this on will make your relay easier " - "to abuse." }, { "ClientDNSRejectInternalAddresses", "Turning this on makes your client " "easier to fingerprint, and may open you to esoteric attacks." }, - { "ExcludeSingleHopRelays", "Turning it on makes your client easier to " - "fingerprint." }, - { "FastFirstHopPK", "Changing this option does not make your client more " - "secure, but does make it easier to fingerprint." }, - { "CloseHSClientCircuitsImmediatelyOnTimeout", "This option makes your " - "client easier to fingerprint." }, - { "CloseHSServiceRendCircuitsImmediatelyOnTimeout", "This option makes " - "your hidden services easier to fingerprint." }, - { "WarnUnsafeSocks", "Changing this option makes it easier for you " - "to accidentally lose your anonymity by leaking DNS information" }, - { "TLSECGroup", "The default is a nice secure choice; the other option " - "is less secure." }, - { "ControlListenAddress", "Use ControlPort instead." }, - { "DirListenAddress", "Use DirPort instead, possibly with the " - "NoAdvertise sub-option" }, - { "DNSListenAddress", "Use DNSPort instead." }, - { "SocksListenAddress", "Use SocksPort instead." }, - { "TransListenAddress", "Use TransPort instead." }, - { "NATDListenAddress", "Use NATDPort instead." }, - { "ORListenAddress", "Use ORPort instead, possibly with the " - "NoAdvertise sub-option" }, /* End of options deprecated since 0.2.9.2-alpha. */ { NULL, NULL } @@ -707,7 +688,9 @@ static int options_transition_affects_workers( const or_options_t *old_options, const or_options_t *new_options); static int options_transition_affects_descriptor( const or_options_t *old_options, const or_options_t *new_options); -static int check_nickname_list(char **lst, const char *name, char **msg); +static int normalize_nickname_list(config_line_t **normalized_out, + const config_line_t *lst, const char *name, + char **msg); static char *get_bindaddr_from_transport_listen_line(const char *line, const char *transport); static int parse_ports(or_options_t *options, int validate_only, @@ -915,6 +898,7 @@ or_options_free(or_options_t *options) tor_free(options->BridgePassword_AuthDigest_); tor_free(options->command_arg); tor_free(options->master_key_fname); + config_free_lines(options->MyFamily); config_free(&options_format, options); } @@ -1556,23 +1540,6 @@ get_effective_bwburst(const or_options_t *options) return (uint32_t)bw; } -/** Return True if any changes from <b>old_options</b> to - * <b>new_options</b> needs us to refresh our TLS context. */ -static int -options_transition_requires_fresh_tls_context(const or_options_t *old_options, - const or_options_t *new_options) -{ - tor_assert(new_options); - - if (!old_options) - return 0; - - if (!opt_streq(old_options->TLSECGroup, new_options->TLSECGroup)) - return 1; - - return 0; -} - /** * Return true if changing the configuration from <b>old</b> to <b>new</b> * affects the guard susbsystem. @@ -1791,13 +1758,6 @@ options_act(const or_options_t *old_options) log_warn(LD_BUG,"Error initializing keys; exiting"); return -1; } - } else if (old_options && - options_transition_requires_fresh_tls_context(old_options, - options)) { - if (router_initialize_tls_context() < 0) { - log_warn(LD_BUG,"Error initializing TLS context."); - return -1; - } } /* Write our PID to the PID file. If we do not have write permissions we @@ -1818,6 +1778,15 @@ options_act(const or_options_t *old_options) return -1; } + if (server_mode(options)) { + static int cdm_initialized = 0; + if (cdm_initialized == 0) { + cdm_initialized = 1; + consdiffmgr_configure(NULL); + consdiffmgr_validate(); + } + } + if (init_control_cookie_authentication(options->CookieAuthentication) < 0) { log_warn(LD_CONFIG,"Error creating control cookie authentication file."); return -1; @@ -2350,7 +2319,7 @@ print_usage(void) printf( "Copyright (c) 2001-2004, Roger Dingledine\n" "Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson\n" -"Copyright (c) 2007-2016, The Tor Project, Inc.\n\n" +"Copyright (c) 2007-2017, The Tor Project, Inc.\n\n" "tor -f <torrc> [args]\n" "See man page for options, or https://www.torproject.org/ for " "documentation.\n"); @@ -2812,13 +2781,13 @@ compute_publishserverdescriptor(or_options_t *options) #define MIN_REND_POST_PERIOD (10*60) #define MIN_REND_POST_PERIOD_TESTING (5) -/** Highest allowable value for PredictedPortsRelevanceTime; if this is - * too high, our selection of exits will decrease for an extended - * period of time to an uncomfortable level .*/ -#define MAX_PREDICTED_CIRCS_RELEVANCE (60*60) +/** Highest allowable value for CircuitsAvailableTimeout. + * If this is too large, client connections will stay open for too long, + * incurring extra padding overhead. */ +#define MAX_CIRCS_AVAILABLE_TIME (24*60*60) /** Highest allowable value for RendPostPeriod. */ -#define MAX_DIR_PERIOD (MIN_ONION_KEY_LIFETIME/2) +#define MAX_DIR_PERIOD ((7*24*60*60)/2) /** Lowest allowable value for MaxCircuitDirtiness; if this is too low, Tor * will generate too many circuits and potentially overload the network. */ @@ -3001,6 +2970,10 @@ options_validate(or_options_t *old_options, or_options_t *options, tor_assert(msg); *msg = NULL; + if (parse_ports(options, 1, msg, &n_ports, + &world_writable_control_socket) < 0) + return -1; + /* Set UseEntryGuards from the configured value, before we check it below. * We change UseEntryGuards when it's incompatible with other options, * but leave UseEntryGuards_option with the original value. @@ -3019,10 +2992,6 @@ options_validate(or_options_t *old_options, or_options_t *options, "for details.", uname); } - if (parse_ports(options, 1, msg, &n_ports, - &world_writable_control_socket) < 0) - return -1; - if (parse_outbound_addresses(options, 1, msg) < 0) return -1; @@ -3115,14 +3084,12 @@ options_validate(or_options_t *old_options, or_options_t *options, if (strcasecmp(options->TransProxyType, "default") && !options->TransPort_set) { - REJECT("Cannot use TransProxyType without any valid TransPort or " - "TransListenAddress."); + REJECT("Cannot use TransProxyType without any valid TransPort."); } } #else if (options->TransPort_set) - REJECT("TransPort and TransListenAddress are disabled " - "in this build."); + REJECT("TransPort is disabled in this build."); #endif if (options->TokenBucketRefillInterval <= 0 @@ -3159,15 +3126,6 @@ options_validate(or_options_t *old_options, or_options_t *options, } } - if (options->TLSECGroup && (strcasecmp(options->TLSECGroup, "P256") && - strcasecmp(options->TLSECGroup, "P224"))) { - COMPLAIN("Unrecognized TLSECGroup: Falling back to the default."); - tor_free(options->TLSECGroup); - } - if (!evaluate_ecgroup_for_tls(options->TLSECGroup)) { - REJECT("Unsupported TLSECGroup."); - } - if (options->ExcludeNodes && options->StrictNodes) { COMPLAIN("You have asked to exclude certain relays from all positions " "in your circuits. Expect hidden services and other Tor " @@ -3375,28 +3333,6 @@ options_validate(or_options_t *old_options, or_options_t *options, server_mode(options)); options->MaxMemInQueues_low_threshold = (options->MaxMemInQueues / 4) * 3; - options->AllowInvalid_ = 0; - - if (options->AllowInvalidNodes) { - SMARTLIST_FOREACH_BEGIN(options->AllowInvalidNodes, const char *, cp) { - if (!strcasecmp(cp, "entry")) - options->AllowInvalid_ |= ALLOW_INVALID_ENTRY; - else if (!strcasecmp(cp, "exit")) - options->AllowInvalid_ |= ALLOW_INVALID_EXIT; - else if (!strcasecmp(cp, "middle")) - options->AllowInvalid_ |= ALLOW_INVALID_MIDDLE; - else if (!strcasecmp(cp, "introduction")) - options->AllowInvalid_ |= ALLOW_INVALID_INTRODUCTION; - else if (!strcasecmp(cp, "rendezvous")) - options->AllowInvalid_ |= ALLOW_INVALID_RENDEZVOUS; - else { - tor_asprintf(msg, - "Unrecognized value '%s' in AllowInvalidNodes", cp); - return -1; - } - } SMARTLIST_FOREACH_END(cp); - } - if (!options->SafeLogging || !strcasecmp(options->SafeLogging, "0")) { options->SafeLogging_ = SAFELOG_SCRUB_NONE; @@ -3432,6 +3368,14 @@ options_validate(or_options_t *old_options, or_options_t *options, options->DirPort_set = 0; } + if (server_mode(options) && options->ConnectionPadding != -1) { + REJECT("Relays must use 'auto' for the ConnectionPadding setting."); + } + + if (server_mode(options) && options->ReducedConnectionPadding != 0) { + REJECT("Relays cannot set ReducedConnectionPadding. "); + } + if (options->MinUptimeHidServDirectoryV2 < 0) { log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at " "least 0 seconds. Changing to 0."); @@ -3453,17 +3397,17 @@ options_validate(or_options_t *old_options, or_options_t *options, options->RendPostPeriod = MAX_DIR_PERIOD; } - if (options->PredictedPortsRelevanceTime > - MAX_PREDICTED_CIRCS_RELEVANCE) { - log_warn(LD_CONFIG, "PredictedPortsRelevanceTime is too large; " - "clipping to %ds.", MAX_PREDICTED_CIRCS_RELEVANCE); - options->PredictedPortsRelevanceTime = MAX_PREDICTED_CIRCS_RELEVANCE; - } - /* Check the Single Onion Service options */ if (options_validate_single_onion(options, msg) < 0) return -1; + if (options->CircuitsAvailableTimeout > MAX_CIRCS_AVAILABLE_TIME) { + // options_t is immutable for new code (the above code is older), + // so just make the user fix the value themselves rather than + // silently keep a shadow value lower than what they asked for. + REJECT("CircuitsAvailableTimeout is too large. Max is 24 hours."); + } + #ifdef ENABLE_TOR2WEB_MODE if (options->Tor2webMode && options->UseEntryGuards) { /* tor2web mode clients do not (and should not) use entry guards @@ -3513,6 +3457,20 @@ options_validate(or_options_t *old_options, or_options_t *options, return -1; } + /* Inform the hidden service operator that pinning EntryNodes can possibly + * be harmful for the service anonymity. */ + if (options->EntryNodes && + routerset_is_list(options->EntryNodes) && + (options->RendConfigLines != NULL)) { + log_warn(LD_CONFIG, + "EntryNodes is set with multiple entries and at least one " + "hidden service is configured. Pinning entry nodes can possibly " + "be harmful to the service anonymity. Because of this, we " + "recommend you either don't do that or make sure you know what " + "you are doing. For more details, please look at " + "https://trac.torproject.org/projects/tor/ticket/21155."); + } + /* Single Onion Services: non-anonymous hidden services */ if (rend_service_non_anonymous_mode_enabled(options)) { log_warn(LD_CONFIG, @@ -3868,13 +3826,14 @@ options_validate(or_options_t *old_options, or_options_t *options, "have it group-readable."); } - if (options->MyFamily && options->BridgeRelay) { + if (options->MyFamily_lines && options->BridgeRelay) { log_warn(LD_CONFIG, "Listing a family for a bridge relay is not " "supported: it can reveal bridge fingerprints to censors. " "You should also make sure you aren't listing this bridge's " "fingerprint in any other MyFamily."); } - if (check_nickname_list(&options->MyFamily, "MyFamily", msg)) + if (normalize_nickname_list(&options->MyFamily, + options->MyFamily_lines, "MyFamily", msg)) return -1; for (cl = options->NodeFamilies; cl; cl = cl->next) { routerset_t *rs = routerset_new(); @@ -4071,13 +4030,6 @@ options_validate(or_options_t *old_options, or_options_t *options, "AlternateDirAuthority and AlternateBridgeAuthority configured."); } - if (options->AllowSingleHopExits && !options->DirAuthorities) { - COMPLAIN("You have set AllowSingleHopExits; now your relay will allow " - "others to make one-hop exits. However, since by default most " - "clients avoid relays that set this option, most clients will " - "ignore you."); - } - #define CHECK_DEFAULT(arg) \ STMT_BEGIN \ if (!options->TestingTorNetwork && \ @@ -4583,7 +4535,7 @@ options_transition_affects_descriptor(const or_options_t *old_options, get_effective_bwburst(old_options) != get_effective_bwburst(new_options) || !opt_streq(old_options->ContactInfo, new_options->ContactInfo) || - !opt_streq(old_options->MyFamily, new_options->MyFamily) || + !config_lines_eq(old_options->MyFamily, new_options->MyFamily) || !opt_streq(old_options->AccountingStart, new_options->AccountingStart) || old_options->AccountingMax != new_options->AccountingMax || old_options->AccountingRule != new_options->AccountingRule || @@ -4679,27 +4631,36 @@ get_default_conf_file(int defaults_file) #endif } -/** Verify whether lst is a string containing valid-looking comma-separated - * nicknames, or NULL. Will normalise <b>lst</b> to prefix '$' to any nickname - * or fingerprint that needs it. Return 0 on success. +/** Verify whether lst is a list of strings containing valid-looking + * comma-separated nicknames, or NULL. Will normalise <b>lst</b> to prefix '$' + * to any nickname or fingerprint that needs it. Also splits comma-separated + * list elements into multiple elements. Return 0 on success. * Warn and return -1 on failure. */ static int -check_nickname_list(char **lst, const char *name, char **msg) +normalize_nickname_list(config_line_t **normalized_out, + const config_line_t *lst, const char *name, + char **msg) { - int r = 0; - smartlist_t *sl; - int changes = 0; - - if (!*lst) + if (!lst) return 0; - sl = smartlist_new(); - smartlist_split_string(sl, *lst, ",", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0); + config_line_t *new_nicknames = NULL; + config_line_t **new_nicknames_next = &new_nicknames; - SMARTLIST_FOREACH_BEGIN(sl, char *, s) + const config_line_t *cl; + for (cl = lst; cl; cl = cl->next) { + const char *line = cl->value; + if (!line) + continue; + + int valid_line = 1; + smartlist_t *sl = smartlist_new(); + smartlist_split_string(sl, line, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0); + SMARTLIST_FOREACH_BEGIN(sl, char *, s) { + char *normalized = NULL; if (!is_legal_nickname_or_hexdigest(s)) { // check if first char is dollar if (s[0] != '$') { @@ -4708,36 +4669,45 @@ check_nickname_list(char **lst, const char *name, char **msg) tor_asprintf(&prepended, "$%s", s); if (is_legal_nickname_or_hexdigest(prepended)) { - // The nickname is valid when it's prepended, swap the current - // version with a prepended one - tor_free(s); - SMARTLIST_REPLACE_CURRENT(sl, s, prepended); - changes = 1; - continue; + // The nickname is valid when it's prepended, set it as the + // normalized version + normalized = prepended; + } else { + // Still not valid, free and fallback to error message + tor_free(prepended); } - - // Still not valid, free and fallback to error message - tor_free(prepended); } - tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name); - r = -1; - break; + if (!normalized) { + tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name); + valid_line = 0; + break; + } + } else { + normalized = tor_strdup(s); } - } - SMARTLIST_FOREACH_END(s); - // Replace the caller's nickname list with a fixed one - if (changes && r == 0) { - char *newNicknames = smartlist_join_strings(sl, ", ", 0, NULL); - tor_free(*lst); - *lst = newNicknames; + config_line_t *next = tor_malloc_zero(sizeof(*next)); + next->key = tor_strdup(cl->key); + next->value = normalized; + next->next = NULL; + + *new_nicknames_next = next; + new_nicknames_next = &next->next; + } SMARTLIST_FOREACH_END(s); + + SMARTLIST_FOREACH(sl, char *, s, tor_free(s)); + smartlist_free(sl); + + if (!valid_line) { + config_free_lines(new_nicknames); + return -1; + } } - SMARTLIST_FOREACH(sl, char *, s, tor_free(s)); - smartlist_free(sl); + *normalized_out = new_nicknames; - return r; + return 0; } /** Learn config file name from command line arguments, or use the default. @@ -4939,9 +4909,21 @@ options_init_from_torrc(int argc, char **argv) printf("OpenSSL \t\t%-15s\t\t%s\n", crypto_openssl_get_header_version_str(), crypto_openssl_get_version_str()); - printf("Zlib \t\t%-15s\t\t%s\n", - tor_zlib_get_header_version_str(), - tor_zlib_get_version_str()); + if (tor_compress_supports_method(ZLIB_METHOD)) { + printf("Zlib \t\t%-15s\t\t%s\n", + tor_compress_version_str(ZLIB_METHOD), + tor_compress_header_version_str(ZLIB_METHOD)); + } + if (tor_compress_supports_method(LZMA_METHOD)) { + printf("Liblzma \t\t%-15s\t\t%s\n", + tor_compress_version_str(LZMA_METHOD), + tor_compress_header_version_str(LZMA_METHOD)); + } + if (tor_compress_supports_method(ZSTD_METHOD)) { + printf("Libzstd \t\t%-15s\t\t%s\n", + tor_compress_version_str(ZSTD_METHOD), + tor_compress_header_version_str(ZSTD_METHOD)); + } //TODO: Hex versions? exit(0); } @@ -5081,6 +5063,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, config_line_t *cl; int retval; setopt_err_t err = SETOPT_ERR_MISC; + int cf_has_include = 0; tor_assert(msg); oldoptions = global_options; /* get_options unfortunately asserts if @@ -5097,7 +5080,8 @@ options_init_from_string(const char *cf_defaults, const char *cf, if (!body) continue; /* get config lines, assign them */ - retval = config_get_lines(body, &cl, 1); + retval = config_get_lines_include(body, &cl, 1, + body == cf ? &cf_has_include : NULL); if (retval < 0) { err = SETOPT_ERR_PARSE; goto err; @@ -5125,6 +5109,8 @@ options_init_from_string(const char *cf_defaults, const char *cf, goto err; } + newoptions->IncludeUsed = cf_has_include; + /* If this is a testing network configuration, change defaults * for a list of dependent config options, re-initialize newoptions * with the new defaults, and assign all options to it second time. */ @@ -5168,7 +5154,8 @@ options_init_from_string(const char *cf_defaults, const char *cf, if (!body) continue; /* get config lines, assign them */ - retval = config_get_lines(body, &cl, 1); + retval = config_get_lines_include(body, &cl, 1, + body == cf ? &cf_has_include : NULL); if (retval < 0) { err = SETOPT_ERR_PARSE; goto err; @@ -5191,6 +5178,8 @@ options_init_from_string(const char *cf_defaults, const char *cf, } } + newoptions->IncludeUsed = cf_has_include; + /* Validate newoptions */ if (options_validate(oldoptions, newoptions, newdefaultoptions, 0, msg) < 0) { @@ -6427,14 +6416,9 @@ warn_client_dns_cache(const char *option, int disabling) /** * Parse port configuration for a single port type. * - * Read entries of the "FooPort" type from the list <b>ports</b>, and - * entries of the "FooListenAddress" type from the list - * <b>listenaddrs</b>. Two syntaxes are supported: a legacy syntax - * where FooPort is at most a single entry containing a port number and - * where FooListenAddress has any number of address:port combinations; - * and a new syntax where there are no FooListenAddress entries and - * where FooPort can have any number of entries of the format - * "[Address:][Port] IsolationOptions". + * Read entries of the "FooPort" type from the list <b>ports</b>. Syntax is + * that FooPort can have any number of entries of the format + * "[Address:][Port] IsolationOptions". * * In log messages, describe the port type as <b>portname</b>. * @@ -6448,9 +6432,6 @@ warn_client_dns_cache(const char *option, int disabling) * ports are not on a local address. If CL_PORT_FORBID_NONLOCAL is set, * this is a control port with no password set: don't even allow it. * - * Unless CL_PORT_ALLOW_EXTRA_LISTENADDR is set in <b>flags</b>, warn - * if FooListenAddress is set but FooPort is 0. - * * If CL_PORT_SERVER_OPTIONS is set in <b>flags</b>, do not allow stream * isolation options in the FooPort entries; instead allow the * server-port option set. @@ -6465,7 +6446,6 @@ warn_client_dns_cache(const char *option, int disabling) STATIC int parse_port_config(smartlist_t *out, const config_line_t *ports, - const config_line_t *listenaddrs, const char *portname, int listener_type, const char *defaultaddr, @@ -6482,90 +6462,12 @@ parse_port_config(smartlist_t *out, const unsigned forbid_nonlocal = flags & CL_PORT_FORBID_NONLOCAL; const unsigned default_to_group_writable = flags & CL_PORT_DFLT_GROUP_WRITABLE; - const unsigned allow_spurious_listenaddr = - flags & CL_PORT_ALLOW_EXTRA_LISTENADDR; const unsigned takes_hostnames = flags & CL_PORT_TAKES_HOSTNAMES; const unsigned is_unix_socket = flags & CL_PORT_IS_UNIXSOCKET; int got_zero_port=0, got_nonzero_port=0; char *unix_socket_path = NULL; - /* FooListenAddress is deprecated; let's make it work like it used to work, - * though. */ - if (listenaddrs) { - int mainport = defaultport; - - if (ports && ports->next) { - log_warn(LD_CONFIG, "%sListenAddress can't be used when there are " - "multiple %sPort lines", portname, portname); - return -1; - } else if (ports) { - if (!strcmp(ports->value, "auto")) { - mainport = CFG_AUTO_PORT; - } else { - int ok; - mainport = (int)tor_parse_long(ports->value, 10, 0, 65535, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, "%sListenAddress can only be used with a single " - "%sPort with value \"auto\" or 1-65535 and no options set.", - portname, portname); - return -1; - } - } - } - - if (mainport == 0) { - if (allow_spurious_listenaddr) - return 1; /*DOCDOC*/ - log_warn(LD_CONFIG, "%sPort must be defined if %sListenAddress is used", - portname, portname); - return -1; - } - - if (use_server_options && out) { - /* Add a no_listen port. */ - port_cfg_t *cfg = port_cfg_new(0); - cfg->type = listener_type; - cfg->port = mainport; - tor_addr_make_unspec(&cfg->addr); /* Server ports default to 0.0.0.0 */ - cfg->server_cfg.no_listen = 1; - cfg->server_cfg.bind_ipv4_only = 1; - /* cfg->entry_cfg defaults are already set by port_cfg_new */ - smartlist_add(out, cfg); - } - - for (; listenaddrs; listenaddrs = listenaddrs->next) { - tor_addr_t addr; - uint16_t port = 0; - if (tor_addr_port_lookup(listenaddrs->value, &addr, &port) < 0) { - log_warn(LD_CONFIG, "Unable to parse %sListenAddress '%s'", - portname, listenaddrs->value); - return -1; - } - if (out) { - port_cfg_t *cfg = port_cfg_new(0); - cfg->type = listener_type; - cfg->port = port ? port : mainport; - tor_addr_copy(&cfg->addr, &addr); - cfg->entry_cfg.session_group = SESSION_GROUP_UNSET; - cfg->entry_cfg.isolation_flags = ISO_DEFAULT; - cfg->server_cfg.no_advertise = 1; - smartlist_add(out, cfg); - } - } - - if (warn_nonlocal && out) { - if (is_control) - warn_nonlocal_controller_ports(out, forbid_nonlocal); - else if (is_ext_orport) - warn_nonlocal_ext_orports(out, portname); - else - warn_nonlocal_client_ports(out, portname, listener_type); - } - return 0; - } /* end if (listenaddrs) */ - - /* No ListenAddress lines. If there's no FooPort, then maybe make a default - * one. */ + /* If there's no FooPort, then maybe make a default one. */ if (! ports) { if (defaultport && defaultaddr && out) { port_cfg_t *cfg = port_cfg_new(is_unix_socket ? strlen(defaultaddr) : 0); @@ -6966,6 +6868,7 @@ parse_port_config(smartlist_t *out, SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); smartlist_clear(elts); tor_free(addrport); + tor_free(unix_socket_path); } if (warn_nonlocal && out) { @@ -7036,36 +6939,35 @@ parse_ports(or_options_t *options, int validate_only, const unsigned gw_flag = options->SocksSocketsGroupWritable ? CL_PORT_DFLT_GROUP_WRITABLE : 0; if (parse_port_config(ports, - options->SocksPort_lines, options->SocksListenAddress, + options->SocksPort_lines, "Socks", CONN_TYPE_AP_LISTENER, "127.0.0.1", 9050, - CL_PORT_WARN_NONLOCAL|CL_PORT_ALLOW_EXTRA_LISTENADDR| - CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) { - *msg = tor_strdup("Invalid SocksPort/SocksListenAddress configuration"); + CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) { + *msg = tor_strdup("Invalid SocksPort configuration"); goto err; } if (parse_port_config(ports, - options->DNSPort_lines, options->DNSListenAddress, + options->DNSPort_lines, "DNS", CONN_TYPE_AP_DNS_LISTENER, "127.0.0.1", 0, CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES) < 0) { - *msg = tor_strdup("Invalid DNSPort/DNSListenAddress configuration"); + *msg = tor_strdup("Invalid DNSPort configuration"); goto err; } if (parse_port_config(ports, - options->TransPort_lines, options->TransListenAddress, + options->TransPort_lines, "Trans", CONN_TYPE_AP_TRANS_LISTENER, "127.0.0.1", 0, CL_PORT_WARN_NONLOCAL) < 0) { - *msg = tor_strdup("Invalid TransPort/TransListenAddress configuration"); + *msg = tor_strdup("Invalid TransPort configuration"); goto err; } if (parse_port_config(ports, - options->NATDPort_lines, options->NATDListenAddress, + options->NATDPort_lines, "NATD", CONN_TYPE_AP_NATD_LISTENER, "127.0.0.1", 0, CL_PORT_WARN_NONLOCAL) < 0) { - *msg = tor_strdup("Invalid NatdPort/NatdListenAddress configuration"); + *msg = tor_strdup("Invalid NatdPort configuration"); goto err; } { @@ -7081,16 +6983,14 @@ parse_ports(or_options_t *options, int validate_only, if (parse_port_config(ports, options->ControlPort_lines, - options->ControlListenAddress, "Control", CONN_TYPE_CONTROL_LISTENER, "127.0.0.1", 0, control_port_flags) < 0) { - *msg = tor_strdup("Invalid ControlPort/ControlListenAddress " - "configuration"); + *msg = tor_strdup("Invalid ControlPort configuration"); goto err; } - if (parse_port_config(ports, options->ControlSocket, NULL, + if (parse_port_config(ports, options->ControlSocket, "ControlSocket", CONN_TYPE_CONTROL_LISTENER, NULL, 0, control_port_flags | CL_PORT_IS_UNIXSOCKET) < 0) { @@ -7100,15 +7000,15 @@ parse_ports(or_options_t *options, int validate_only, } if (! options->ClientOnly) { if (parse_port_config(ports, - options->ORPort_lines, options->ORListenAddress, + options->ORPort_lines, "OR", CONN_TYPE_OR_LISTENER, "0.0.0.0", 0, CL_PORT_SERVER_OPTIONS) < 0) { - *msg = tor_strdup("Invalid ORPort/ORListenAddress configuration"); + *msg = tor_strdup("Invalid ORPort configuration"); goto err; } if (parse_port_config(ports, - options->ExtORPort_lines, NULL, + options->ExtORPort_lines, "ExtOR", CONN_TYPE_EXT_OR_LISTENER, "127.0.0.1", 0, CL_PORT_SERVER_OPTIONS|CL_PORT_WARN_NONLOCAL) < 0) { @@ -7116,11 +7016,11 @@ parse_ports(or_options_t *options, int validate_only, goto err; } if (parse_port_config(ports, - options->DirPort_lines, options->DirListenAddress, + options->DirPort_lines, "Dir", CONN_TYPE_DIR_LISTENER, "0.0.0.0", 0, CL_PORT_SERVER_OPTIONS) < 0) { - *msg = tor_strdup("Invalid DirPort/DirListenAddress configuration"); + *msg = tor_strdup("Invalid DirPort configuration"); goto err; } } diff --git a/src/or/config.h b/src/or/config.h index 6645532514..27aec7fe3d 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -157,7 +157,7 @@ smartlist_t *get_options_for_server_transport(const char *transport); #define CL_PORT_NO_STREAM_OPTIONS (1u<<0) #define CL_PORT_WARN_NONLOCAL (1u<<1) -#define CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2) +/* Was CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2) */ #define CL_PORT_SERVER_OPTIONS (1u<<3) #define CL_PORT_FORBID_NONLOCAL (1u<<4) #define CL_PORT_TAKES_HOSTNAMES (1u<<5) @@ -193,7 +193,6 @@ STATIC int have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem, char **msg); STATIC int parse_port_config(smartlist_t *out, const config_line_t *ports, - const config_line_t *listenaddrs, const char *portname, int listener_type, const char *defaultaddr, diff --git a/src/or/confparse.c b/src/or/confparse.c index 9b13a91856..abae7e33dc 100644 --- a/src/or/confparse.c +++ b/src/or/confparse.c @@ -2,7 +2,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -31,8 +31,6 @@ static int config_parse_msec_interval(const char *s, int *ok); static int config_parse_interval(const char *s, int *ok); static void config_reset(const config_format_t *fmt, void *options, const config_var_t *var, int use_defaults); -static config_line_t *config_lines_dup_and_filter(const config_line_t *inp, - const char *key); /** Allocate an empty configuration object of a given format type. */ void * @@ -80,120 +78,6 @@ config_expand_abbrev(const config_format_t *fmt, const char *option, return option; } -/** Helper: allocate a new configuration option mapping 'key' to 'val', - * append it to *<b>lst</b>. */ -void -config_line_append(config_line_t **lst, - const char *key, - const char *val) -{ - config_line_t *newline; - - newline = tor_malloc_zero(sizeof(config_line_t)); - newline->key = tor_strdup(key); - newline->value = tor_strdup(val); - newline->next = NULL; - while (*lst) - lst = &((*lst)->next); - - (*lst) = newline; -} - -/** Return the line in <b>lines</b> whose key is exactly <b>key</b>, or NULL - * if no such key exists. For handling commandline-only options only; other - * options should be looked up in the appropriate data structure. */ -const config_line_t * -config_line_find(const config_line_t *lines, - const char *key) -{ - const config_line_t *cl; - for (cl = lines; cl; cl = cl->next) { - if (!strcmp(cl->key, key)) - return cl; - } - return NULL; -} - -/** Helper: parse the config string and strdup into key/value - * strings. Set *result to the list, or NULL if parsing the string - * failed. Return 0 on success, -1 on failure. Warn and ignore any - * misformatted lines. - * - * If <b>extended</b> is set, then treat keys beginning with / and with + as - * indicating "clear" and "append" respectively. */ -int -config_get_lines(const char *string, config_line_t **result, int extended) -{ - config_line_t *list = NULL, **next; - char *k, *v; - const char *parse_err; - - next = &list; - do { - k = v = NULL; - string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err); - if (!string) { - log_warn(LD_CONFIG, "Error while parsing configuration: %s", - parse_err?parse_err:"<unknown>"); - config_free_lines(list); - tor_free(k); - tor_free(v); - return -1; - } - if (k && v) { - unsigned command = CONFIG_LINE_NORMAL; - if (extended) { - if (k[0] == '+') { - char *k_new = tor_strdup(k+1); - tor_free(k); - k = k_new; - command = CONFIG_LINE_APPEND; - } else if (k[0] == '/') { - char *k_new = tor_strdup(k+1); - tor_free(k); - k = k_new; - tor_free(v); - v = tor_strdup(""); - command = CONFIG_LINE_CLEAR; - } - } - /* This list can get long, so we keep a pointer to the end of it - * rather than using config_line_append over and over and getting - * n^2 performance. */ - *next = tor_malloc_zero(sizeof(config_line_t)); - (*next)->key = k; - (*next)->value = v; - (*next)->next = NULL; - (*next)->command = command; - next = &((*next)->next); - } else { - tor_free(k); - tor_free(v); - } - } while (*string); - - *result = list; - return 0; -} - -/** - * Free all the configuration lines on the linked list <b>front</b>. - */ -void -config_free_lines(config_line_t *front) -{ - config_line_t *tmp; - - while (front) { - tmp = front; - front = tmp->next; - - tor_free(tmp->key); - tor_free(tmp->value); - tor_free(tmp); - } -} - /** If <b>key</b> is a deprecated configuration option, return the message * explaining why it is deprecated (which may be an empty string). Return NULL * if it is not deprecated. The <b>key</b> field must be fully expanded. */ @@ -633,36 +517,6 @@ config_value_needs_escape(const char *value) return 0; } -/** Return a newly allocated deep copy of the lines in <b>inp</b>. */ -config_line_t * -config_lines_dup(const config_line_t *inp) -{ - return config_lines_dup_and_filter(inp, NULL); -} - -/** Return a newly allocated deep copy of the lines in <b>inp</b>, - * but only the ones that match <b>key</b>. */ -static config_line_t * -config_lines_dup_and_filter(const config_line_t *inp, - const char *key) -{ - config_line_t *result = NULL; - config_line_t **next_out = &result; - while (inp) { - if (key && strcasecmpstart(inp->key, key)) { - inp = inp->next; - continue; - } - *next_out = tor_malloc_zero(sizeof(config_line_t)); - (*next_out)->key = tor_strdup(inp->key); - (*next_out)->value = tor_strdup(inp->value); - inp = inp->next; - next_out = &((*next_out)->next); - } - (*next_out) = NULL; - return result; -} - /** Return newly allocated line or lines corresponding to <b>key</b> in the * configuration <b>options</b>. If <b>escape_val</b> is true and a * value needs to be quoted before it's put in a config file, quote and @@ -1028,36 +882,6 @@ config_free(const config_format_t *fmt, void *options) tor_free(options); } -/** Return true iff a and b contain identical keys and values in identical - * order. */ -int -config_lines_eq(config_line_t *a, config_line_t *b) -{ - while (a && b) { - if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value)) - return 0; - a = a->next; - b = b->next; - } - if (a || b) - return 0; - return 1; -} - -/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */ -int -config_count_key(const config_line_t *a, const char *key) -{ - int n = 0; - while (a) { - if (!strcasecmp(a->key, key)) { - ++n; - } - a = a->next; - } - return n; -} - /** Return true iff the option <b>name</b> has the same value in <b>o1</b> * and <b>o2</b>. Must not be called for LINELIST_S or OBSOLETE options. */ @@ -1366,8 +1190,6 @@ config_parse_msec_interval(const char *s, int *ok) { uint64_t r; r = config_parse_units(s, time_msec_units, ok); - if (!ok) - return -1; if (r > INT_MAX) { log_warn(LD_CONFIG, "Msec interval '%s' is too long", s); *ok = 0; @@ -1385,8 +1207,6 @@ config_parse_interval(const char *s, int *ok) { uint64_t r; r = config_parse_units(s, time_units, ok); - if (!ok) - return -1; if (r > INT_MAX) { log_warn(LD_CONFIG, "Interval '%s' is too long", s); *ok = 0; diff --git a/src/or/confparse.h b/src/or/confparse.h index 8d915d266b..9c4205d07c 100644 --- a/src/or/confparse.h +++ b/src/or/confparse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_CONFPARSE_H @@ -103,14 +103,7 @@ typedef struct config_format_t { #define CAL_WARN_DEPRECATIONS (1u<<2) void *config_new(const config_format_t *fmt); -void config_line_append(config_line_t **lst, - const char *key, const char *val); -config_line_t *config_lines_dup(const config_line_t *inp); -const config_line_t *config_line_find(const config_line_t *lines, - const char *key); void config_free(const config_format_t *fmt, void *options); -int config_lines_eq(config_line_t *a, config_line_t *b); -int config_count_key(const config_line_t *a, const char *key); config_line_t *config_get_assigned_option(const config_format_t *fmt, const void *options, const char *key, int escape_val); @@ -131,9 +124,6 @@ const char *config_find_deprecation(const config_format_t *fmt, const char *key); const config_var_t *config_find_option(const config_format_t *fmt, const char *key); - -int config_get_lines(const char *string, config_line_t **result, int extended); -void config_free_lines(config_line_t *front); const char *config_expand_abbrev(const config_format_t *fmt, const char *option, int command_line, int warn_obsolete); diff --git a/src/or/connection.c b/src/or/connection.c index 5628c6a3f9..192d705b5b 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -628,13 +628,13 @@ connection_free_(connection_t *conn) dir_connection_t *dir_conn = TO_DIR_CONN(conn); tor_free(dir_conn->requested_resource); - tor_zlib_free(dir_conn->zlib_state); - if (dir_conn->fingerprint_stack) { - SMARTLIST_FOREACH(dir_conn->fingerprint_stack, char *, cp, tor_free(cp)); - smartlist_free(dir_conn->fingerprint_stack); + tor_compress_free(dir_conn->compress_state); + if (dir_conn->spool) { + SMARTLIST_FOREACH(dir_conn->spool, spooled_resource_t *, spooled, + spooled_resource_free(spooled)); + smartlist_free(dir_conn->spool); } - cached_dir_decref(dir_conn->cached_dir); rend_data_free(dir_conn->rend_data); if (dir_conn->guard_state) { /* Cancel before freeing, if it's still there. */ @@ -4042,10 +4042,6 @@ connection_flush(connection_t *conn) * its contents compressed or decompressed as they're written. If zlib is * negative, this is the last data to be compressed, and the connection's zlib * state should be flushed. - * - * If it's a local control connection and a 64k chunk is ready, try to flush - * it all, so we don't end up with many megabytes of controller info queued at - * once. */ MOCK_IMPL(void, connection_write_to_buf_impl_,(const char *string, size_t len, @@ -4064,9 +4060,9 @@ connection_write_to_buf_impl_,(const char *string, size_t len, if (zlib) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); int done = zlib < 0; - CONN_LOG_PROTECT(conn, r = write_to_buf_zlib(conn->outbuf, - dir_conn->zlib_state, - string, len, done)); + CONN_LOG_PROTECT(conn, r = write_to_buf_compress(conn->outbuf, + dir_conn->compress_state, + string, len, done)); } else { CONN_LOG_PROTECT(conn, r = write_to_buf(string, len, conn->outbuf)); } diff --git a/src/or/connection.h b/src/or/connection.h index d25e002fa4..36e45aef38 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -141,17 +141,17 @@ MOCK_DECL(void, connection_write_to_buf_impl_, /* DOCDOC connection_write_to_buf */ static void connection_write_to_buf(const char *string, size_t len, connection_t *conn); -/* DOCDOC connection_write_to_buf_zlib */ -static void connection_write_to_buf_zlib(const char *string, size_t len, - dir_connection_t *conn, int done); +/* DOCDOC connection_write_to_buf_compress */ +static void connection_write_to_buf_compress(const char *string, size_t len, + dir_connection_t *conn, int done); static inline void connection_write_to_buf(const char *string, size_t len, connection_t *conn) { connection_write_to_buf_impl_(string, len, conn, 0); } static inline void -connection_write_to_buf_zlib(const char *string, size_t len, - dir_connection_t *conn, int done) +connection_write_to_buf_compress(const char *string, size_t len, + dir_connection_t *conn, int done) { connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1); } diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index c8e32cfacb..8480a35458 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -29,7 +29,7 @@ * <li>DNS lookup streams, created on the exit side in response to * a RELAY_RESOLVE cell from a client. * <li>Tunneled directory streams, created on the directory cache side - * in response to a RELAY_BEGINDIR cell. These streams attach directly + * in response to a RELAY_BEGIN_DIR cell. These streams attach directly * to a dir_connection_t object without ever using TCP. * </ul> * @@ -1763,7 +1763,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, conn->entry_cfg.ipv6_traffic = 0; /* Still handling CONNECT. Now, check for exit enclaves. (Which we - * don't do on BEGINDIR, or when there is a chosen exit.) + * don't do on BEGIN_DIR, or when there is a chosen exit.) * * TODO: Should we remove this? Exit enclaves are nutty and don't * work very well @@ -2528,7 +2528,7 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn) /* Sensitive directory connections must have an anonymous path length. * Otherwise, directory connections are typically one-hop. * This matches the earlier check for directory connection path anonymity - * in directory_initiate_command_rend(). */ + * in directory_initiate_request(). */ if (purpose_needs_anonymity(linked_dir_conn_base->purpose, TO_DIR_CONN(linked_dir_conn_base)->router_purpose, TO_DIR_CONN(linked_dir_conn_base)->requested_resource)) { @@ -3002,7 +3002,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, return; } -/** Read a RELAY_BEGIN or RELAY_BEGINDIR cell from <b>cell</b>, decode it, and +/** Read a RELAY_BEGIN or RELAY_BEGIN_DIR cell from <b>cell</b>, decode it, and * place the result in <b>bcell</b>. On success return 0; on failure return * <0 and set *<b>end_reason_out</b> to the end reason we should send back to * the client. @@ -3141,15 +3141,13 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) port = bcell.port; if (or_circ && or_circ->p_chan) { - if (!options->AllowSingleHopExits && - (or_circ->is_first_hop || - (!connection_or_digest_is_known_relay( + if ((or_circ->is_first_hop || + (!connection_or_digest_is_known_relay( or_circ->p_chan->identity_digest) && should_refuse_unknown_exits(options)))) { - /* Don't let clients use us as a single-hop proxy, unless the user - * has explicitly allowed that in the config. It attracts attackers - * and users who'd be better off with, well, single-hop proxies. - */ + /* Don't let clients use us as a single-hop proxy. It attracts + * attackers and users who'd be better off with, well, single-hop + * proxies. */ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Attempt by %s to open a stream %s. Closing.", safe_str(channel_get_canonical_remote_descr(or_circ->p_chan)), diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 61b5752aed..e4780b3c7d 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/connection_or.c b/src/or/connection_or.c index e7a55a80a6..59a9bdffda 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -55,6 +55,7 @@ #include "ext_orport.h" #include "scheduler.h" #include "torcert.h" +#include "channelpadding.h" static int connection_tls_finish_handshake(or_connection_t *conn); static int connection_or_launch_v3_or_handshake(or_connection_t *conn); @@ -816,24 +817,6 @@ connection_or_update_token_buckets(smartlist_t *conns, }); } -/** How long do we wait before killing non-canonical OR connections with no - * circuits? In Tor versions up to 0.2.1.25 and 0.2.2.12-alpha, we waited 15 - * minutes before cancelling these connections, which caused fast relays to - * accrue many many idle connections. Hopefully 3-4.5 minutes is low enough - * that it kills most idle connections, without being so low that we cause - * clients to bounce on and off. - * - * For canonical connections, the limit is higher, at 15-22.5 minutes. - * - * For each OR connection, we randomly add up to 50% extra to its idle_timeout - * field, to avoid exposing when exactly the last circuit closed. Since we're - * storing idle_timeout in a uint16_t, don't let these values get higher than - * 12 hours or so without revising connection_or_set_canonical and/or expanding - * idle_timeout. - */ -#define IDLE_OR_CONN_TIMEOUT_NONCANONICAL 180 -#define IDLE_OR_CONN_TIMEOUT_CANONICAL 900 - /* Mark <b>or_conn</b> as canonical if <b>is_canonical</b> is set, and * non-canonical otherwise. Adjust idle_timeout accordingly. */ @@ -841,9 +824,6 @@ void connection_or_set_canonical(or_connection_t *or_conn, int is_canonical) { - const unsigned int timeout_base = is_canonical ? - IDLE_OR_CONN_TIMEOUT_CANONICAL : IDLE_OR_CONN_TIMEOUT_NONCANONICAL; - if (bool_eq(is_canonical, or_conn->is_canonical) && or_conn->idle_timeout != 0) { /* Don't recalculate an existing idle_timeout unless the canonical @@ -852,7 +832,14 @@ connection_or_set_canonical(or_connection_t *or_conn, } or_conn->is_canonical = !! is_canonical; /* force to a 1-bit boolean */ - or_conn->idle_timeout = timeout_base + crypto_rand_int(timeout_base / 2); + or_conn->idle_timeout = channelpadding_get_channel_idle_timeout( + TLS_CHAN_TO_BASE(or_conn->chan), is_canonical); + + log_info(LD_CIRC, + "Channel " U64_FORMAT " chose an idle timeout of %d.", + or_conn->chan ? + U64_PRINTF_ARG(TLS_CHAN_TO_BASE(or_conn->chan)->global_identifier):0, + or_conn->idle_timeout); } /** If we don't necessarily know the router we're connecting to, but we @@ -1055,10 +1042,8 @@ connection_or_group_set_badness_(smartlist_t *group, int force) } if (!best || - channel_is_better(now, - TLS_CHAN_TO_BASE(or_conn->chan), - TLS_CHAN_TO_BASE(best->chan), - 0)) { + channel_is_better(TLS_CHAN_TO_BASE(or_conn->chan), + TLS_CHAN_TO_BASE(best->chan))) { best = or_conn; } } SMARTLIST_FOREACH_END(or_conn); @@ -1086,11 +1071,9 @@ connection_or_group_set_badness_(smartlist_t *group, int force) or_conn->base_.state != OR_CONN_STATE_OPEN) continue; if (or_conn != best && - channel_is_better(now, - TLS_CHAN_TO_BASE(best->chan), - TLS_CHAN_TO_BASE(or_conn->chan), 1)) { - /* This isn't the best conn, _and_ the best conn is better than it, - even when we're being forgiving. */ + channel_is_better(TLS_CHAN_TO_BASE(best->chan), + TLS_CHAN_TO_BASE(or_conn->chan))) { + /* This isn't the best conn, _and_ the best conn is better than it */ if (best->is_canonical) { log_info(LD_OR, "Marking OR conn to %s:%d as unsuitable for new circuits: " @@ -1566,7 +1549,9 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, } if (identity_rcvd) { - crypto_pk_get_digest(identity_rcvd, digest_rcvd_out); + if (crypto_pk_get_digest(identity_rcvd, digest_rcvd_out) < 0) { + return -1; + } } else { memset(digest_rcvd_out, 0, DIGEST_LEN); } @@ -1989,12 +1974,23 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn) cell_pack(&networkcell, cell, conn->wide_circ_ids); + rep_hist_padding_count_write(PADDING_TYPE_TOTAL); + if (cell->command == CELL_PADDING) + rep_hist_padding_count_write(PADDING_TYPE_CELL); + connection_write_to_buf(networkcell.body, cell_network_size, TO_CONN(conn)); /* Touch the channel's active timestamp if there is one */ - if (conn->chan) + if (conn->chan) { channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); + if (TLS_CHAN_TO_BASE(conn->chan)->currently_padding) { + rep_hist_padding_count_write(PADDING_TYPE_ENABLED_TOTAL); + if (cell->command == CELL_PADDING) + rep_hist_padding_count_write(PADDING_TYPE_ENABLED_CELL); + } + } + if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_cell(conn, conn->handshake_state, cell, 0); } @@ -2100,7 +2096,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) } /** Array of recognized link protocol versions. */ -static const uint16_t or_protocol_versions[] = { 1, 2, 3, 4 }; +static const uint16_t or_protocol_versions[] = { 1, 2, 3, 4, 5 }; /** Number of versions in <b>or_protocol_versions</b>. */ static const int n_or_protocol_versions = (int)( sizeof(or_protocol_versions)/sizeof(uint16_t) ); diff --git a/src/or/connection_or.h b/src/or/connection_or.h index 514a0fd008..fe85a3f5fd 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -109,6 +109,8 @@ void var_cell_free(var_cell_t *cell); /* DOCDOC */ #define MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS 4 +#define MIN_LINK_PROTO_FOR_CHANNEL_PADDING 5 +#define MAX_LINK_PROTO MIN_LINK_PROTO_FOR_CHANNEL_PADDING void connection_or_group_set_badness_(smartlist_t *group, int force); diff --git a/src/or/conscache.c b/src/or/conscache.c new file mode 100644 index 0000000000..33a5495974 --- /dev/null +++ b/src/or/conscache.c @@ -0,0 +1,626 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" + +#include "config.h" +#include "conscache.h" +#include "storagedir.h" + +#define CCE_MAGIC 0x17162253 + +#ifdef _WIN32 +/* On Windows, unlink won't work on a file if the file is actively mmap()ed. + * That forces us to be less aggressive about unlinking files, and causes other + * changes throughout our logic. + */ +#define MUST_UNMAP_TO_UNLINK +#endif + +/** + * A consensus_cache_entry_t is a reference-counted handle to an + * item in a consensus_cache_t. It can be mmapped into RAM, or not, + * depending whether it's currently in use. + */ +struct consensus_cache_entry_t { + uint32_t magic; /**< Must be set to CCE_MAGIC */ + HANDLE_ENTRY(consensus_cache_entry, consensus_cache_entry_t); + int32_t refcnt; /**< Reference count. */ + unsigned can_remove : 1; /**< If true, we want to delete this file. */ + /** If true, we intend to unmap this file as soon as we're done with it. */ + unsigned release_aggressively : 1; + + /** Filename for this object within the storage_dir_t */ + char *fname; + /** Labels associated with this object. Immutable once the object + * is created. */ + config_line_t *labels; + /** Pointer to the cache that includes this entry (if any). */ + consensus_cache_t *in_cache; + + /** Since what time has this object been mapped into RAM, but with the cache + * being the only having a reference to it? */ + time_t unused_since; + /** mmaped contents of the underlying file. May be NULL */ + tor_mmap_t *map; + /** Length of the body within <b>map</b>. */ + size_t bodylen; + /** Pointer to the body within <b>map</b>. */ + const uint8_t *body; +}; + +/** + * A consensus_cache_t holds a directory full of labeled items. + */ +struct consensus_cache_t { + /** Underling storage_dir_t to handle persistence */ + storage_dir_t *dir; + /** List of all the entries in the directory. */ + smartlist_t *entries; + + /** The maximum number of entries that we'd like to allow in this cache. + * This is the same as the storagedir limit when MUST_UNMAP_TO_UNLINK is + * not defined. */ + unsigned max_entries; +}; + +static void consensus_cache_clear(consensus_cache_t *cache); +static void consensus_cache_rescan(consensus_cache_t *); +static void consensus_cache_entry_map(consensus_cache_t *, + consensus_cache_entry_t *); +static void consensus_cache_entry_unmap(consensus_cache_entry_t *ent); + +/** + * Helper: Open a consensus cache in subdirectory <b>subdir</b> of the + * data directory, to hold up to <b>max_entries</b> of data. + */ +consensus_cache_t * +consensus_cache_open(const char *subdir, int max_entries) +{ + int storagedir_max_entries; + consensus_cache_t *cache = tor_malloc_zero(sizeof(consensus_cache_t)); + char *directory = get_datadir_fname(subdir); + cache->max_entries = max_entries; + +#ifdef MUST_UNMAP_TO_UNLINK + /* If we can't unlink the files that we're still using, then we need to + * tell the storagedir backend to allow far more files than this consensus + * cache actually wants, so that it can hold files which, from this cache's + * perspective, have become useless. + */ +#define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) + storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; +#else + /* Otherwise, we can just tell the storagedir to use the same limits + * as this cache. */ + storagedir_max_entries = max_entries; +#endif + + cache->dir = storage_dir_new(directory, storagedir_max_entries); + tor_free(directory); + if (!cache->dir) { + tor_free(cache); + return NULL; + } + + consensus_cache_rescan(cache); + return cache; +} + +/** Return true if it's okay to put more entries in this cache than + * its official file limit. + * + * (We need this method on Windows, where we can't unlink files that are still + * in use, and therefore might need to temporarily exceed the file limit until + * the no-longer-wanted files are deletable.) + */ +int +consensus_cache_may_overallocate(consensus_cache_t *cache) +{ + (void) cache; +#ifdef MUST_UNMAP_TO_UNLINK + return 1; +#else + return 0; +#endif +} + +/** + * Tell the sandbox (if any) configured by <b>cfg</b> to allow the + * operations that <b>cache</b> will need. + */ +int +consensus_cache_register_with_sandbox(consensus_cache_t *cache, + struct sandbox_cfg_elem **cfg) +{ +#ifdef MUST_UNMAP_TO_UNLINK + /* Our Linux sandbox doesn't support huge file lists like the one that would + * be generated by using VERY_LARGE_STORAGEDIR_LIMIT above in + * consensus_cache_open(). Since the Linux sandbox is the only one we have + * right now, we just assert that we never reach this point when we've had + * to use VERY_LARGE_STORAGEDIR_LIMIT. + * + * If at some point in the future we have a different sandbox mechanism that + * can handle huge file lists, we can remove this assertion or make it + * conditional. + */ + tor_assert_nonfatal_unreached(); +#endif + return storage_dir_register_with_sandbox(cache->dir, cfg); +} + +/** + * Helper: clear all entries from <b>cache</b> (but do not delete + * any that aren't marked for removal + */ +static void +consensus_cache_clear(consensus_cache_t *cache) +{ + consensus_cache_delete_pending(cache, 0); + + SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) { + ent->in_cache = NULL; + consensus_cache_entry_decref(ent); + } SMARTLIST_FOREACH_END(ent); + smartlist_free(cache->entries); + cache->entries = NULL; +} + +/** + * Drop all storage held by <b>cache</b>. + */ +void +consensus_cache_free(consensus_cache_t *cache) +{ + if (! cache) + return; + + if (cache->entries) { + consensus_cache_clear(cache); + } + storage_dir_free(cache->dir); + tor_free(cache); +} + +/** + * Write <b>datalen</b> bytes of data at <b>data</b> into the <b>cache</b>, + * labeling that data with <b>labels</b>. On failure, return NULL. On + * success, return a newly created consensus_cache_entry_t. + * + * The returned value will be owned by the cache, and you will have a + * reference to it. Call consensus_cache_entry_decref() when you are + * done with it. + * + * The provided <b>labels</b> MUST have distinct keys: if they don't, + * this API does not specify which values (if any) for the duplicate keys + * will be considered. + */ +consensus_cache_entry_t * +consensus_cache_add(consensus_cache_t *cache, + const config_line_t *labels, + const uint8_t *data, + size_t datalen) +{ + char *fname = NULL; + int r = storage_dir_save_labeled_to_file(cache->dir, + labels, data, datalen, &fname); + if (r < 0 || fname == NULL) { + return NULL; + } + consensus_cache_entry_t *ent = + tor_malloc_zero(sizeof(consensus_cache_entry_t)); + ent->magic = CCE_MAGIC; + ent->fname = fname; + ent->labels = config_lines_dup(labels); + ent->in_cache = cache; + ent->unused_since = TIME_MAX; + smartlist_add(cache->entries, ent); + /* Start the reference count at 2: the caller owns one copy, and the + * cache owns another. + */ + ent->refcnt = 2; + + return ent; +} + +/** + * Given a <b>cache</b>, return some entry for which <b>key</b>=<b>value</b>. + * Return NULL if no such entry exists. + * + * Does not adjust reference counts. + */ +consensus_cache_entry_t * +consensus_cache_find_first(consensus_cache_t *cache, + const char *key, + const char *value) +{ + smartlist_t *tmp = smartlist_new(); + consensus_cache_find_all(tmp, cache, key, value); + consensus_cache_entry_t *ent = NULL; + if (smartlist_len(tmp)) + ent = smartlist_get(tmp, 0); + smartlist_free(tmp); + return ent; +} + +/** + * Given a <b>cache</b>, add every entry to <b>out<b> for which + * <b>key</b>=<b>value</b>. If <b>key</b> is NULL, add every entry. + * + * Do not add any entry that has been marked for removal. + * + * Does not adjust reference counts. + */ +void +consensus_cache_find_all(smartlist_t *out, + consensus_cache_t *cache, + const char *key, + const char *value) +{ + SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) { + if (ent->can_remove == 1) { + /* We want to delete this; pretend it isn't there. */ + continue; + } + if (! key) { + smartlist_add(out, ent); + continue; + } + const char *found_val = consensus_cache_entry_get_value(ent, key); + if (found_val && !strcmp(value, found_val)) { + smartlist_add(out, ent); + } + } SMARTLIST_FOREACH_END(ent); +} + +/** + * Given a list of consensus_cache_entry_t, remove all those entries + * that do not have <b>key</b>=<b>value</b> in their labels. + * + * Does not adjust reference counts. + */ +void +consensus_cache_filter_list(smartlist_t *lst, + const char *key, + const char *value) +{ + if (BUG(lst == NULL)) + return; // LCOV_EXCL_LINE + if (key == NULL) + return; + SMARTLIST_FOREACH_BEGIN(lst, consensus_cache_entry_t *, ent) { + const char *found_val = consensus_cache_entry_get_value(ent, key); + if (! found_val || strcmp(value, found_val)) { + SMARTLIST_DEL_CURRENT(lst, ent); + } + } SMARTLIST_FOREACH_END(ent); +} + +/** + * If <b>ent</b> has a label with the given <b>key</b>, return its + * value. Otherwise return NULL. + * + * The return value is only guaranteed to be valid for as long as you + * hold a reference to <b>ent</b>. + */ +const char * +consensus_cache_entry_get_value(const consensus_cache_entry_t *ent, + const char *key) +{ + const config_line_t *match = config_line_find(ent->labels, key); + if (match) + return match->value; + else + return NULL; +} + +/** + * Return a pointer to the labels in <b>ent</b>. + * + * This pointer is only guaranteed to be valid for as long as you + * hold a reference to <b>ent</b>. + */ +const config_line_t * +consensus_cache_entry_get_labels(const consensus_cache_entry_t *ent) +{ + return ent->labels; +} + +/** + * Increase the reference count of <b>ent</b>. + */ +void +consensus_cache_entry_incref(consensus_cache_entry_t *ent) +{ + if (BUG(ent->magic != CCE_MAGIC)) + return; // LCOV_EXCL_LINE + ++ent->refcnt; + ent->unused_since = TIME_MAX; +} + +/** + * Release a reference held to <b>ent</b>. + * + * If it was the last reference, ent will be freed. Therefore, you must not + * use <b>ent</b> after calling this function. + */ +void +consensus_cache_entry_decref(consensus_cache_entry_t *ent) +{ + if (! ent) + return; + if (BUG(ent->refcnt <= 0)) + return; // LCOV_EXCL_LINE + if (BUG(ent->magic != CCE_MAGIC)) + return; // LCOV_EXCL_LINE + + --ent->refcnt; + + if (ent->refcnt == 1 && ent->in_cache) { + /* Only the cache has a reference: we don't need to keep the file + * mapped */ + if (ent->map) { + if (ent->release_aggressively) { + consensus_cache_entry_unmap(ent); + } else { + ent->unused_since = approx_time(); + } + } + return; + } + + if (ent->refcnt > 0) + return; + + /* Refcount is zero; we can free it. */ + if (ent->map) { + consensus_cache_entry_unmap(ent); + } + tor_free(ent->fname); + config_free_lines(ent->labels); + consensus_cache_entry_handles_clear(ent); + memwipe(ent, 0, sizeof(consensus_cache_entry_t)); + tor_free(ent); +} + +/** + * Mark <b>ent</b> for deletion from the cache. Deletion will not occur + * until the cache is the only place that holds a reference to <b>ent</b>. + */ +void +consensus_cache_entry_mark_for_removal(consensus_cache_entry_t *ent) +{ + ent->can_remove = 1; +} + +/** + * Mark <b>ent</b> as the kind of entry that we don't need to keep mmap'd for + * any longer than we're actually using it. + */ +void +consensus_cache_entry_mark_for_aggressive_release(consensus_cache_entry_t *ent) +{ + ent->release_aggressively = 1; +} + +/** + * Try to read the body of <b>ent</b> into memory if it isn't already + * loaded. On success, set *<b>body_out</b> to the body, *<b>sz_out</b> + * to its size, and return 0. On failure return -1. + * + * The resulting body pointer will only be valid for as long as you + * hold a reference to <b>ent</b>. + */ +int +consensus_cache_entry_get_body(const consensus_cache_entry_t *ent, + const uint8_t **body_out, + size_t *sz_out) +{ + if (BUG(ent->magic != CCE_MAGIC)) + return -1; // LCOV_EXCL_LINE + + if (! ent->map) { + if (! ent->in_cache) + return -1; + + consensus_cache_entry_map((consensus_cache_t *)ent->in_cache, + (consensus_cache_entry_t *)ent); + if (! ent->map) { + return -1; + } + } + + *body_out = ent->body; + *sz_out = ent->bodylen; + return 0; +} + +/** + * Unmap every mmap'd element of <b>cache</b> that has been unused + * since <b>cutoff</b>. + */ +void +consensus_cache_unmap_lazy(consensus_cache_t *cache, time_t cutoff) +{ + SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) { + tor_assert_nonfatal(ent->in_cache == cache); + if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) { + /* Somebody is using this entry right now */ + continue; + } + if (ent->unused_since > cutoff) { + /* Has been unused only for a little while */ + continue; + } + if (ent->map == NULL) { + /* Not actually mapped. */ + continue; + } + consensus_cache_entry_unmap(ent); + } SMARTLIST_FOREACH_END(ent); +} + +/** + * Return the number of currently unused filenames available in this cache. + */ +int +consensus_cache_get_n_filenames_available(consensus_cache_t *cache) +{ + tor_assert(cache); + int max = cache->max_entries; + int used = smartlist_len(storage_dir_list(cache->dir)); +#ifdef MUST_UNMAP_TO_UNLINK + if (used > max) + return 0; +#else + tor_assert_nonfatal(max >= used); +#endif + return max - used; +} + +/** + * Delete every element of <b>cache</b> has been marked with + * consensus_cache_entry_mark_for_removal. If <b>force</b> is false, + * retain those entries which are in use by something other than the cache. + */ +void +consensus_cache_delete_pending(consensus_cache_t *cache, int force) +{ + SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) { + tor_assert_nonfatal(ent->in_cache == cache); + int force_ent = force; +#ifdef MUST_UNMAP_TO_UNLINK + /* We cannot delete anything with an active mmap on win32, so no + * force-deletion. */ + if (ent->map) { + force_ent = 0; + } +#endif + if (! force_ent) { + if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) { + /* Somebody is using this entry right now */ + continue; + } + } + if (ent->can_remove == 0) { + /* Don't want to delete this. */ + continue; + } + if (BUG(ent->refcnt <= 0)) { + continue; // LCOV_EXCL_LINE + } + + SMARTLIST_DEL_CURRENT(cache->entries, ent); + ent->in_cache = NULL; + char *fname = tor_strdup(ent->fname); /* save a copy */ + consensus_cache_entry_decref(ent); + storage_dir_remove_file(cache->dir, fname); + tor_free(fname); + } SMARTLIST_FOREACH_END(ent); +} + +/** + * Internal helper: rescan <b>cache</b> and rebuild its list of entries. + */ +static void +consensus_cache_rescan(consensus_cache_t *cache) +{ + if (cache->entries) { + consensus_cache_clear(cache); + } + + cache->entries = smartlist_new(); + const smartlist_t *fnames = storage_dir_list(cache->dir); + SMARTLIST_FOREACH_BEGIN(fnames, const char *, fname) { + tor_mmap_t *map = NULL; + config_line_t *labels = NULL; + const uint8_t *body; + size_t bodylen; + map = storage_dir_map_labeled(cache->dir, fname, + &labels, &body, &bodylen); + if (! map) { + /* 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 = + tor_malloc_zero(sizeof(consensus_cache_entry_t)); + ent->magic = CCE_MAGIC; + ent->fname = tor_strdup(fname); + ent->labels = labels; + ent->refcnt = 1; + ent->in_cache = cache; + ent->unused_since = TIME_MAX; + smartlist_add(cache->entries, ent); + tor_munmap_file(map); /* don't actually need to keep this around */ + } SMARTLIST_FOREACH_END(fname); +} + +/** + * Make sure that <b>ent</b> is mapped into RAM. + */ +static void +consensus_cache_entry_map(consensus_cache_t *cache, + consensus_cache_entry_t *ent) +{ + if (ent->map) + return; + + ent->map = storage_dir_map_labeled(cache->dir, ent->fname, + NULL, &ent->body, &ent->bodylen); + ent->unused_since = TIME_MAX; +} + +/** + * Unmap <b>ent</b> from RAM. + * + * Do not call this if something other than the cache is holding a reference + * to <b>ent</b> + */ +static void +consensus_cache_entry_unmap(consensus_cache_entry_t *ent) +{ + ent->unused_since = TIME_MAX; + if (!ent->map) + return; + + tor_munmap_file(ent->map); + ent->map = NULL; + ent->body = NULL; + ent->bodylen = 0; + ent->unused_since = TIME_MAX; +} + +HANDLE_IMPL(consensus_cache_entry, consensus_cache_entry_t, ) + +#ifdef TOR_UNIT_TESTS +/** + * Testing only: Return true iff <b>ent</b> is mapped into memory. + * + * (In normal operation, this information is not exposed.) + */ +int +consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent) +{ + if (ent->map) { + tor_assert(ent->body); + return 1; + } else { + tor_assert(!ent->body); + return 0; + } +} +#endif + diff --git a/src/or/conscache.h b/src/or/conscache.h new file mode 100644 index 0000000000..a0d74c4e08 --- /dev/null +++ b/src/or/conscache.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_CONSCACHE_H +#define TOR_CONSCACHE_H + +#include "handles.h" + +typedef struct consensus_cache_entry_t consensus_cache_entry_t; +typedef struct consensus_cache_t consensus_cache_t; + +HANDLE_DECL(consensus_cache_entry, consensus_cache_entry_t, ) + +consensus_cache_t *consensus_cache_open(const char *subdir, int max_entries); +void consensus_cache_free(consensus_cache_t *cache); +struct sandbox_cfg_elem; +int consensus_cache_may_overallocate(consensus_cache_t *cache); +int consensus_cache_register_with_sandbox(consensus_cache_t *cache, + struct sandbox_cfg_elem **cfg); +void consensus_cache_unmap_lazy(consensus_cache_t *cache, time_t cutoff); +void consensus_cache_delete_pending(consensus_cache_t *cache, + int force); +int consensus_cache_get_n_filenames_available(consensus_cache_t *cache); +consensus_cache_entry_t *consensus_cache_add(consensus_cache_t *cache, + const config_line_t *labels, + const uint8_t *data, + size_t datalen); + +consensus_cache_entry_t *consensus_cache_find_first( + consensus_cache_t *cache, + const char *key, + const char *value); + +void consensus_cache_find_all(smartlist_t *out, + consensus_cache_t *cache, + const char *key, + const char *value); +void consensus_cache_filter_list(smartlist_t *lst, + const char *key, + const char *value); + +const char *consensus_cache_entry_get_value(const consensus_cache_entry_t *ent, + const char *key); +const config_line_t *consensus_cache_entry_get_labels( + const consensus_cache_entry_t *ent); + +void consensus_cache_entry_incref(consensus_cache_entry_t *ent); +void consensus_cache_entry_decref(consensus_cache_entry_t *ent); + +void consensus_cache_entry_mark_for_removal(consensus_cache_entry_t *ent); +void consensus_cache_entry_mark_for_aggressive_release( + consensus_cache_entry_t *ent); +int consensus_cache_entry_get_body(const consensus_cache_entry_t *ent, + const uint8_t **body_out, + size_t *sz_out); + +#ifdef TOR_UNIT_TESTS +int consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent); +#endif + +#endif + diff --git a/src/or/consdiff.c b/src/or/consdiff.c new file mode 100644 index 0000000000..1baa11897c --- /dev/null +++ b/src/or/consdiff.c @@ -0,0 +1,1412 @@ +/* Copyright (c) 2014, Daniel Martà + * Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file consdiff.c + * \brief Consensus diff implementation, including both the generation and the + * application of diffs in a minimal ed format. + * + * The consensus diff application is done in consdiff_apply_diff, which relies + * on apply_ed_diff for the main ed diff part and on some digest helper + * functions to check the digest hashes found in the consensus diff header. + * + * The consensus diff generation is more complex. consdiff_gen_diff generates + * it, relying on gen_ed_diff to generate the ed diff and some digest helper + * functions to generate the digest hashes. + * + * gen_ed_diff is the tricky bit. In it simplest form, it will take quadratic + * time and linear space to generate an ed diff given two smartlists. As shown + * in its comment section, calling calc_changes on the entire two consensuses + * will calculate what is to be added and what is to be deleted in the diff. + * Its comment section briefly explains how it works. + * + * In our case specific to consensuses, we take advantage of the fact that + * consensuses list routers sorted by their identities. We use that + * information to avoid running calc_changes on the whole smartlists. + * gen_ed_diff will navigate through the two consensuses identity by identity + * and will send small couples of slices to calc_changes, keeping the running + * time near-linear. This is explained in more detail in the gen_ed_diff + * comments. + * + * The allocation strategy tries to save time and memory by avoiding needless + * copies. Instead of actually splitting the inputs into separate strings, we + * allocate cdline_t objects, each of which represents a line in the original + * object or in the output. We use memarea_t allocators to manage the + * temporary memory we use when generating or applying diffs. + **/ + +#define CONSDIFF_PRIVATE + +#include "or.h" +#include "consdiff.h" +#include "memarea.h" +#include "routerparse.h" + +static const char* ns_diff_version = "network-status-diff-version 1"; +static const char* hash_token = "hash"; + +static char *consensus_join_lines(const smartlist_t *inp); + +/** Return true iff a and b have the same contents. */ +STATIC int +lines_eq(const cdline_t *a, const cdline_t *b) +{ + return a->len == b->len && fast_memeq(a->s, b->s, a->len); +} + +/** Return true iff a has the same contents as the nul-terminated string b. */ +STATIC int +line_str_eq(const cdline_t *a, const char *b) +{ + const size_t len = strlen(b); + tor_assert(len <= UINT32_MAX); + cdline_t bline = { b, (uint32_t)len }; + return lines_eq(a, &bline); +} + +/** Return true iff a begins with the same contents as the nul-terminated + * string b. */ +static int +line_starts_with_str(const cdline_t *a, const char *b) +{ + const size_t len = strlen(b); + tor_assert(len <= UINT32_MAX); + return a->len >= len && fast_memeq(a->s, b, len); +} + +/** Return a new cdline_t holding as its contents the nul-terminated + * string s. Use the provided memory area for storage. */ +static cdline_t * +cdline_linecpy(memarea_t *area, const char *s) +{ + size_t len = strlen(s); + const char *ss = memarea_memdup(area, s, len); + cdline_t *line = memarea_alloc(area, sizeof(cdline_t)); + line->s = ss; + line->len = (uint32_t)len; + return line; +} + +/** Add a cdline_t to <b>lst</b> holding as its contents the nul-terminated + * string s. Use the provided memory area for storage. */ +STATIC void +smartlist_add_linecpy(smartlist_t *lst, memarea_t *area, const char *s) +{ + smartlist_add(lst, cdline_linecpy(area, s)); +} + +/** Compute the digest of <b>cons</b>, and store the result in + * <b>digest_out</b>. Return 0 on success, -1 on failure. */ +/* This is a separate, mockable function so that we can override it when + * fuzzing. */ +MOCK_IMPL(STATIC int, +consensus_compute_digest,(const char *cons, + consensus_digest_t *digest_out)) +{ + int r = crypto_digest256((char*)digest_out->sha3_256, + cons, strlen(cons), DIGEST_SHA3_256); + return r; +} + +/** Compute the digest-as-signed of <b>cons</b>, and store the result in + * <b>digest_out</b>. Return 0 on success, -1 on failure. */ +/* This is a separate, mockable function so that we can override it when + * fuzzing. */ +MOCK_IMPL(STATIC int, +consensus_compute_digest_as_signed,(const char *cons, + consensus_digest_t *digest_out)) +{ + return router_get_networkstatus_v3_sha3_as_signed(digest_out->sha3_256, + cons); +} + +/** Return true iff <b>d1</b> and <b>d2</b> contain the same digest */ +/* This is a separate, mockable function so that we can override it when + * fuzzing. */ +MOCK_IMPL(STATIC int, +consensus_digest_eq,(const uint8_t *d1, + const uint8_t *d2)) +{ + return fast_memeq(d1, d2, DIGEST256_LEN); +} + +/** Create (allocate) a new slice from a smartlist. Assumes that the start + * and the end indexes are within the bounds of the initial smartlist. The end + * element is not part of the resulting slice. If end is -1, the slice is to + * reach the end of the smartlist. + */ +STATIC smartlist_slice_t * +smartlist_slice(const smartlist_t *list, int start, int end) +{ + int list_len = smartlist_len(list); + tor_assert(start >= 0); + tor_assert(start <= list_len); + if (end == -1) { + end = list_len; + } + tor_assert(start <= end); + + smartlist_slice_t *slice = tor_malloc(sizeof(smartlist_slice_t)); + slice->list = list; + slice->offset = start; + slice->len = end - start; + return slice; +} + +/** Helper: Compute the longest common subsequence lengths for the two slices. + * Used as part of the diff generation to find the column at which to split + * slice2 while still having the optimal solution. + * If direction is -1, the navigation is reversed. Otherwise it must be 1. + * The length of the resulting integer array is that of the second slice plus + * one. + */ +STATIC int * +lcs_lengths(const smartlist_slice_t *slice1, const smartlist_slice_t *slice2, + int direction) +{ + size_t a_size = sizeof(int) * (slice2->len+1); + + /* Resulting lcs lengths. */ + int *result = tor_malloc_zero(a_size); + /* Copy of the lcs lengths from the last iteration. */ + int *prev = tor_malloc(a_size); + + tor_assert(direction == 1 || direction == -1); + + int si = slice1->offset; + if (direction == -1) { + si += (slice1->len-1); + } + + for (int i = 0; i < slice1->len; ++i, si+=direction) { + + const cdline_t *line1 = smartlist_get(slice1->list, si); + /* Store the last results. */ + memcpy(prev, result, a_size); + + int sj = slice2->offset; + if (direction == -1) { + sj += (slice2->len-1); + } + + for (int j = 0; j < slice2->len; ++j, sj+=direction) { + + const cdline_t *line2 = smartlist_get(slice2->list, sj); + if (lines_eq(line1, line2)) { + /* If the lines are equal, the lcs is one line longer. */ + result[j + 1] = prev[j] + 1; + } else { + /* If not, see what lcs parent path is longer. */ + result[j + 1] = MAX(result[j], prev[j + 1]); + } + } + } + tor_free(prev); + return result; +} + +/** Helper: Trim any number of lines that are equally at the start or the end + * of both slices. + */ +STATIC void +trim_slices(smartlist_slice_t *slice1, smartlist_slice_t *slice2) +{ + while (slice1->len>0 && slice2->len>0) { + const cdline_t *line1 = smartlist_get(slice1->list, slice1->offset); + const cdline_t *line2 = smartlist_get(slice2->list, slice2->offset); + if (!lines_eq(line1, line2)) { + break; + } + slice1->offset++; slice1->len--; + slice2->offset++; slice2->len--; + } + + int i1 = (slice1->offset+slice1->len)-1; + int i2 = (slice2->offset+slice2->len)-1; + + while (slice1->len>0 && slice2->len>0) { + const cdline_t *line1 = smartlist_get(slice1->list, i1); + const cdline_t *line2 = smartlist_get(slice2->list, i2); + if (!lines_eq(line1, line2)) { + break; + } + i1--; + slice1->len--; + i2--; + slice2->len--; + } +} + +/** Like smartlist_string_pos, but uses a cdline_t, and is restricted to the + * bounds of the slice. + */ +STATIC int +smartlist_slice_string_pos(const smartlist_slice_t *slice, + const cdline_t *string) +{ + int end = slice->offset + slice->len; + for (int i = slice->offset; i < end; ++i) { + const cdline_t *el = smartlist_get(slice->list, i); + if (lines_eq(el, string)) { + return i; + } + } + return -1; +} + +/** Helper: Set all the appropriate changed booleans to true. The first slice + * must be of length 0 or 1. All the lines of slice1 and slice2 which are not + * present in the other slice will be set to changed in their bool array. + * The two changed bool arrays are passed in the same order as the slices. + */ +STATIC void +set_changed(bitarray_t *changed1, bitarray_t *changed2, + const smartlist_slice_t *slice1, const smartlist_slice_t *slice2) +{ + int toskip = -1; + tor_assert(slice1->len == 0 || slice1->len == 1); + + if (slice1->len == 1) { + const cdline_t *line_common = smartlist_get(slice1->list, slice1->offset); + toskip = smartlist_slice_string_pos(slice2, line_common); + if (toskip == -1) { + bitarray_set(changed1, slice1->offset); + } + } + int end = slice2->offset + slice2->len; + for (int i = slice2->offset; i < end; ++i) { + if (i != toskip) { + bitarray_set(changed2, i); + } + } +} + +/* + * Helper: Given that slice1 has been split by half into top and bot, we want + * to fetch the column at which to split slice2 so that we are still on track + * to the optimal diff solution, i.e. the shortest one. We use lcs_lengths + * since the shortest diff is just another way to say the longest common + * subsequence. + */ +static int +optimal_column_to_split(const smartlist_slice_t *top, + const smartlist_slice_t *bot, + const smartlist_slice_t *slice2) +{ + int *lens_top = lcs_lengths(top, slice2, 1); + int *lens_bot = lcs_lengths(bot, slice2, -1); + int column=0, max_sum=-1; + + for (int i = 0; i < slice2->len+1; ++i) { + int sum = lens_top[i] + lens_bot[slice2->len-i]; + if (sum > max_sum) { + column = i; + max_sum = sum; + } + } + tor_free(lens_top); + tor_free(lens_bot); + + return column; +} + +/** + * Helper: Figure out what elements are new or gone on the second smartlist + * relative to the first smartlist, and store the booleans in the bitarrays. + * True on the first bitarray means the element is gone, true on the second + * bitarray means it's new. + * + * In its base case, either of the smartlists is of length <= 1 and we can + * quickly see what elements are new or are gone. In the other case, we will + * split one smartlist by half and we'll use optimal_column_to_split to find + * the optimal column at which to split the second smartlist so that we are + * finding the smallest diff possible. + */ +STATIC void +calc_changes(smartlist_slice_t *slice1, + smartlist_slice_t *slice2, + bitarray_t *changed1, bitarray_t *changed2) +{ + trim_slices(slice1, slice2); + + if (slice1->len <= 1) { + set_changed(changed1, changed2, slice1, slice2); + + } else if (slice2->len <= 1) { + set_changed(changed2, changed1, slice2, slice1); + + /* Keep on splitting the slices in two. */ + } else { + smartlist_slice_t *top, *bot, *left, *right; + + /* Split the first slice in half. */ + int mid = slice1->len/2; + top = smartlist_slice(slice1->list, slice1->offset, slice1->offset+mid); + bot = smartlist_slice(slice1->list, slice1->offset+mid, + slice1->offset+slice1->len); + + /* Split the second slice by the optimal column. */ + int mid2 = optimal_column_to_split(top, bot, slice2); + left = smartlist_slice(slice2->list, slice2->offset, slice2->offset+mid2); + right = smartlist_slice(slice2->list, slice2->offset+mid2, + slice2->offset+slice2->len); + + calc_changes(top, left, changed1, changed2); + calc_changes(bot, right, changed1, changed2); + tor_free(top); + tor_free(bot); + tor_free(left); + tor_free(right); + } +} + +/* This table is from crypto.c. The SP and PAD defines are different. */ +#define NOT_VALID_BASE64 255 +#define X NOT_VALID_BASE64 +#define SP NOT_VALID_BASE64 +#define PAD NOT_VALID_BASE64 +static const uint8_t base64_compare_table[256] = { + X, X, X, X, X, X, X, X, X, SP, SP, SP, X, SP, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + SP, X, X, X, X, X, X, X, X, X, X, 62, X, X, X, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, X, X, X, PAD, X, X, + X, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, X, X, X, X, X, + X, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, +}; + +/** Helper: Get the identity hash from a router line, assuming that the line + * at least appears to be a router line and thus starts with "r ". + * + * If an identity hash is found, store it (without decoding it) in + * <b>hash_out</b>, and return 0. On failure, return -1. + */ +STATIC int +get_id_hash(const cdline_t *line, cdline_t *hash_out) +{ + if (line->len < 2) + return -1; + + /* Skip the router name. */ + const char *hash = memchr(line->s + 2, ' ', line->len - 2); + if (!hash) { + return -1; + } + + hash++; + const char *hash_end = hash; + /* Stop when the first non-base64 character is found. Use unsigned chars to + * avoid negative indexes causing crashes. + */ + while (base64_compare_table[*((unsigned char*)hash_end)] + != NOT_VALID_BASE64 && + hash_end < line->s + line->len) { + hash_end++; + } + + /* Empty hash. */ + if (hash_end == hash) { + return -1; + } + + hash_out->s = hash; + /* Always true because lines are limited to this length */ + tor_assert(hash_end >= hash); + tor_assert((size_t)(hash_end - hash) <= UINT32_MAX); + hash_out->len = (uint32_t)(hash_end - hash); + + return 0; +} + +/** Helper: Check that a line is a valid router entry. We must at least be + * able to fetch a proper identity hash from it for it to be valid. + */ +STATIC int +is_valid_router_entry(const cdline_t *line) +{ + if (line->len < 2 || fast_memneq(line->s, "r ", 2)) + return 0; + cdline_t tmp; + return (get_id_hash(line, &tmp) == 0); +} + +/** Helper: Find the next router line starting at the current position. + * Assumes that cur is lower than the length of the smartlist, i.e. it is a + * line within the bounds of the consensus. The only exception is when we + * don't want to skip the first line, in which case cur will be -1. + */ +STATIC int +next_router(const smartlist_t *cons, int cur) +{ + int len = smartlist_len(cons); + tor_assert(cur >= -1 && cur < len); + + if (++cur >= len) { + return len; + } + + const cdline_t *line = smartlist_get(cons, cur); + while (!is_valid_router_entry(line)) { + if (++cur >= len) { + return len; + } + line = smartlist_get(cons, cur); + } + return cur; +} + +/** Helper: compare two base64-encoded identity hashes, which may be of + * different lengths. Comparison ends when the first non-base64 char is found. + */ +STATIC int +base64cmp(const cdline_t *hash1, const cdline_t *hash2) +{ + /* NULL is always lower, useful for last_hash which starts at NULL. */ + if (!hash1->s && !hash2->s) { + return 0; + } + if (!hash1->s) { + return -1; + } + if (!hash2->s) { + return 1; + } + + /* Don't index with a char; char may be signed. */ + const unsigned char *a = (unsigned char*)hash1->s; + const unsigned char *b = (unsigned char*)hash2->s; + const unsigned char *a_end = a + hash1->len; + const unsigned char *b_end = b + hash2->len; + while (1) { + uint8_t av = base64_compare_table[*a]; + uint8_t bv = base64_compare_table[*b]; + if (av == NOT_VALID_BASE64) { + if (bv == NOT_VALID_BASE64) { + /* Both ended with exactly the same characters. */ + return 0; + } else { + /* hash2 goes on longer than hash1 and thus hash1 is lower. */ + return -1; + } + } else if (bv == NOT_VALID_BASE64) { + /* hash1 goes on longer than hash2 and thus hash1 is greater. */ + return 1; + } else if (av < bv) { + /* The first difference shows that hash1 is lower. */ + return -1; + } else if (av > bv) { + /* The first difference shows that hash1 is greater. */ + return 1; + } else { + a++; + b++; + if (a == a_end) { + if (b == b_end) { + return 0; + } else { + return -1; + } + } else if (b == b_end) { + return 1; + } + } + } +} + +/** Structure used to remember the previous and current identity hash of + * the "r " lines in a consensus, to enforce well-ordering. */ +typedef struct router_id_iterator_t { + cdline_t last_hash; + cdline_t hash; +} router_id_iterator_t; + +/** + * Initializer for a router_id_iterator_t. + */ +#define ROUTER_ID_ITERATOR_INIT { { NULL, 0 }, { NULL, 0 } } + +/** Given an index *<b>idxp</b> into the consensus at <b>cons</b>, advance + * the index to the next router line ("r ...") in the consensus, or to + * an index one after the end of the list if there is no such line. + * + * Use <b>iter</b> to record the hash of the found router line, if any, + * and to enforce ordering on the hashes. If the hashes are mis-ordered, + * return -1. Else, return 0. + **/ +static int +find_next_router_line(const smartlist_t *cons, + const char *consname, + int *idxp, + router_id_iterator_t *iter) +{ + *idxp = next_router(cons, *idxp); + if (*idxp < smartlist_len(cons)) { + memcpy(&iter->last_hash, &iter->hash, sizeof(cdline_t)); + if (get_id_hash(smartlist_get(cons, *idxp), &iter->hash) < 0 || + base64cmp(&iter->hash, &iter->last_hash) <= 0) { + log_warn(LD_CONSDIFF, "Refusing to generate consensus diff because " + "the %s consensus doesn't have its router entries sorted " + "properly.", consname); + return -1; + } + } + return 0; +} + +/** Line-prefix indicating the beginning of the signatures section that we + * intend to delete. */ +#define START_OF_SIGNATURES_SECTION "directory-signature " + +/** Pre-process a consensus in <b>cons</b> (represented as a list of cdline_t) + * to remove the signatures from it. If the footer is removed, return a + * cdline_t containing a delete command to delete the footer, allocated in + * <b>area</>. If no footer is removed, return NULL. + * + * We remove the signatures here because they are not themselves signed, and + * as such there might be different encodings for them. + */ +static cdline_t * +preprocess_consensus(memarea_t *area, + smartlist_t *cons) +{ + int idx; + int dirsig_idx = -1; + for (idx = 0; idx < smartlist_len(cons); ++idx) { + cdline_t *line = smartlist_get(cons, idx); + if (line_starts_with_str(line, START_OF_SIGNATURES_SECTION)) { + dirsig_idx = idx; + break; + } + } + if (dirsig_idx >= 0) { + char buf[64]; + while (smartlist_len(cons) > dirsig_idx) + smartlist_del(cons, dirsig_idx); + tor_snprintf(buf, sizeof(buf), "%d,$d", dirsig_idx+1); + return cdline_linecpy(area, buf); + } else { + return NULL; + } +} + +/** Generate an ed diff as a smartlist from two consensuses, also given as + * smartlists. Will return NULL if the diff could not be generated, which can + * happen if any lines the script had to add matched "." or if the routers + * were not properly ordered. + * + * All cdline_t objects in the resulting object are either references to lines + * in one of the inputs, or are newly allocated lines in the provided memarea. + * + * This implementation is consensus-specific. To generate an ed diff for any + * given input in quadratic time, you can replace all the code until the + * navigation in reverse order with the following: + * + * int len1 = smartlist_len(cons1); + * int len2 = smartlist_len(cons2); + * bitarray_t *changed1 = bitarray_init_zero(len1); + * bitarray_t *changed2 = bitarray_init_zero(len2); + * cons1_sl = smartlist_slice(cons1, 0, -1); + * cons2_sl = smartlist_slice(cons2, 0, -1); + * calc_changes(cons1_sl, cons2_sl, changed1, changed2); + */ +STATIC smartlist_t * +gen_ed_diff(const smartlist_t *cons1_orig, const smartlist_t *cons2, + memarea_t *area) +{ + smartlist_t *cons1 = smartlist_new(); + smartlist_add_all(cons1, cons1_orig); + cdline_t *remove_trailer = preprocess_consensus(area, cons1); + + int len1 = smartlist_len(cons1); + int len2 = smartlist_len(cons2); + smartlist_t *result = smartlist_new(); + + if (remove_trailer) { + /* There's a delete-the-trailer line at the end, so add it here. */ + smartlist_add(result, remove_trailer); + } + + /* Initialize the changed bitarrays to zero, so that calc_changes only needs + * to set the ones that matter and leave the rest untouched. + */ + bitarray_t *changed1 = bitarray_init_zero(len1); + bitarray_t *changed2 = bitarray_init_zero(len2); + int i1=-1, i2=-1; + int start1=0, start2=0; + + /* To check that hashes are ordered properly */ + router_id_iterator_t iter1 = ROUTER_ID_ITERATOR_INIT; + router_id_iterator_t iter2 = ROUTER_ID_ITERATOR_INIT; + + /* i1 and i2 are initialized at the first line of each consensus. They never + * reach past len1 and len2 respectively, since next_router doesn't let that + * happen. i1 and i2 are advanced by at least one line at each iteration as + * long as they have not yet reached len1 and len2, so the loop is + * guaranteed to end, and each pair of (i1,i2) will be inspected at most + * once. + */ + while (i1 < len1 || i2 < len2) { + + /* Advance each of the two navigation positions by one router entry if not + * yet at the end. + */ + if (i1 < len1) { + if (find_next_router_line(cons1, "base", &i1, &iter1) < 0) { + goto error_cleanup; + } + } + + if (i2 < len2) { + if (find_next_router_line(cons2, "target", &i2, &iter2) < 0) { + goto error_cleanup; + } + } + + /* If we have reached the end of both consensuses, there is no need to + * compare hashes anymore, since this is the last iteration. + */ + if (i1 < len1 || i2 < len2) { + + /* Keep on advancing the lower (by identity hash sorting) position until + * we have two matching positions. The only other possible outcome is + * that a lower position reaches the end of the consensus before it can + * reach a hash that is no longer the lower one. Since there will always + * be a lower hash for as long as the loop runs, one of the two indexes + * will always be incremented, thus assuring that the loop must end + * after a finite number of iterations. If that cannot be because said + * consensus has already reached the end, both are extended to their + * respecting ends since we are done. + */ + int cmp = base64cmp(&iter1.hash, &iter2.hash); + while (cmp != 0) { + if (i1 < len1 && cmp < 0) { + if (find_next_router_line(cons1, "base", &i1, &iter1) < 0) { + goto error_cleanup; + } + if (i1 == len1) { + /* We finished the first consensus, so grab all the remaining + * lines of the second consensus and finish up. + */ + i2 = len2; + break; + } + } else if (i2 < len2 && cmp > 0) { + if (find_next_router_line(cons2, "target", &i2, &iter2) < 0) { + goto error_cleanup; + } + if (i2 == len2) { + /* We finished the second consensus, so grab all the remaining + * lines of the first consensus and finish up. + */ + i1 = len1; + break; + } + } else { + i1 = len1; + i2 = len2; + break; + } + cmp = base64cmp(&iter1.hash, &iter2.hash); + } + } + + /* Make slices out of these chunks (up to the common router entry) and + * calculate the changes for them. + * Error if any of the two slices are longer than 10K lines. That should + * never happen with any pair of real consensuses. Feeding more than 10K + * lines to calc_changes would be very slow anyway. + */ +#define MAX_LINE_COUNT (10000) + if (i1-start1 > MAX_LINE_COUNT || i2-start2 > MAX_LINE_COUNT) { + log_warn(LD_CONSDIFF, "Refusing to generate consensus diff because " + "we found too few common router ids."); + goto error_cleanup; + } + + smartlist_slice_t *cons1_sl = smartlist_slice(cons1, start1, i1); + smartlist_slice_t *cons2_sl = smartlist_slice(cons2, start2, i2); + calc_changes(cons1_sl, cons2_sl, changed1, changed2); + tor_free(cons1_sl); + tor_free(cons2_sl); + start1 = i1, start2 = i2; + } + + /* Navigate the changes in reverse order and generate one ed command for + * each chunk of changes. + */ + i1=len1-1, i2=len2-1; + char buf[128]; + while (i1 >= 0 || i2 >= 0) { + + int start1x, start2x, end1, end2, added, deleted; + + /* We are at a point were no changed bools are true, so just keep going. */ + if (!(i1 >= 0 && bitarray_is_set(changed1, i1)) && + !(i2 >= 0 && bitarray_is_set(changed2, i2))) { + if (i1 >= 0) { + i1--; + } + if (i2 >= 0) { + i2--; + } + continue; + } + + end1 = i1, end2 = i2; + + /* Grab all contiguous changed lines */ + while (i1 >= 0 && bitarray_is_set(changed1, i1)) { + i1--; + } + while (i2 >= 0 && bitarray_is_set(changed2, i2)) { + i2--; + } + + start1x = i1+1, start2x = i2+1; + added = end2-i2, deleted = end1-i1; + + if (added == 0) { + if (deleted == 1) { + tor_snprintf(buf, sizeof(buf), "%id", start1x+1); + smartlist_add_linecpy(result, area, buf); + } else { + tor_snprintf(buf, sizeof(buf), "%i,%id", start1x+1, start1x+deleted); + smartlist_add_linecpy(result, area, buf); + } + } else { + int i; + if (deleted == 0) { + tor_snprintf(buf, sizeof(buf), "%ia", start1x); + smartlist_add_linecpy(result, area, buf); + } else if (deleted == 1) { + tor_snprintf(buf, sizeof(buf), "%ic", start1x+1); + smartlist_add_linecpy(result, area, buf); + } else { + tor_snprintf(buf, sizeof(buf), "%i,%ic", start1x+1, start1x+deleted); + smartlist_add_linecpy(result, area, buf); + } + + for (i = start2x; i <= end2; ++i) { + cdline_t *line = smartlist_get(cons2, i); + if (line_str_eq(line, ".")) { + log_warn(LD_CONSDIFF, "Cannot generate consensus diff because " + "one of the lines to be added is \".\"."); + goto error_cleanup; + } + smartlist_add(result, line); + } + smartlist_add_linecpy(result, area, "."); + } + } + + smartlist_free(cons1); + bitarray_free(changed1); + bitarray_free(changed2); + + return result; + + error_cleanup: + + smartlist_free(cons1); + bitarray_free(changed1); + bitarray_free(changed2); + + smartlist_free(result); + + return NULL; +} + +/* Helper: Read a base-10 number between 0 and INT32_MAX from <b>s</b> and + * store it in <b>num_out</b>. Advance <b>s</b> to the characer immediately + * after the number. Return 0 on success, -1 on failure. */ +static int +get_linenum(const char **s, int *num_out) +{ + int ok; + char *next; + if (!TOR_ISDIGIT(**s)) { + return -1; + } + *num_out = (int) tor_parse_long(*s, 10, 0, INT32_MAX, &ok, &next); + if (ok && next) { + *s = next; + return 0; + } else { + return -1; + } +} + +/** Apply the ed diff, starting at <b>diff_starting_line</b>, to the consensus + * and return a new consensus, also as a line-based smartlist. Will return + * NULL if the ed diff is not properly formatted. + * + * All cdline_t objects in the resulting object are references to lines + * in one of the inputs; nothing is copied. + */ +STATIC smartlist_t * +apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff, + int diff_starting_line) +{ + int diff_len = smartlist_len(diff); + int j = smartlist_len(cons1); + smartlist_t *cons2 = smartlist_new(); + + for (int i=diff_starting_line; i<diff_len; ++i) { + const cdline_t *diff_cdline = smartlist_get(diff, i); + char diff_line[128]; + + if (diff_cdline->len > sizeof(diff_line) - 1) { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "an ed command was far too long"); + goto error_cleanup; + } + /* Copy the line to make it nul-terminated. */ + memcpy(diff_line, diff_cdline->s, diff_cdline->len); + diff_line[diff_cdline->len] = 0; + const char *ptr = diff_line; + int start = 0, end = 0; + int had_range = 0; + int end_was_eof = 0; + if (get_linenum(&ptr, &start) < 0) { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "an ed command was missing a line number."); + goto error_cleanup; + } + if (*ptr == ',') { + /* Two-item range */ + had_range = 1; + ++ptr; + if (*ptr == '$') { + end_was_eof = 1; + end = smartlist_len(cons1); + ++ptr; + } else if (get_linenum(&ptr, &end) < 0) { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "an ed command was missing a range end line number."); + goto error_cleanup; + } + /* Incoherent range. */ + if (end <= start) { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "an invalid range was found in an ed command."); + goto error_cleanup; + } + } else { + /* We'll take <n1> as <n1>,<n1> for simplicity. */ + end = start; + } + + if (end > j) { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "its commands are not properly sorted in reverse order."); + goto error_cleanup; + } + + if (*ptr == '\0') { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "a line with no ed command was found"); + goto error_cleanup; + } + + if (*(ptr+1) != '\0') { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "an ed command longer than one char was found."); + goto error_cleanup; + } + + char action = *ptr; + + switch (action) { + case 'a': + case 'c': + case 'd': + break; + default: + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "an unrecognised ed command was found."); + goto error_cleanup; + } + + /** $ is not allowed with non-d actions. */ + if (end_was_eof && action != 'd') { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "it wanted to use $ with a command other than delete"); + goto error_cleanup; + } + + /* 'a' commands are not allowed to have ranges. */ + if (had_range && action == 'a') { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "it wanted to add lines after a range."); + goto error_cleanup; + } + + /* Add unchanged lines. */ + for (; j && j > end; --j) { + cdline_t *cons_line = smartlist_get(cons1, j-1); + smartlist_add(cons2, cons_line); + } + + /* Ignore removed lines. */ + if (action == 'c' || action == 'd') { + while (--j >= start) { + /* Skip line */ + } + } + + /* Add new lines in reverse order, since it will all be reversed at the + * end. + */ + if (action == 'a' || action == 'c') { + int added_end = i; + + i++; /* Skip the line with the range and command. */ + while (i < diff_len) { + if (line_str_eq(smartlist_get(diff, i), ".")) { + break; + } + if (++i == diff_len) { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "it has lines to be inserted that don't end with a \".\"."); + goto error_cleanup; + } + } + + int added_i = i-1; + + /* It would make no sense to add zero new lines. */ + if (added_i == added_end) { + log_warn(LD_CONSDIFF, "Could not apply consensus diff because " + "it has an ed command that tries to insert zero lines."); + goto error_cleanup; + } + + while (added_i > added_end) { + cdline_t *added_line = smartlist_get(diff, added_i--); + smartlist_add(cons2, added_line); + } + } + } + + /* Add remaining unchanged lines. */ + for (; j > 0; --j) { + cdline_t *cons_line = smartlist_get(cons1, j-1); + smartlist_add(cons2, cons_line); + } + + /* Reverse the whole thing since we did it from the end. */ + smartlist_reverse(cons2); + return cons2; + + error_cleanup: + + smartlist_free(cons2); + + return NULL; +} + +/** Generate a consensus diff as a smartlist from two given consensuses, also + * as smartlists. Will return NULL if the consensus diff could not be + * generated. Neither of the two consensuses are modified in any way, so it's + * up to the caller to free their resources. + */ +smartlist_t * +consdiff_gen_diff(const smartlist_t *cons1, + const smartlist_t *cons2, + const consensus_digest_t *digests1, + const consensus_digest_t *digests2, + memarea_t *area) +{ + smartlist_t *ed_diff = gen_ed_diff(cons1, cons2, area); + /* ed diff could not be generated - reason already logged by gen_ed_diff. */ + if (!ed_diff) { + goto error_cleanup; + } + + /* See that the script actually produces what we want. */ + smartlist_t *ed_cons2 = apply_ed_diff(cons1, ed_diff, 0); + if (!ed_cons2) { + /* LCOV_EXCL_START -- impossible if diff generation is correct */ + log_warn(LD_BUG|LD_CONSDIFF, "Refusing to generate consensus diff because " + "the generated ed diff could not be tested to successfully generate " + "the target consensus."); + goto error_cleanup; + /* LCOV_EXCL_STOP */ + } + + int cons2_eq = 1; + if (smartlist_len(cons2) == smartlist_len(ed_cons2)) { + SMARTLIST_FOREACH_BEGIN(cons2, const cdline_t *, line1) { + const cdline_t *line2 = smartlist_get(ed_cons2, line1_sl_idx); + if (! lines_eq(line1, line2) ) { + cons2_eq = 0; + break; + } + } SMARTLIST_FOREACH_END(line1); + } else { + cons2_eq = 0; + } + smartlist_free(ed_cons2); + if (!cons2_eq) { + /* LCOV_EXCL_START -- impossible if diff generation is correct. */ + log_warn(LD_BUG|LD_CONSDIFF, "Refusing to generate consensus diff because " + "the generated ed diff did not generate the target consensus " + "successfully when tested."); + goto error_cleanup; + /* LCOV_EXCL_STOP */ + } + + char cons1_hash_hex[HEX_DIGEST256_LEN+1]; + char cons2_hash_hex[HEX_DIGEST256_LEN+1]; + base16_encode(cons1_hash_hex, HEX_DIGEST256_LEN+1, + (const char*)digests1->sha3_256, DIGEST256_LEN); + base16_encode(cons2_hash_hex, HEX_DIGEST256_LEN+1, + (const char*)digests2->sha3_256, DIGEST256_LEN); + + /* Create the resulting consensus diff. */ + char buf[160]; + smartlist_t *result = smartlist_new(); + tor_snprintf(buf, sizeof(buf), "%s", ns_diff_version); + smartlist_add_linecpy(result, area, buf); + tor_snprintf(buf, sizeof(buf), "%s %s %s", hash_token, + cons1_hash_hex, cons2_hash_hex); + smartlist_add_linecpy(result, area, buf); + smartlist_add_all(result, ed_diff); + smartlist_free(ed_diff); + return result; + + error_cleanup: + + if (ed_diff) { + /* LCOV_EXCL_START -- ed_diff is NULL except in unreachable cases above */ + smartlist_free(ed_diff); + /* LCOV_EXCL_STOP */ + } + + return NULL; +} + +/** Fetch the digest of the base consensus in the consensus diff, encoded in + * base16 as found in the diff itself. digest1_out and digest2_out must be of + * length DIGEST256_LEN or larger if not NULL. + */ +int +consdiff_get_digests(const smartlist_t *diff, + char *digest1_out, + char *digest2_out) +{ + smartlist_t *hash_words = NULL; + const cdline_t *format; + char cons1_hash[DIGEST256_LEN], cons2_hash[DIGEST256_LEN]; + char *cons1_hash_hex, *cons2_hash_hex; + if (smartlist_len(diff) < 2) { + log_info(LD_CONSDIFF, "The provided consensus diff is too short."); + goto error_cleanup; + } + + /* Check that it's the format and version we know. */ + format = smartlist_get(diff, 0); + if (!line_str_eq(format, ns_diff_version)) { + log_warn(LD_CONSDIFF, "The provided consensus diff format is not known."); + goto error_cleanup; + } + + /* Grab the base16 digests. */ + hash_words = smartlist_new(); + { + const cdline_t *line2 = smartlist_get(diff, 1); + char *h = tor_memdup_nulterm(line2->s, line2->len); + smartlist_split_string(hash_words, h, " ", 0, 0); + tor_free(h); + } + + /* There have to be three words, the first of which must be hash_token. */ + if (smartlist_len(hash_words) != 3 || + strcmp(smartlist_get(hash_words, 0), hash_token)) { + log_info(LD_CONSDIFF, "The provided consensus diff does not include " + "the necessary digests."); + goto error_cleanup; + } + + /* Expected hashes as found in the consensus diff header. They must be of + * length HEX_DIGEST256_LEN, normally 64 hexadecimal characters. + * If any of the decodings fail, error to make sure that the hashes are + * proper base16-encoded digests. + */ + cons1_hash_hex = smartlist_get(hash_words, 1); + cons2_hash_hex = smartlist_get(hash_words, 2); + if (strlen(cons1_hash_hex) != HEX_DIGEST256_LEN || + strlen(cons2_hash_hex) != HEX_DIGEST256_LEN) { + log_info(LD_CONSDIFF, "The provided consensus diff includes " + "base16-encoded digests of incorrect size."); + goto error_cleanup; + } + + if (base16_decode(cons1_hash, DIGEST256_LEN, + cons1_hash_hex, HEX_DIGEST256_LEN) != DIGEST256_LEN || + base16_decode(cons2_hash, DIGEST256_LEN, + cons2_hash_hex, HEX_DIGEST256_LEN) != DIGEST256_LEN) { + log_info(LD_CONSDIFF, "The provided consensus diff includes " + "malformed digests."); + goto error_cleanup; + } + + if (digest1_out) { + memcpy(digest1_out, cons1_hash, DIGEST256_LEN); + } + if (digest2_out) { + memcpy(digest2_out, cons2_hash, DIGEST256_LEN); + } + + SMARTLIST_FOREACH(hash_words, char *, cp, tor_free(cp)); + smartlist_free(hash_words); + return 0; + + error_cleanup: + + if (hash_words) { + SMARTLIST_FOREACH(hash_words, char *, cp, tor_free(cp)); + smartlist_free(hash_words); + } + return 1; +} + +/** Apply the consensus diff to the given consensus and return a new + * consensus, also as a line-based smartlist. Will return NULL if the diff + * could not be applied. Neither the consensus nor the diff are modified in + * any way, so it's up to the caller to free their resources. + */ +char * +consdiff_apply_diff(const smartlist_t *cons1, + const smartlist_t *diff, + const consensus_digest_t *digests1) +{ + smartlist_t *cons2 = NULL; + char *cons2_str = NULL; + char e_cons1_hash[DIGEST256_LEN]; + char e_cons2_hash[DIGEST256_LEN]; + + if (consdiff_get_digests(diff, e_cons1_hash, e_cons2_hash) != 0) { + goto error_cleanup; + } + + /* See that the consensus that was given to us matches its hash. */ + if (!consensus_digest_eq(digests1->sha3_256, + (const uint8_t*)e_cons1_hash)) { + char hex_digest1[HEX_DIGEST256_LEN+1]; + char e_hex_digest1[HEX_DIGEST256_LEN+1]; + log_warn(LD_CONSDIFF, "Refusing to apply consensus diff because " + "the base consensus doesn't match the digest as found in " + "the consensus diff header."); + base16_encode(hex_digest1, HEX_DIGEST256_LEN+1, + (const char *)digests1->sha3_256, DIGEST256_LEN); + base16_encode(e_hex_digest1, HEX_DIGEST256_LEN+1, + e_cons1_hash, DIGEST256_LEN); + log_warn(LD_CONSDIFF, "Expected: %s; found: %s", + hex_digest1, e_hex_digest1); + goto error_cleanup; + } + + /* Grab the ed diff and calculate the resulting consensus. */ + /* Skip the first two lines. */ + cons2 = apply_ed_diff(cons1, diff, 2); + + /* ed diff could not be applied - reason already logged by apply_ed_diff. */ + if (!cons2) { + goto error_cleanup; + } + + cons2_str = consensus_join_lines(cons2); + + consensus_digest_t cons2_digests; + if (consensus_compute_digest(cons2_str, &cons2_digests) < 0) { + /* LCOV_EXCL_START -- digest can't fail */ + log_warn(LD_CONSDIFF, "Could not compute digests of the consensus " + "resulting from applying a consensus diff."); + goto error_cleanup; + /* LCOV_EXCL_STOP */ + } + + /* See that the resulting consensus matches its hash. */ + if (!consensus_digest_eq(cons2_digests.sha3_256, + (const uint8_t*)e_cons2_hash)) { + log_warn(LD_CONSDIFF, "Refusing to apply consensus diff because " + "the resulting consensus doesn't match the digest as found in " + "the consensus diff header."); + char hex_digest2[HEX_DIGEST256_LEN+1]; + char e_hex_digest2[HEX_DIGEST256_LEN+1]; + base16_encode(hex_digest2, HEX_DIGEST256_LEN+1, + (const char *)cons2_digests.sha3_256, DIGEST256_LEN); + base16_encode(e_hex_digest2, HEX_DIGEST256_LEN+1, + e_cons2_hash, DIGEST256_LEN); + log_warn(LD_CONSDIFF, "Expected: %s; found: %s", + hex_digest2, e_hex_digest2); + goto error_cleanup; + } + + goto done; + + error_cleanup: + tor_free(cons2_str); /* Sets it to NULL */ + + done: + if (cons2) { + smartlist_free(cons2); + } + + return cons2_str; +} + +/** Any consensus line longer than this means that the input is invalid. */ +#define CONSENSUS_LINE_MAX_LEN (1<<20) + +/** + * Helper: For every NL-terminated line in <b>s</b>, add a cdline referring to + * that line (without trailing newline) to <b>out</b>. Return -1 if there are + * any non-NL terminated lines; 0 otherwise. + * + * Unlike tor_split_lines, this function avoids ambiguity on its + * handling of a final line that isn't NL-terminated. + * + * All cdline_t objects are allocated in the provided memarea. Strings + * are not copied: if <b>s</b> changes or becomes invalid, then all + * generated cdlines will become invalid. + */ +STATIC int +consensus_split_lines(smartlist_t *out, const char *s, memarea_t *area) +{ + while (*s) { + const char *eol = strchr(s, '\n'); + if (!eol) { + /* File doesn't end with newline. */ + return -1; + } + if (eol - s > CONSENSUS_LINE_MAX_LEN) { + /* Line is far too long. */ + return -1; + } + cdline_t *line = memarea_alloc(area, sizeof(cdline_t)); + line->s = s; + line->len = (uint32_t)(eol - s); + smartlist_add(out, line); + s = eol+1; + } + return 0; +} + +/** Given a list of cdline_t, return a newly allocated string containing + * all of the lines, terminated with NL, concatenated. + * + * Unlike smartlist_join_strings(), avoids lossy operations on empty + * lists. */ +static char * +consensus_join_lines(const smartlist_t *inp) +{ + size_t n = 0; + SMARTLIST_FOREACH(inp, const cdline_t *, cdline, n += cdline->len + 1); + n += 1; + char *result = tor_malloc(n); + char *out = result; + SMARTLIST_FOREACH_BEGIN(inp, const cdline_t *, cdline) { + memcpy(out, cdline->s, cdline->len); + out += cdline->len; + *out++ = '\n'; + } SMARTLIST_FOREACH_END(cdline); + *out++ = '\0'; + tor_assert(out == result+n); + return result; +} + +/** Given two consensus documents, try to compute a diff between them. On + * success, retun a newly allocated string containing that diff. On failure, + * return NULL. */ +char * +consensus_diff_generate(const char *cons1, + const char *cons2) +{ + consensus_digest_t d1, d2; + smartlist_t *lines1 = NULL, *lines2 = NULL, *result_lines = NULL; + int r1, r2; + char *result = NULL; + + r1 = consensus_compute_digest_as_signed(cons1, &d1); + r2 = consensus_compute_digest(cons2, &d2); + if (BUG(r1 < 0 || r2 < 0)) + return NULL; // LCOV_EXCL_LINE + + memarea_t *area = memarea_new(); + lines1 = smartlist_new(); + lines2 = smartlist_new(); + if (consensus_split_lines(lines1, cons1, area) < 0) + goto done; + if (consensus_split_lines(lines2, cons2, area) < 0) + goto done; + + result_lines = consdiff_gen_diff(lines1, lines2, &d1, &d2, area); + + done: + if (result_lines) { + result = consensus_join_lines(result_lines); + smartlist_free(result_lines); + } + + memarea_drop_all(area); + smartlist_free(lines1); + smartlist_free(lines2); + + return result; +} + +/** Given a consensus document and a diff, try to apply the diff to the + * consensus. On success return a newly allocated string containing the new + * consensus. On failure, return NULL. */ +char * +consensus_diff_apply(const char *consensus, + const char *diff) +{ + consensus_digest_t d1; + smartlist_t *lines1 = NULL, *lines2 = NULL; + int r1; + char *result = NULL; + memarea_t *area = memarea_new(); + + r1 = consensus_compute_digest_as_signed(consensus, &d1); + if (BUG(r1 < 0)) + return NULL; // LCOV_EXCL_LINE + + lines1 = smartlist_new(); + lines2 = smartlist_new(); + if (consensus_split_lines(lines1, consensus, area) < 0) + goto done; + if (consensus_split_lines(lines2, diff, area) < 0) + goto done; + + result = consdiff_apply_diff(lines1, lines2, &d1); + + done: + smartlist_free(lines1); + smartlist_free(lines2); + memarea_drop_all(area); + + return result; +} + +/** Return true iff, based on its header, <b>document</b> is likely + * to be a consensus diff. */ +int +looks_like_a_consensus_diff(const char *document, size_t len) +{ + return (len >= strlen(ns_diff_version) && + fast_memeq(document, ns_diff_version, strlen(ns_diff_version))); +} + diff --git a/src/or/consdiff.h b/src/or/consdiff.h new file mode 100644 index 0000000000..d05df74b75 --- /dev/null +++ b/src/or/consdiff.h @@ -0,0 +1,98 @@ +/* Copyright (c) 2014, Daniel Martà + * Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_CONSDIFF_H +#define TOR_CONSDIFF_H + +#include "or.h" + +char *consensus_diff_generate(const char *cons1, + const char *cons2); +char *consensus_diff_apply(const char *consensus, + const char *diff); + +int looks_like_a_consensus_diff(const char *document, size_t len); + +#ifdef CONSDIFF_PRIVATE +struct memarea_t; + +/** Line type used for constructing consensus diffs. Each of these lines + * refers to a chunk of memory allocated elsewhere, and is not necessarily + * NUL-terminated: this helps us avoid copies and save memory. */ +typedef struct cdline_t { + const char *s; + uint32_t len; +} cdline_t; + +typedef struct consensus_digest_t { + uint8_t sha3_256[DIGEST256_LEN]; +} consensus_digest_t; + +STATIC smartlist_t *consdiff_gen_diff(const smartlist_t *cons1, + const smartlist_t *cons2, + const consensus_digest_t *digests1, + const consensus_digest_t *digests2, + struct memarea_t *area); +STATIC char *consdiff_apply_diff(const smartlist_t *cons1, + const smartlist_t *diff, + const consensus_digest_t *digests1); +STATIC int consdiff_get_digests(const smartlist_t *diff, + char *digest1_out, + char *digest2_out); + +/** Data structure to define a slice of a smarltist. */ +typedef struct smartlist_slice_t { + /** + * Smartlist that this slice is made from. + * References the whole original smartlist that the slice was made out of. + * */ + const smartlist_t *list; + /** Starting position of the slice in the smartlist. */ + int offset; + /** Length of the slice, i.e. the number of elements it holds. */ + int len; +} smartlist_slice_t; +STATIC smartlist_t *gen_ed_diff(const smartlist_t *cons1, + const smartlist_t *cons2, + struct memarea_t *area); +STATIC smartlist_t *apply_ed_diff(const smartlist_t *cons1, + const smartlist_t *diff, + int start_line); +STATIC void calc_changes(smartlist_slice_t *slice1, smartlist_slice_t *slice2, + bitarray_t *changed1, bitarray_t *changed2); +STATIC smartlist_slice_t *smartlist_slice(const smartlist_t *list, + int start, int end); +STATIC int next_router(const smartlist_t *cons, int cur); +STATIC int *lcs_lengths(const smartlist_slice_t *slice1, + const smartlist_slice_t *slice2, + int direction); +STATIC void trim_slices(smartlist_slice_t *slice1, smartlist_slice_t *slice2); +STATIC int base64cmp(const cdline_t *hash1, const cdline_t *hash2); +STATIC int get_id_hash(const cdline_t *line, cdline_t *hash_out); +STATIC int is_valid_router_entry(const cdline_t *line); +STATIC int smartlist_slice_string_pos(const smartlist_slice_t *slice, + const cdline_t *string); +STATIC void set_changed(bitarray_t *changed1, bitarray_t *changed2, + const smartlist_slice_t *slice1, + const smartlist_slice_t *slice2); +STATIC int consensus_split_lines(smartlist_t *out, const char *s, + struct memarea_t *area); +STATIC void smartlist_add_linecpy(smartlist_t *lst, struct memarea_t *area, + const char *s); +STATIC int lines_eq(const cdline_t *a, const cdline_t *b); +STATIC int line_str_eq(const cdline_t *a, const char *b); + +MOCK_DECL(STATIC int, + consensus_compute_digest,(const char *cons, + consensus_digest_t *digest_out)); +MOCK_DECL(STATIC int, + consensus_compute_digest_as_signed,(const char *cons, + consensus_digest_t *digest_out)); +MOCK_DECL(STATIC int, + consensus_digest_eq,(const uint8_t *d1, + const uint8_t *d2)); +#endif + +#endif + diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c new file mode 100644 index 0000000000..c5f55b6f3f --- /dev/null +++ b/src/or/consdiffmgr.c @@ -0,0 +1,1895 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file consdiffmsr.c + * + * \brief consensus diff manager functions + * + * This module is run by directory authorities and caches in order + * to remember a number of past consensus documents, and to generate + * and serve the diffs from those documents to the latest consensus. + */ + +#define CONSDIFFMGR_PRIVATE + +#include "or.h" +#include "config.h" +#include "conscache.h" +#include "consdiff.h" +#include "consdiffmgr.h" +#include "cpuworker.h" +#include "networkstatus.h" +#include "routerparse.h" +#include "workqueue.h" + +/** + * Labels to apply to items in the conscache object. + * + * @{ + */ +/* One of DOCTYPE_CONSENSUS or DOCTYPE_CONSENSUS_DIFF */ +#define LABEL_DOCTYPE "document-type" +/* The valid-after time for a consensus (or for the target consensus of a + * diff), encoded as ISO UTC. */ +#define LABEL_VALID_AFTER "consensus-valid-after" +/* The fresh-until time for a consensus (or for the target consensus of a + * diff), encoded as ISO UTC. */ +#define LABEL_FRESH_UNTIL "consensus-fresh-until" +/* The valid-until time for a consensus (or for the target consensus of a + * diff), encoded as ISO UTC. */ +#define LABEL_VALID_UNTIL "consensus-valid-until" +/* Comma-separated list of hex-encoded identity digests for the voting + * authorities. */ +#define LABEL_SIGNATORIES "consensus-signatories" +/* A hex encoded SHA3 digest of the object, as compressed (if any) */ +#define LABEL_SHA3_DIGEST "sha3-digest" +/* A hex encoded SHA3 digest of the object before compression. */ +#define LABEL_SHA3_DIGEST_UNCOMPRESSED "sha3-digest-uncompressed" +/* A hex encoded SHA3 digest-as-signed of a consensus */ +#define LABEL_SHA3_DIGEST_AS_SIGNED "sha3-digest-as-signed" +/* The flavor of the consensus or consensuses diff */ +#define LABEL_FLAVOR "consensus-flavor" +/* Diff only: the SHA3 digest-as-signed of the source consensus. */ +#define LABEL_FROM_SHA3_DIGEST "from-sha3-digest" +/* Diff only: the SHA3 digest-in-full of the target consensus. */ +#define LABEL_TARGET_SHA3_DIGEST "target-sha3-digest" +/* Diff only: the valid-after date of the source consensus. */ +#define LABEL_FROM_VALID_AFTER "from-valid-after" +/* What kind of compression was used? */ +#define LABEL_COMPRESSION_TYPE "compression" +/** @} */ + +#define DOCTYPE_CONSENSUS "consensus" +#define DOCTYPE_CONSENSUS_DIFF "consensus-diff" + +/** + * Underlying directory that stores consensuses and consensus diffs. Don't + * use this directly: use cdm_cache_get() instead. + */ +static consensus_cache_t *cons_diff_cache = NULL; +/** + * If true, we have learned at least one new consensus since the + * consensus cache was last up-to-date. + */ +static int cdm_cache_dirty = 0; +/** + * If true, we have scanned the cache to update our hashtable of diffs. + */ +static int cdm_cache_loaded = 0; + +/** + * Possible status values for cdm_diff_t.cdm_diff_status + **/ +typedef enum cdm_diff_status_t { + CDM_DIFF_PRESENT=1, + CDM_DIFF_IN_PROGRESS=2, + CDM_DIFF_ERROR=3, +} cdm_diff_status_t; + +/** Which methods do we use for precompressing diffs? */ +static const compress_method_t compress_diffs_with[] = { + NO_METHOD, + GZIP_METHOD, +#ifdef HAVE_LZMA + LZMA_METHOD, +#endif +#ifdef HAVE_ZSTD + ZSTD_METHOD, +#endif +}; + +/** How many different methods will we try to use for diff compression? */ +STATIC unsigned +n_diff_compression_methods(void) +{ + return ARRAY_LENGTH(compress_diffs_with); +} + +/** Which methods do we use for precompressing consensuses? */ +static const compress_method_t compress_consensus_with[] = { + ZLIB_METHOD, +#ifdef HAVE_LZMA + LZMA_METHOD, +#endif +#ifdef HAVE_ZSTD + ZSTD_METHOD, +#endif +}; + +/** How many different methods will we try to use for diff compression? */ +STATIC unsigned +n_consensus_compression_methods(void) +{ + return ARRAY_LENGTH(compress_consensus_with); +} + +/** For which compression method do we retain old consensuses? There's no + * need to keep all of them, since we won't be serving them. We'll + * go with ZLIB_METHOD because it's pretty fast and everyone has it. + */ +#define RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD ZLIB_METHOD + +/** Handles pointing to the latest consensus entries as compressed and + * stored. */ +static consensus_cache_entry_handle_t * + latest_consensus[N_CONSENSUS_FLAVORS] + [ARRAY_LENGTH(compress_consensus_with)]; + +/** Hashtable node used to remember the current status of the diff + * from a given sha3 digest to the current consensus. */ +typedef struct cdm_diff_t { + HT_ENTRY(cdm_diff_t) node; + + /** Consensus flavor for this diff (part of ht key) */ + consensus_flavor_t flavor; + /** SHA3-256 digest of the consensus that this diff is _from_. (part of the + * ht key) */ + uint8_t from_sha3[DIGEST256_LEN]; + /** Method by which the diff is compressed. (part of the ht key */ + compress_method_t compress_method; + + /** One of the CDM_DIFF_* values, depending on whether this diff + * is available, in progress, or impossible to compute. */ + cdm_diff_status_t cdm_diff_status; + /** SHA3-256 digest of the consensus that this diff is _to. */ + uint8_t target_sha3[DIGEST256_LEN]; + + /** Handle to the cache entry for this diff, if any. We use a handle here + * to avoid thinking too hard about cache entry lifetime issues. */ + consensus_cache_entry_handle_t *entry; +} cdm_diff_t; + +/** Hashtable mapping flavor and source consensus digest to status. */ +static HT_HEAD(cdm_diff_ht, cdm_diff_t) cdm_diff_ht = HT_INITIALIZER(); + +/** + * Configuration for this module + */ +static consdiff_cfg_t consdiff_cfg = { + // XXXX I'd like to make this number bigger, but it interferes with the + // XXXX seccomp2 syscall filter, which tops out at BPF_MAXINS (4096) + // XXXX rules. + /* .cache_max_num = */ 128 +}; + +static int consdiffmgr_ensure_space_for_files(int n); +static int consensus_queue_compression_work(const char *consensus, + const networkstatus_t *as_parsed); +static int consensus_diff_queue_diff_work(consensus_cache_entry_t *diff_from, + consensus_cache_entry_t *diff_to); +static void consdiffmgr_set_cache_flags(void); + +/* ===== + * Hashtable setup + * ===== */ + +/** Helper: hash the key of a cdm_diff_t. */ +static unsigned +cdm_diff_hash(const cdm_diff_t *diff) +{ + uint8_t tmp[DIGEST256_LEN + 2]; + memcpy(tmp, diff->from_sha3, DIGEST256_LEN); + tmp[DIGEST256_LEN] = (uint8_t) diff->flavor; + tmp[DIGEST256_LEN+1] = (uint8_t) diff->compress_method; + return (unsigned) siphash24g(tmp, sizeof(tmp)); +} +/** Helper: compare two cdm_diff_t objects for key equality */ +static int +cdm_diff_eq(const cdm_diff_t *diff1, const cdm_diff_t *diff2) +{ + return fast_memeq(diff1->from_sha3, diff2->from_sha3, DIGEST256_LEN) && + diff1->flavor == diff2->flavor && + diff1->compress_method == diff2->compress_method; +} + +HT_PROTOTYPE(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq) +HT_GENERATE2(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq, + 0.6, tor_reallocarray, tor_free_) + +/** Release all storage held in <b>diff</b>. */ +static void +cdm_diff_free(cdm_diff_t *diff) +{ + if (!diff) + return; + consensus_cache_entry_handle_free(diff->entry); + tor_free(diff); +} + +/** Create and return a new cdm_diff_t with the given values. Does not + * add it to the hashtable. */ +static cdm_diff_t * +cdm_diff_new(consensus_flavor_t flav, + const uint8_t *from_sha3, + const uint8_t *target_sha3, + compress_method_t method) +{ + cdm_diff_t *ent; + ent = tor_malloc_zero(sizeof(cdm_diff_t)); + ent->flavor = flav; + memcpy(ent->from_sha3, from_sha3, DIGEST256_LEN); + memcpy(ent->target_sha3, target_sha3, DIGEST256_LEN); + ent->compress_method = method; + return ent; +} + +/** + * Examine the diff hashtable to see whether we know anything about computing + * a diff of type <b>flav</b> between consensuses with the two provided + * SHA3-256 digests. If a computation is in progress, or if the computation + * has already been tried and failed, return 1. Otherwise, note the + * computation as "in progress" so that we don't reattempt it later, and + * return 0. + */ +static int +cdm_diff_ht_check_and_note_pending(consensus_flavor_t flav, + const uint8_t *from_sha3, + const uint8_t *target_sha3) +{ + struct cdm_diff_t search, *ent; + unsigned u; + int result = 0; + for (u = 0; u < n_diff_compression_methods(); ++u) { + compress_method_t method = compress_diffs_with[u]; + memset(&search, 0, sizeof(cdm_diff_t)); + search.flavor = flav; + search.compress_method = method; + memcpy(search.from_sha3, from_sha3, DIGEST256_LEN); + ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search); + if (ent) { + tor_assert_nonfatal(ent->cdm_diff_status != CDM_DIFF_PRESENT); + result = 1; + continue; + } + ent = cdm_diff_new(flav, from_sha3, target_sha3, method); + ent->cdm_diff_status = CDM_DIFF_IN_PROGRESS; + HT_INSERT(cdm_diff_ht, &cdm_diff_ht, ent); + } + return result; +} + +/** + * Update the status of the diff of type <b>flav</b> between consensuses with + * the two provided SHA3-256 digests, so that its status becomes + * <b>status</b>, and its value becomes the <b>handle</b>. If <b>handle</b> + * is NULL, then the old handle (if any) is freed, and replaced with NULL. + */ +static void +cdm_diff_ht_set_status(consensus_flavor_t flav, + const uint8_t *from_sha3, + const uint8_t *to_sha3, + compress_method_t method, + 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; + search.compress_method = method, + memcpy(search.from_sha3, from_sha3, DIGEST256_LEN); + ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search); + if (!ent) { + ent = cdm_diff_new(flav, from_sha3, to_sha3, method); + ent->cdm_diff_status = CDM_DIFF_IN_PROGRESS; + HT_INSERT(cdm_diff_ht, &cdm_diff_ht, ent); + } else if (fast_memneq(ent->target_sha3, to_sha3, DIGEST256_LEN)) { + // This can happen under certain really pathological conditions + // if we decide we don't care about a diff before it is actually + // done computing. + return; + } + + tor_assert_nonfatal(ent->cdm_diff_status == CDM_DIFF_IN_PROGRESS); + + ent->cdm_diff_status = status; + consensus_cache_entry_handle_free(ent->entry); + ent->entry = handle; +} + +/** + * Helper: Remove from the hash table every present (actually computed) diff + * of type <b>flav</b> whose target digest does not match + * <b>unless_target_sha3_matches</b>. + * + * This function is used for the hash table to throw away references to diffs + * that do not lead to the most given consensus of a given flavor. + */ +static void +cdm_diff_ht_purge(consensus_flavor_t flav, + const uint8_t *unless_target_sha3_matches) +{ + cdm_diff_t **diff, **next; + for (diff = HT_START(cdm_diff_ht, &cdm_diff_ht); diff; diff = next) { + cdm_diff_t *this = *diff; + + if ((*diff)->cdm_diff_status == CDM_DIFF_PRESENT && + flav == (*diff)->flavor) { + + if (BUG((*diff)->entry == NULL) || + consensus_cache_entry_handle_get((*diff)->entry) == NULL) { + /* the underlying entry has gone away; drop this. */ + next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff); + cdm_diff_free(this); + continue; + } + + if (unless_target_sha3_matches && + fast_memneq(unless_target_sha3_matches, (*diff)->target_sha3, + DIGEST256_LEN)) { + /* target hash doesn't match; drop this. */ + next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff); + cdm_diff_free(this); + continue; + } + } + next = HT_NEXT(cdm_diff_ht, &cdm_diff_ht, diff); + } +} + +/** + * Helper: initialize <b>cons_diff_cache</b>. + */ +static void +cdm_cache_init(void) +{ + unsigned n_entries = consdiff_cfg.cache_max_num * 2; + + tor_assert(cons_diff_cache == NULL); + cons_diff_cache = consensus_cache_open("diff-cache", n_entries); + if (cons_diff_cache == NULL) { + // LCOV_EXCL_START + log_err(LD_FS, "Error: Couldn't open storage for consensus diffs."); + tor_assert_unreached(); + // LCOV_EXCL_STOP + } else { + consdiffmgr_set_cache_flags(); + } + cdm_cache_dirty = 1; + cdm_cache_loaded = 0; +} + +/** + * Helper: return the consensus_cache_t * that backs this manager, + * initializing it if needed. + */ +STATIC consensus_cache_t * +cdm_cache_get(void) +{ + if (PREDICT_UNLIKELY(cons_diff_cache == NULL)) { + cdm_cache_init(); + } + return cons_diff_cache; +} + +/** + * Helper: given a list of labels, prepend the hex-encoded SHA3 digest + * of the <b>bodylen</b>-byte object at <b>body</b> to those labels, + * with <b>label</b> as its label. + */ +static void +cdm_labels_prepend_sha3(config_line_t **labels, + const char *label, + const uint8_t *body, + size_t bodylen) +{ + uint8_t sha3_digest[DIGEST256_LEN]; + char hexdigest[HEX_DIGEST256_LEN+1]; + crypto_digest256((char *)sha3_digest, + (const char *)body, bodylen, DIGEST_SHA3_256); + base16_encode(hexdigest, sizeof(hexdigest), + (const char *)sha3_digest, sizeof(sha3_digest)); + + config_line_prepend(labels, label, hexdigest); +} + +/** Helper: if there is a sha3-256 hex-encoded digest in <b>ent</b> with the + * given label, set <b>digest_out</b> to that value (decoded), and return 0. + * + * Return -1 if there is no such label, and -2 if it is badly formatted. */ +STATIC int +cdm_entry_get_sha3_value(uint8_t *digest_out, + consensus_cache_entry_t *ent, + const char *label) +{ + if (ent == NULL) + return -1; + + const char *hex = consensus_cache_entry_get_value(ent, label); + if (hex == NULL) + return -1; + + int n = base16_decode((char*)digest_out, DIGEST256_LEN, hex, strlen(hex)); + if (n != DIGEST256_LEN) + return -2; + else + return 0; +} + +/** + * Helper: look for a consensus with the given <b>flavor</b> and + * <b>valid_after</b> time in the cache. Return that consensus if it's + * present, or NULL if it's missing. + */ +STATIC consensus_cache_entry_t * +cdm_cache_lookup_consensus(consensus_flavor_t flavor, time_t valid_after) +{ + char formatted_time[ISO_TIME_LEN+1]; + format_iso_time_nospace(formatted_time, valid_after); + const char *flavname = networkstatus_get_flavor_name(flavor); + + /* We'll filter by valid-after time first, since that should + * match the fewest documents. */ + /* We could add an extra hashtable here, but since we only do this scan + * when adding a new consensus, it probably doesn't matter much. */ + smartlist_t *matches = smartlist_new(); + consensus_cache_find_all(matches, cdm_cache_get(), + LABEL_VALID_AFTER, formatted_time); + consensus_cache_filter_list(matches, LABEL_FLAVOR, flavname); + consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS); + + consensus_cache_entry_t *result = NULL; + if (smartlist_len(matches)) { + result = smartlist_get(matches, 0); + } + smartlist_free(matches); + + return result; +} + +/** Return the maximum age (in seconds) of consensuses that we should consider + * storing. The available space in the directory may impose additional limits + * on how much we store. */ +static int32_t +get_max_age_to_cache(void) +{ + const int32_t DEFAULT_MAX_AGE_TO_CACHE = 8192; + const int32_t MIN_MAX_AGE_TO_CACHE = 0; + const int32_t MAX_MAX_AGE_TO_CACHE = 8192; + const char MAX_AGE_TO_CACHE_NAME[] = "max-consensus-age-to-cache-for-diff"; + + const or_options_t *options = get_options(); + + if (options->MaxConsensusAgeForDiffs) { + const int v = options->MaxConsensusAgeForDiffs; + if (v >= MAX_MAX_AGE_TO_CACHE * 3600) + return MAX_MAX_AGE_TO_CACHE; + else + return v; + } + + /* The parameter is in hours, so we multiply */ + return 3600 * networkstatus_get_param(NULL, + MAX_AGE_TO_CACHE_NAME, + DEFAULT_MAX_AGE_TO_CACHE, + MIN_MAX_AGE_TO_CACHE, + MAX_MAX_AGE_TO_CACHE); +} + +/** + * Given a string containing a networkstatus consensus, and the results of + * having parsed that consensus, add that consensus to the cache if it is not + * already present and not too old. Create new consensus diffs from or to + * that consensus as appropriate. + * + * Return 0 on success and -1 on failure. + */ +int +consdiffmgr_add_consensus(const char *consensus, + const networkstatus_t *as_parsed) +{ + if (BUG(consensus == NULL) || BUG(as_parsed == NULL)) + return -1; // LCOV_EXCL_LINE + if (BUG(as_parsed->type != NS_TYPE_CONSENSUS)) + return -1; // LCOV_EXCL_LINE + + const consensus_flavor_t flavor = as_parsed->flavor; + const time_t valid_after = as_parsed->valid_after; + + if (valid_after < approx_time() - get_max_age_to_cache()) { + log_info(LD_DIRSERV, "We don't care about this consensus document; it's " + "too old."); + return -1; + } + + /* Do we already have this one? */ + consensus_cache_entry_t *entry = + cdm_cache_lookup_consensus(flavor, valid_after); + if (entry) { + log_info(LD_DIRSERV, "We already have a copy of that consensus"); + return -1; + } + + /* We don't have it. Add it to the cache. */ + return consensus_queue_compression_work(consensus, as_parsed); +} + +/** + * Helper: used to sort two smartlists of consensus_cache_entry_t by their + * LABEL_VALID_AFTER labels. + */ +static int +compare_by_valid_after_(const void **a, const void **b) +{ + const consensus_cache_entry_t *e1 = *a; + const consensus_cache_entry_t *e2 = *b; + /* We're in luck here: sorting UTC iso-encoded values lexically will work + * fine (until 9999). */ + return strcmp_opt(consensus_cache_entry_get_value(e1, LABEL_VALID_AFTER), + consensus_cache_entry_get_value(e2, LABEL_VALID_AFTER)); +} + +/** + * Helper: Sort <b>lst</b> by LABEL_VALID_AFTER and return the most recent + * entry. + */ +static consensus_cache_entry_t * +sort_and_find_most_recent(smartlist_t *lst) +{ + smartlist_sort(lst, compare_by_valid_after_); + if (smartlist_len(lst)) { + return smartlist_get(lst, smartlist_len(lst) - 1); + } else { + return NULL; + } +} + +/** Return i such that compress_consensus_with[i] == method. Return + * -1 if no such i exists. */ +static int +consensus_compression_method_pos(compress_method_t method) +{ + unsigned i; + for (i = 0; i < n_consensus_compression_methods(); ++i) { + if (compress_consensus_with[i] == method) { + return i; + } + } + return -1; +} + +/** + * If we know a consensus with the flavor <b>flavor</b> compressed with + * <b>method</b>, set *<b>entry_out</b> to that value. Return values are as + * for consdiffmgr_find_diff_from(). + */ +consdiff_status_t +consdiffmgr_find_consensus(struct consensus_cache_entry_t **entry_out, + consensus_flavor_t flavor, + compress_method_t method) +{ + tor_assert(entry_out); + tor_assert((int)flavor < N_CONSENSUS_FLAVORS); + + int pos = consensus_compression_method_pos(method); + if (pos < 0) { + // We don't compress consensuses with this method. + return CONSDIFF_NOT_FOUND; + } + consensus_cache_entry_handle_t *handle = latest_consensus[flavor][pos]; + if (!handle) + return CONSDIFF_NOT_FOUND; + *entry_out = consensus_cache_entry_handle_get(handle); + if (*entry_out) + return CONSDIFF_AVAILABLE; + else + return CONSDIFF_NOT_FOUND; +} + +/** + * Look up consensus_cache_entry_t for the consensus of type <b>flavor</b>, + * from the source consensus with the specified digest (which must be SHA3). + * + * If the diff is present, store it into *<b>entry_out</b> and return + * CONSDIFF_AVAILABLE. Otherwise return CONSDIFF_NOT_FOUND or + * CONSDIFF_IN_PROGRESS. + */ +consdiff_status_t +consdiffmgr_find_diff_from(consensus_cache_entry_t **entry_out, + consensus_flavor_t flavor, + int digest_type, + const uint8_t *digest, + size_t digestlen, + compress_method_t method) +{ + if (BUG(digest_type != DIGEST_SHA3_256) || + BUG(digestlen != DIGEST256_LEN)) { + return CONSDIFF_NOT_FOUND; // LCOV_EXCL_LINE + } + + // Try to look up the entry in the hashtable. + cdm_diff_t search, *ent; + memset(&search, 0, sizeof(search)); + search.flavor = flavor; + search.compress_method = method; + memcpy(search.from_sha3, digest, DIGEST256_LEN); + ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search); + + if (ent == NULL || + ent->cdm_diff_status == CDM_DIFF_ERROR) { + return CONSDIFF_NOT_FOUND; + } else if (ent->cdm_diff_status == CDM_DIFF_IN_PROGRESS) { + return CONSDIFF_IN_PROGRESS; + } else if (BUG(ent->cdm_diff_status != CDM_DIFF_PRESENT)) { + return CONSDIFF_IN_PROGRESS; + } + + if (BUG(ent->entry == NULL)) { + return CONSDIFF_NOT_FOUND; + } + *entry_out = consensus_cache_entry_handle_get(ent->entry); + return (*entry_out) ? CONSDIFF_AVAILABLE : CONSDIFF_NOT_FOUND; + +#if 0 + // XXXX Remove this. I'm keeping it around for now in case we need to + // XXXX debug issues in the hashtable. + char hex[HEX_DIGEST256_LEN+1]; + base16_encode(hex, sizeof(hex), (const char *)digest, digestlen); + const char *flavname = networkstatus_get_flavor_name(flavor); + + smartlist_t *matches = smartlist_new(); + consensus_cache_find_all(matches, cdm_cache_get(), + LABEL_FROM_SHA3_DIGEST, hex); + consensus_cache_filter_list(matches, LABEL_FLAVOR, flavname); + consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF); + + *entry_out = sort_and_find_most_recent(matches); + consdiff_status_t result = + (*entry_out) ? CONSDIFF_AVAILABLE : CONSDIFF_NOT_FOUND; + smartlist_free(matches); + + return result; +#endif +} + +/** + * Perform periodic cleanup tasks on the consensus diff cache. Return + * the number of objects marked for deletion. + */ +int +consdiffmgr_cleanup(void) +{ + smartlist_t *objects = smartlist_new(); + smartlist_t *consensuses = smartlist_new(); + smartlist_t *diffs = smartlist_new(); + int n_to_delete = 0; + + log_debug(LD_DIRSERV, "Looking for consdiffmgr entries to remove"); + + // 1. Delete any consensus or diff or anything whose valid_after is too old. + const time_t valid_after_cutoff = approx_time() - get_max_age_to_cache(); + + consensus_cache_find_all(objects, cdm_cache_get(), + NULL, NULL); + SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) { + const char *lv_valid_after = + consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER); + if (! lv_valid_after) { + log_debug(LD_DIRSERV, "Ignoring entry because it had no %s label", + LABEL_VALID_AFTER); + continue; + } + time_t valid_after = 0; + if (parse_iso_time_nospace(lv_valid_after, &valid_after) < 0) { + log_debug(LD_DIRSERV, "Ignoring entry because its %s value (%s) was " + "unparseable", LABEL_VALID_AFTER, escaped(lv_valid_after)); + continue; + } + if (valid_after < valid_after_cutoff) { + log_debug(LD_DIRSERV, "Deleting entry because its %s value (%s) was " + "too old", LABEL_VALID_AFTER, lv_valid_after); + consensus_cache_entry_mark_for_removal(ent); + ++n_to_delete; + } + } SMARTLIST_FOREACH_END(ent); + + // 2. Delete all diffs that lead to a consensus whose valid-after is not the + // latest. + for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) { + const char *flavname = networkstatus_get_flavor_name(flav); + /* Determine the most recent consensus of this flavor */ + consensus_cache_find_all(consensuses, cdm_cache_get(), + LABEL_DOCTYPE, DOCTYPE_CONSENSUS); + consensus_cache_filter_list(consensuses, LABEL_FLAVOR, flavname); + consensus_cache_entry_t *most_recent = + sort_and_find_most_recent(consensuses); + if (most_recent == NULL) + continue; + const char *most_recent_sha3 = + consensus_cache_entry_get_value(most_recent, + LABEL_SHA3_DIGEST_UNCOMPRESSED); + if (BUG(most_recent_sha3 == NULL)) + continue; // LCOV_EXCL_LINE + + /* consider all such-flavored diffs, and look to see if they match. */ + consensus_cache_find_all(diffs, cdm_cache_get(), + LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF); + consensus_cache_filter_list(diffs, LABEL_FLAVOR, flavname); + SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) { + const char *this_diff_target_sha3 = + consensus_cache_entry_get_value(diff, LABEL_TARGET_SHA3_DIGEST); + if (!this_diff_target_sha3) + continue; + if (strcmp(this_diff_target_sha3, most_recent_sha3)) { + consensus_cache_entry_mark_for_removal(diff); + ++n_to_delete; + } + } SMARTLIST_FOREACH_END(diff); + smartlist_clear(consensuses); + smartlist_clear(diffs); + } + + // 3. Delete all consensuses except the most recent that are compressed with + // an un-preferred method. + for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) { + const char *flavname = networkstatus_get_flavor_name(flav); + /* Determine the most recent consensus of this flavor */ + consensus_cache_find_all(consensuses, cdm_cache_get(), + LABEL_DOCTYPE, DOCTYPE_CONSENSUS); + consensus_cache_filter_list(consensuses, LABEL_FLAVOR, flavname); + consensus_cache_entry_t *most_recent = + sort_and_find_most_recent(consensuses); + if (most_recent == NULL) + continue; + const char *most_recent_sha3_uncompressed = + consensus_cache_entry_get_value(most_recent, + LABEL_SHA3_DIGEST_UNCOMPRESSED); + const char *retain_methodname = compression_method_get_name( + RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD); + + if (BUG(most_recent_sha3_uncompressed == NULL)) + continue; + SMARTLIST_FOREACH_BEGIN(consensuses, consensus_cache_entry_t *, ent) { + const char *lv_sha3_uncompressed = + consensus_cache_entry_get_value(ent, LABEL_SHA3_DIGEST_UNCOMPRESSED); + if (BUG(! lv_sha3_uncompressed)) + continue; + if (!strcmp(lv_sha3_uncompressed, most_recent_sha3_uncompressed)) + continue; // This _is_ the most recent. + const char *lv_methodname = + consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE); + if (! lv_methodname || strcmp(lv_methodname, retain_methodname)) { + consensus_cache_entry_mark_for_removal(ent); + ++n_to_delete; + } + } SMARTLIST_FOREACH_END(ent); + } + + smartlist_free(objects); + smartlist_free(consensuses); + smartlist_free(diffs); + + // Actually remove files, if they're not used. + consensus_cache_delete_pending(cdm_cache_get(), 0); + return n_to_delete; +} + +/** + * Initialize the consensus diff manager and its cache, and configure + * its parameters based on the latest torrc and networkstatus parameters. + */ +void +consdiffmgr_configure(const consdiff_cfg_t *cfg) +{ + if (cfg) + memcpy(&consdiff_cfg, cfg, sizeof(consdiff_cfg)); + + (void) cdm_cache_get(); +} + +/** + * Tell the sandbox (if any) configured by <b>cfg</b> to allow the + * operations that the consensus diff manager will need. + */ +int +consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem **cfg) +{ + return consensus_cache_register_with_sandbox(cdm_cache_get(), cfg); +} + +/** + * Scan the consensus diff manager's cache for any grossly malformed entries, + * and mark them as deletable. Return 0 if no problems were found; 1 + * if problems were found and fixed. + */ +int +consdiffmgr_validate(void) +{ + /* Right now, we only check for entries that have bad sha3 values */ + int problems = 0; + + smartlist_t *objects = smartlist_new(); + consensus_cache_find_all(objects, cdm_cache_get(), + NULL, NULL); + SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, obj) { + uint8_t sha3_expected[DIGEST256_LEN]; + uint8_t sha3_received[DIGEST256_LEN]; + int r = cdm_entry_get_sha3_value(sha3_expected, obj, LABEL_SHA3_DIGEST); + if (r == -1) { + /* digest isn't there; that's allowed */ + continue; + } else if (r == -2) { + /* digest is malformed; that's not allowed */ + problems = 1; + consensus_cache_entry_mark_for_removal(obj); + continue; + } + const uint8_t *body; + size_t bodylen; + consensus_cache_entry_incref(obj); + r = consensus_cache_entry_get_body(obj, &body, &bodylen); + if (r == 0) { + crypto_digest256((char *)sha3_received, (const char *)body, bodylen, + DIGEST_SHA3_256); + } + consensus_cache_entry_decref(obj); + if (r < 0) + continue; + + // Deconfuse coverity about the possibility of sha3_received being + // uninitialized + tor_assert(r <= 0); + + if (fast_memneq(sha3_received, sha3_expected, DIGEST256_LEN)) { + problems = 1; + consensus_cache_entry_mark_for_removal(obj); + continue; + } + + } SMARTLIST_FOREACH_END(obj); + smartlist_free(objects); + return problems; +} + +/** + * Helper: build new diffs of <b>flavor</b> as needed + */ +static void +consdiffmgr_rescan_flavor_(consensus_flavor_t flavor) +{ + smartlist_t *matches = NULL; + smartlist_t *diffs = NULL; + smartlist_t *compute_diffs_from = NULL; + strmap_t *have_diff_from = NULL; + + // look for the most recent consensus, and for all previous in-range + // consensuses. Do they all have diffs to it? + const char *flavname = networkstatus_get_flavor_name(flavor); + + // 1. find the most recent consensus, and the ones that we might want + // to diff to it. + const char *methodname = compression_method_get_name( + RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD); + + matches = smartlist_new(); + consensus_cache_find_all(matches, cdm_cache_get(), + LABEL_FLAVOR, flavname); + consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS); + consensus_cache_filter_list(matches, LABEL_COMPRESSION_TYPE, methodname); + consensus_cache_entry_t *most_recent = sort_and_find_most_recent(matches); + if (!most_recent) { + log_info(LD_DIRSERV, "No 'most recent' %s consensus found; " + "not making diffs", flavname); + goto done; + } + tor_assert(smartlist_len(matches)); + smartlist_del(matches, smartlist_len(matches) - 1); + + const char *most_recent_valid_after = + consensus_cache_entry_get_value(most_recent, LABEL_VALID_AFTER); + if (BUG(most_recent_valid_after == NULL)) + goto done; //LCOV_EXCL_LINE + uint8_t most_recent_sha3[DIGEST256_LEN]; + if (BUG(cdm_entry_get_sha3_value(most_recent_sha3, most_recent, + LABEL_SHA3_DIGEST_UNCOMPRESSED) < 0)) + goto done; //LCOV_EXCL_LINE + + // 2. Find all the relevant diffs _to_ this consensus. These are ones + // that we don't need to compute. + diffs = smartlist_new(); + consensus_cache_find_all(diffs, cdm_cache_get(), + LABEL_VALID_AFTER, most_recent_valid_after); + consensus_cache_filter_list(diffs, LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF); + consensus_cache_filter_list(diffs, LABEL_FLAVOR, flavname); + have_diff_from = strmap_new(); + SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) { + const char *va = consensus_cache_entry_get_value(diff, + LABEL_FROM_VALID_AFTER); + if (BUG(va == NULL)) + continue; // LCOV_EXCL_LINE + strmap_set(have_diff_from, va, diff); + } SMARTLIST_FOREACH_END(diff); + + // 3. See which consensuses in 'matches' don't have diffs yet. + smartlist_reverse(matches); // from newest to oldest. + compute_diffs_from = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(matches, consensus_cache_entry_t *, ent) { + const char *va = consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER); + if (BUG(va == NULL)) + continue; // LCOV_EXCL_LINE + if (strmap_get(have_diff_from, va) != NULL) + continue; /* we already have this one. */ + smartlist_add(compute_diffs_from, ent); + /* Since we are not going to serve this as the most recent consensus + * any more, we should stop keeping it mmap'd when it's not in use. + */ + consensus_cache_entry_mark_for_aggressive_release(ent); + } SMARTLIST_FOREACH_END(ent); + + log_info(LD_DIRSERV, + "The most recent %s consensus is valid-after %s. We have diffs to " + "this consensus for %d/%d older %s consensuses. Generating diffs " + "for the other %d.", + flavname, + most_recent_valid_after, + smartlist_len(matches) - smartlist_len(compute_diffs_from), + smartlist_len(matches), + flavname, + smartlist_len(compute_diffs_from)); + + // 4. Update the hashtable; remove entries in this flavor to other + // target consensuses. + cdm_diff_ht_purge(flavor, most_recent_sha3); + + // 5. Actually launch the requests. + SMARTLIST_FOREACH_BEGIN(compute_diffs_from, consensus_cache_entry_t *, c) { + if (BUG(c == most_recent)) + continue; // LCOV_EXCL_LINE + + uint8_t this_sha3[DIGEST256_LEN]; + if (cdm_entry_get_sha3_value(this_sha3, c, + LABEL_SHA3_DIGEST_AS_SIGNED)<0) { + // Not actually a bug, since we might be running with a directory + // with stale files from before the #22143 fixes. + continue; + } + if (cdm_diff_ht_check_and_note_pending(flavor, + this_sha3, most_recent_sha3)) { + // This is already pending, or we encountered an error. + continue; + } + consensus_diff_queue_diff_work(c, most_recent); + } SMARTLIST_FOREACH_END(c); + + done: + smartlist_free(matches); + smartlist_free(diffs); + smartlist_free(compute_diffs_from); + strmap_free(have_diff_from, NULL); +} + +/** + * Scan the cache for the latest consensuses and add their handles to + * latest_consensus + */ +static void +consdiffmgr_consensus_load(void) +{ + smartlist_t *matches = smartlist_new(); + for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) { + const char *flavname = networkstatus_get_flavor_name(flav); + smartlist_clear(matches); + consensus_cache_find_all(matches, cdm_cache_get(), + LABEL_FLAVOR, flavname); + consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS); + consensus_cache_entry_t *most_recent = sort_and_find_most_recent(matches); + if (! most_recent) + continue; // no consensuses. + const char *most_recent_sha3 = + consensus_cache_entry_get_value(most_recent, + LABEL_SHA3_DIGEST_UNCOMPRESSED); + if (BUG(most_recent_sha3 == NULL)) + continue; // LCOV_EXCL_LINE + consensus_cache_filter_list(matches, LABEL_SHA3_DIGEST_UNCOMPRESSED, + most_recent_sha3); + + // Everything that remains matches the most recent consensus of this + // flavor. + SMARTLIST_FOREACH_BEGIN(matches, consensus_cache_entry_t *, ent) { + const char *lv_compression = + consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE); + compress_method_t method = + compression_method_get_by_name(lv_compression); + int pos = consensus_compression_method_pos(method); + if (pos < 0) + continue; + consensus_cache_entry_handle_free(latest_consensus[flav][pos]); + latest_consensus[flav][pos] = consensus_cache_entry_handle_new(ent); + } SMARTLIST_FOREACH_END(ent); + } + smartlist_free(matches); +} + +/** + * Scan the cache for diffs, and add them to the hashtable. + */ +static void +consdiffmgr_diffs_load(void) +{ + smartlist_t *diffs = smartlist_new(); + consensus_cache_find_all(diffs, cdm_cache_get(), + LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF); + SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) { + const char *lv_flavor = + consensus_cache_entry_get_value(diff, LABEL_FLAVOR); + if (!lv_flavor) + continue; + int flavor = networkstatus_parse_flavor_name(lv_flavor); + if (flavor < 0) + continue; + const char *lv_compression = + consensus_cache_entry_get_value(diff, LABEL_COMPRESSION_TYPE); + compress_method_t method = NO_METHOD; + if (lv_compression) { + method = compression_method_get_by_name(lv_compression); + if (method == UNKNOWN_METHOD) { + continue; + } + } + + uint8_t from_sha3[DIGEST256_LEN]; + uint8_t to_sha3[DIGEST256_LEN]; + if (cdm_entry_get_sha3_value(from_sha3, diff, LABEL_FROM_SHA3_DIGEST)<0) + continue; + if (cdm_entry_get_sha3_value(to_sha3, diff, LABEL_TARGET_SHA3_DIGEST)<0) + continue; + + cdm_diff_ht_set_status(flavor, from_sha3, to_sha3, + method, + CDM_DIFF_PRESENT, + consensus_cache_entry_handle_new(diff)); + } SMARTLIST_FOREACH_END(diff); + smartlist_free(diffs); +} + +/** + * Build new diffs as needed. + */ +void +consdiffmgr_rescan(void) +{ + if (cdm_cache_dirty == 0) + return; + + // Clean up here to make room for new diffs, and to ensure that older + // consensuses do not have any entries. + consdiffmgr_cleanup(); + + if (cdm_cache_loaded == 0) { + consdiffmgr_diffs_load(); + consdiffmgr_consensus_load(); + cdm_cache_loaded = 1; + } + + for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) { + consdiffmgr_rescan_flavor_((consensus_flavor_t) flav); + } + + cdm_cache_dirty = 0; +} + +/** + * Helper: compare two files by their from-valid-after and valid-after labels, + * trying to sort in ascending order by from-valid-after (when present) and + * valid-after (when not). Place everything that has neither label first in + * the list. + */ +static int +compare_by_staleness_(const void **a, const void **b) +{ + const consensus_cache_entry_t *e1 = *a; + const consensus_cache_entry_t *e2 = *b; + const char *va1, *fva1, *va2, *fva2; + va1 = consensus_cache_entry_get_value(e1, LABEL_VALID_AFTER); + va2 = consensus_cache_entry_get_value(e2, LABEL_VALID_AFTER); + fva1 = consensus_cache_entry_get_value(e1, LABEL_FROM_VALID_AFTER); + fva2 = consensus_cache_entry_get_value(e2, LABEL_FROM_VALID_AFTER); + + if (fva1) + va1 = fva1; + if (fva2) + va2 = fva2; + + /* See note about iso-encoded values in compare_by_valid_after_. Also note + * that missing dates will get placed first. */ + return strcmp_opt(va1, va2); +} + +/** If there are not enough unused filenames to store <b>n</b> files, then + * delete old consensuses until there are. (We have to keep track of the + * number of filenames because of the way that the seccomp2 cache works.) + * + * Return 0 on success, -1 on failure. + **/ +static int +consdiffmgr_ensure_space_for_files(int n) +{ + consensus_cache_t *cache = cdm_cache_get(); + if (consensus_cache_get_n_filenames_available(cache) >= n) { + // there are already enough unused filenames. + return 0; + } + // Try a cheap deletion of stuff that's waiting to get deleted. + consensus_cache_delete_pending(cache, 0); + if (consensus_cache_get_n_filenames_available(cache) >= n) { + // okay, _that_ made enough filenames available. + return 0; + } + // Let's get more assertive: clean out unused stuff, and force-remove + // the files that we can. + consdiffmgr_cleanup(); + consensus_cache_delete_pending(cache, 1); + const int n_to_remove = n - consensus_cache_get_n_filenames_available(cache); + if (n_to_remove <= 0) { + // okay, finally! + return 0; + } + + // At this point, we're going to have to throw out objects that will be + // missed. Too bad! + smartlist_t *objects = smartlist_new(); + consensus_cache_find_all(objects, cache, NULL, NULL); + smartlist_sort(objects, compare_by_staleness_); + int n_marked = 0; + SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) { + consensus_cache_entry_mark_for_removal(ent); + if (++n_marked >= n_to_remove) + break; + } SMARTLIST_FOREACH_END(ent); + smartlist_free(objects); + + consensus_cache_delete_pending(cache, 1); + + if (consensus_cache_may_overallocate(cache)) { + /* If we're allowed to throw extra files into the cache, let's do so + * rather getting upset. + */ + return 0; + } + + if (BUG(n_marked < n_to_remove)) + return -1; + else + return 0; +} + +/** + * Set consensus cache flags on the objects in this consdiffmgr. + */ +static void +consdiffmgr_set_cache_flags(void) +{ + /* Right now, we just mark the consensus objects for aggressive release, + * so that they get mmapped for as little time as possible. */ + smartlist_t *objects = smartlist_new(); + consensus_cache_find_all(objects, cdm_cache_get(), LABEL_DOCTYPE, + DOCTYPE_CONSENSUS); + SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) { + consensus_cache_entry_mark_for_aggressive_release(ent); + } SMARTLIST_FOREACH_END(ent); + smartlist_free(objects); +} + +/** + * Called before shutdown: drop all storage held by the consdiffmgr.c module. + */ +void +consdiffmgr_free_all(void) +{ + cdm_diff_t **diff, **next; + for (diff = HT_START(cdm_diff_ht, &cdm_diff_ht); diff; diff = next) { + cdm_diff_t *this = *diff; + next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff); + cdm_diff_free(this); + } + int i; + unsigned j; + for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) { + for (j = 0; j < n_consensus_compression_methods(); ++j) { + consensus_cache_entry_handle_free(latest_consensus[i][j]); + } + } + memset(latest_consensus, 0, sizeof(latest_consensus)); + consensus_cache_free(cons_diff_cache); + cons_diff_cache = NULL; +} + +/* ===== + Thread workers + =====*/ + +typedef struct compressed_result_t { + config_line_t *labels; + /** + * Output: Body of the diff, as compressed. + */ + uint8_t *body; + /** + * Output: length of body_out + */ + size_t bodylen; +} compressed_result_t; + +/** + * Compress the bytestring <b>input</b> of length <b>len</b> using the + * <n>n_methods</b> compression methods listed in the array <b>methods</b>. + * + * For each successful compression, set the fields in the <b>results_out</b> + * array in the position corresponding to the compression method. Use + * <b>labels_in</b> as a basis for the labels of the result. + * + * Return 0 if all compression succeeded; -1 if any failed. + */ +static int +compress_multiple(compressed_result_t *results_out, int n_methods, + const compress_method_t *methods, + const uint8_t *input, size_t len, + const config_line_t *labels_in) +{ + int rv = 0; + int i; + for (i = 0; i < n_methods; ++i) { + compress_method_t method = methods[i]; + const char *methodname = compression_method_get_name(method); + char *result; + size_t sz; + if (0 == tor_compress(&result, &sz, (const char*)input, len, method)) { + results_out[i].body = (uint8_t*)result; + results_out[i].bodylen = sz; + results_out[i].labels = config_lines_dup(labels_in); + cdm_labels_prepend_sha3(&results_out[i].labels, LABEL_SHA3_DIGEST, + results_out[i].body, + results_out[i].bodylen); + config_line_prepend(&results_out[i].labels, + LABEL_COMPRESSION_TYPE, + methodname); + } else { + rv = -1; + } + } + return rv; +} + +/** + * Given an array of <b>n</b> compressed_result_t in <b>results</b>, + * as produced by compress_multiple, store them all into the + * consdiffmgr, and store handles to them in the <b>handles_out</b> + * array. + * + * Return CDM_DIFF_PRESENT if any was stored, and CDM_DIFF_ERROR if none + * was stored. + */ +static cdm_diff_status_t +store_multiple(consensus_cache_entry_handle_t **handles_out, + int n, + const compress_method_t *methods, + const compressed_result_t *results, + const char *description) +{ + cdm_diff_status_t status = CDM_DIFF_ERROR; + consdiffmgr_ensure_space_for_files(n); + + int i; + for (i = 0; i < n; ++i) { + compress_method_t method = methods[i]; + uint8_t *body_out = results[i].body; + size_t bodylen_out = results[i].bodylen; + config_line_t *labels = results[i].labels; + const char *methodname = compression_method_get_name(method); + if (body_out && bodylen_out && labels) { + /* Success! Store the results */ + log_info(LD_DIRSERV, "Adding %s, compressed with %s", + description, methodname); + + consensus_cache_entry_t *ent = + consensus_cache_add(cdm_cache_get(), + labels, + body_out, + bodylen_out); + if (BUG(ent == NULL)) + continue; + + status = CDM_DIFF_PRESENT; + handles_out[i] = consensus_cache_entry_handle_new(ent); + consensus_cache_entry_decref(ent); + } + } + return status; +} + +/** + * An object passed to a worker thread that will try to produce a consensus + * diff. + */ +typedef struct consensus_diff_worker_job_t { + /** + * Input: The consensus to compute the diff from. Holds a reference to the + * cache entry, which must not be released until the job is passed back to + * the main thread. The body must be mapped into memory in the main thread. + */ + consensus_cache_entry_t *diff_from; + /** + * Input: The consensus to compute the diff to. Holds a reference to the + * cache entry, which must not be released until the job is passed back to + * the main thread. The body must be mapped into memory in the main thread. + */ + consensus_cache_entry_t *diff_to; + + /** Output: labels and bodies */ + compressed_result_t out[ARRAY_LENGTH(compress_diffs_with)]; +} consensus_diff_worker_job_t; + +/** Given a consensus_cache_entry_t, check whether it has a label claiming + * that it was compressed. If so, uncompress its contents into <b>out</b> and + * set <b>outlen</b> to hold their size. If not, just copy the body into + * <b>out</b> and set <b>outlen</b> to its length. Return 0 on success, + * -1 on failure. + * + * In all cases, the output is nul-terminated. */ +STATIC int +uncompress_or_copy(char **out, size_t *outlen, + consensus_cache_entry_t *ent) +{ + const uint8_t *body; + size_t bodylen; + + if (consensus_cache_entry_get_body(ent, &body, &bodylen) < 0) + return -1; + + const char *lv_compression = + consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE); + compress_method_t method = NO_METHOD; + + if (lv_compression) + method = compression_method_get_by_name(lv_compression); + + return tor_uncompress(out, outlen, (const char *)body, bodylen, + method, 1, LOG_WARN); +} + +/** + * Worker function. This function runs inside a worker thread and receives + * a consensus_diff_worker_job_t as its input. + */ +static workqueue_reply_t +consensus_diff_worker_threadfn(void *state_, void *work_) +{ + (void)state_; + consensus_diff_worker_job_t *job = work_; + const uint8_t *diff_from, *diff_to; + size_t len_from, len_to; + int r; + /* We need to have the body already mapped into RAM here. + */ + r = consensus_cache_entry_get_body(job->diff_from, &diff_from, &len_from); + if (BUG(r < 0)) + return WQ_RPL_REPLY; // LCOV_EXCL_LINE + r = consensus_cache_entry_get_body(job->diff_to, &diff_to, &len_to); + if (BUG(r < 0)) + return WQ_RPL_REPLY; // LCOV_EXCL_LINE + + const char *lv_to_valid_after = + consensus_cache_entry_get_value(job->diff_to, LABEL_VALID_AFTER); + const char *lv_to_fresh_until = + consensus_cache_entry_get_value(job->diff_to, LABEL_FRESH_UNTIL); + const char *lv_to_valid_until = + consensus_cache_entry_get_value(job->diff_to, LABEL_VALID_UNTIL); + const char *lv_to_signatories = + consensus_cache_entry_get_value(job->diff_to, LABEL_SIGNATORIES); + const char *lv_from_valid_after = + consensus_cache_entry_get_value(job->diff_from, LABEL_VALID_AFTER); + const char *lv_from_digest = + consensus_cache_entry_get_value(job->diff_from, + LABEL_SHA3_DIGEST_AS_SIGNED); + const char *lv_from_flavor = + consensus_cache_entry_get_value(job->diff_from, LABEL_FLAVOR); + const char *lv_to_flavor = + consensus_cache_entry_get_value(job->diff_to, LABEL_FLAVOR); + const char *lv_to_digest = + consensus_cache_entry_get_value(job->diff_to, + LABEL_SHA3_DIGEST_UNCOMPRESSED); + + if (! lv_from_digest) { + /* This isn't a bug right now, since it can happen if you're migrating + * from an older version of master to a newer one. The older ones didn't + * annotate their stored consensus objects with sha3-digest-as-signed. + */ + return WQ_RPL_REPLY; // LCOV_EXCL_LINE + } + + /* All these values are mandatory on the input */ + if (BUG(!lv_to_valid_after) || + BUG(!lv_from_valid_after) || + BUG(!lv_from_flavor) || + BUG(!lv_to_flavor)) { + return WQ_RPL_REPLY; // LCOV_EXCL_LINE + } + /* The flavors need to match */ + if (BUG(strcmp(lv_from_flavor, lv_to_flavor))) { + return WQ_RPL_REPLY; // LCOV_EXCL_LINE + } + + char *consensus_diff; + { + char *diff_from_nt = NULL, *diff_to_nt = NULL; + size_t diff_from_nt_len, diff_to_nt_len; + + if (uncompress_or_copy(&diff_from_nt, &diff_from_nt_len, + job->diff_from) < 0) { + return WQ_RPL_REPLY; + } + if (uncompress_or_copy(&diff_to_nt, &diff_to_nt_len, + job->diff_to) < 0) { + tor_free(diff_from_nt); + return WQ_RPL_REPLY; + } + tor_assert(diff_from_nt); + tor_assert(diff_to_nt); + + // XXXX ugh; this is going to calculate the SHA3 of both its + // XXXX inputs again, even though we already have that. Maybe it's time + // XXXX to change the API here? + consensus_diff = consensus_diff_generate(diff_from_nt, diff_to_nt); + tor_free(diff_from_nt); + tor_free(diff_to_nt); + } + if (!consensus_diff) { + /* Couldn't generate consensus; we'll leave the reply blank. */ + return WQ_RPL_REPLY; + } + + /* Compress the results and send the reply */ + tor_assert(compress_diffs_with[0] == NO_METHOD); + size_t difflen = strlen(consensus_diff); + job->out[0].body = (uint8_t *) consensus_diff; + job->out[0].bodylen = difflen; + + config_line_t *common_labels = NULL; + if (lv_to_valid_until) + config_line_prepend(&common_labels, LABEL_VALID_UNTIL, lv_to_valid_until); + if (lv_to_fresh_until) + config_line_prepend(&common_labels, LABEL_FRESH_UNTIL, lv_to_fresh_until); + if (lv_to_signatories) + config_line_prepend(&common_labels, LABEL_SIGNATORIES, lv_to_signatories); + cdm_labels_prepend_sha3(&common_labels, + LABEL_SHA3_DIGEST_UNCOMPRESSED, + job->out[0].body, + job->out[0].bodylen); + config_line_prepend(&common_labels, LABEL_FROM_VALID_AFTER, + lv_from_valid_after); + config_line_prepend(&common_labels, LABEL_VALID_AFTER, + lv_to_valid_after); + config_line_prepend(&common_labels, LABEL_FLAVOR, lv_from_flavor); + config_line_prepend(&common_labels, LABEL_FROM_SHA3_DIGEST, + lv_from_digest); + config_line_prepend(&common_labels, LABEL_TARGET_SHA3_DIGEST, + lv_to_digest); + config_line_prepend(&common_labels, LABEL_DOCTYPE, + DOCTYPE_CONSENSUS_DIFF); + + job->out[0].labels = config_lines_dup(common_labels); + cdm_labels_prepend_sha3(&job->out[0].labels, + LABEL_SHA3_DIGEST, + job->out[0].body, + job->out[0].bodylen); + + compress_multiple(job->out+1, + n_diff_compression_methods()-1, + compress_diffs_with+1, + (const uint8_t*)consensus_diff, difflen, common_labels); + + config_free_lines(common_labels); + return WQ_RPL_REPLY; +} + +/** + * Helper: release all storage held in <b>job</b>. + */ +static void +consensus_diff_worker_job_free(consensus_diff_worker_job_t *job) +{ + if (!job) + return; + unsigned u; + for (u = 0; u < n_diff_compression_methods(); ++u) { + config_free_lines(job->out[u].labels); + tor_free(job->out[u].body); + } + consensus_cache_entry_decref(job->diff_from); + consensus_cache_entry_decref(job->diff_to); + tor_free(job); +} + +/** + * Worker function: This function runs in the main thread, and receives + * a consensus_diff_worker_job_t that the worker thread has already + * processed. + */ +static void +consensus_diff_worker_replyfn(void *work_) +{ + tor_assert(in_main_thread()); + tor_assert(work_); + + consensus_diff_worker_job_t *job = work_; + + const char *lv_from_digest = + consensus_cache_entry_get_value(job->diff_from, + LABEL_SHA3_DIGEST_AS_SIGNED); + const char *lv_to_digest = + consensus_cache_entry_get_value(job->diff_to, + LABEL_SHA3_DIGEST_UNCOMPRESSED); + const char *lv_flavor = + consensus_cache_entry_get_value(job->diff_to, LABEL_FLAVOR); + if (BUG(lv_from_digest == NULL)) + lv_from_digest = "???"; // LCOV_EXCL_LINE + if (BUG(lv_to_digest == NULL)) + lv_to_digest = "???"; // LCOV_EXCL_LINE + + uint8_t from_sha3[DIGEST256_LEN]; + uint8_t to_sha3[DIGEST256_LEN]; + int flav = -1; + int cache = 1; + if (BUG(cdm_entry_get_sha3_value(from_sha3, job->diff_from, + LABEL_SHA3_DIGEST_AS_SIGNED) < 0)) + cache = 0; + if (BUG(cdm_entry_get_sha3_value(to_sha3, job->diff_to, + LABEL_SHA3_DIGEST_UNCOMPRESSED) < 0)) + cache = 0; + if (BUG(lv_flavor == NULL)) { + cache = 0; + } else if ((flav = networkstatus_parse_flavor_name(lv_flavor)) < 0) { + cache = 0; + } + + consensus_cache_entry_handle_t *handles[ARRAY_LENGTH(compress_diffs_with)]; + memset(handles, 0, sizeof(handles)); + + char description[128]; + tor_snprintf(description, sizeof(description), + "consensus diff from %s to %s", + lv_from_digest, lv_to_digest); + + int status = store_multiple(handles, + n_diff_compression_methods(), + compress_diffs_with, + job->out, + description); + + if (status != CDM_DIFF_PRESENT) { + /* Failure! Nothing to do but complain */ + log_warn(LD_DIRSERV, + "Worker was unable to compute consensus diff " + "from %s to %s", lv_from_digest, lv_to_digest); + /* Cache this error so we don't try to compute this one again. */ + status = CDM_DIFF_ERROR; + } + + unsigned u; + for (u = 0; u < ARRAY_LENGTH(handles); ++u) { + compress_method_t method = compress_diffs_with[u]; + if (cache) { + 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]); + } + } + + consensus_diff_worker_job_free(job); +} + +/** + * Queue the job of computing the diff from <b>diff_from</b> to <b>diff_to</b> + * in a worker thread. + */ +static int +consensus_diff_queue_diff_work(consensus_cache_entry_t *diff_from, + consensus_cache_entry_t *diff_to) +{ + tor_assert(in_main_thread()); + + consensus_cache_entry_incref(diff_from); + consensus_cache_entry_incref(diff_to); + + consensus_diff_worker_job_t *job = tor_malloc_zero(sizeof(*job)); + job->diff_from = diff_from; + job->diff_to = diff_to; + + /* Make sure body is mapped. */ + const uint8_t *body; + size_t bodylen; + int r1 = consensus_cache_entry_get_body(diff_from, &body, &bodylen); + int r2 = consensus_cache_entry_get_body(diff_to, &body, &bodylen); + if (r1 < 0 || r2 < 0) + goto err; + + workqueue_entry_t *work; + work = cpuworker_queue_work(WQ_PRI_LOW, + consensus_diff_worker_threadfn, + consensus_diff_worker_replyfn, + job); + if (!work) + goto err; + + return 0; + err: + consensus_diff_worker_job_free(job); // includes decrefs. + return -1; +} + +/** + * Holds requests and replies for consensus_compress_workers. + */ +typedef struct consensus_compress_worker_job_t { + char *consensus; + size_t consensus_len; + consensus_flavor_t flavor; + config_line_t *labels_in; + compressed_result_t out[ARRAY_LENGTH(compress_consensus_with)]; +} consensus_compress_worker_job_t; + +/** + * Free all resources held in <b>job</b> + */ +static void +consensus_compress_worker_job_free(consensus_compress_worker_job_t *job) +{ + if (!job) + return; + tor_free(job->consensus); + config_free_lines(job->labels_in); + unsigned u; + for (u = 0; u < n_consensus_compression_methods(); ++u) { + config_free_lines(job->out[u].labels); + tor_free(job->out[u].body); + } + tor_free(job); +} +/** + * Worker function. This function runs inside a worker thread and receives + * a consensus_compress_worker_job_t as its input. + */ +static workqueue_reply_t +consensus_compress_worker_threadfn(void *state_, void *work_) +{ + (void)state_; + consensus_compress_worker_job_t *job = work_; + consensus_flavor_t flavor = job->flavor; + const char *consensus = job->consensus; + size_t bodylen = job->consensus_len; + + config_line_t *labels = config_lines_dup(job->labels_in); + const char *flavname = networkstatus_get_flavor_name(flavor); + + cdm_labels_prepend_sha3(&labels, LABEL_SHA3_DIGEST_UNCOMPRESSED, + (const uint8_t *)consensus, bodylen); + { + const char *start, *end; + if (router_get_networkstatus_v3_signed_boundaries(consensus, + &start, &end) < 0) { + start = consensus; + end = consensus+bodylen; + } + cdm_labels_prepend_sha3(&labels, LABEL_SHA3_DIGEST_AS_SIGNED, + (const uint8_t *)start, + end - start); + } + config_line_prepend(&labels, LABEL_FLAVOR, flavname); + config_line_prepend(&labels, LABEL_DOCTYPE, DOCTYPE_CONSENSUS); + + compress_multiple(job->out, + n_consensus_compression_methods(), + compress_consensus_with, + (const uint8_t*)consensus, bodylen, labels); + config_free_lines(labels); + return WQ_RPL_REPLY; +} + +/** + * Worker function: This function runs in the main thread, and receives + * a consensus_diff_compress_job_t that the worker thread has already + * processed. + */ +static void +consensus_compress_worker_replyfn(void *work_) +{ + consensus_compress_worker_job_t *job = work_; + + consensus_cache_entry_handle_t *handles[ + ARRAY_LENGTH(compress_consensus_with)]; + memset(handles, 0, sizeof(handles)); + + store_multiple(handles, + n_consensus_compression_methods(), + compress_consensus_with, + job->out, + "consensus"); + cdm_cache_dirty = 1; + + unsigned u; + consensus_flavor_t f = job->flavor; + tor_assert((int)f < N_CONSENSUS_FLAVORS); + for (u = 0; u < ARRAY_LENGTH(handles); ++u) { + if (handles[u] == NULL) + continue; + consensus_cache_entry_handle_free(latest_consensus[f][u]); + latest_consensus[f][u] = handles[u]; + } + + consensus_compress_worker_job_free(job); +} + +/** + * If true, we compress in worker threads. + */ +static int background_compression = 0; + +/** + * Queue a job to compress <b>consensus</b> and store its compressed + * text in the cache. + */ +static int +consensus_queue_compression_work(const char *consensus, + const networkstatus_t *as_parsed) +{ + tor_assert(consensus); + tor_assert(as_parsed); + + consensus_compress_worker_job_t *job = tor_malloc_zero(sizeof(*job)); + job->consensus = tor_strdup(consensus); + job->consensus_len = strlen(consensus); + job->flavor = as_parsed->flavor; + + char va_str[ISO_TIME_LEN+1]; + char vu_str[ISO_TIME_LEN+1]; + char fu_str[ISO_TIME_LEN+1]; + format_iso_time_nospace(va_str, as_parsed->valid_after); + format_iso_time_nospace(fu_str, as_parsed->fresh_until); + format_iso_time_nospace(vu_str, as_parsed->valid_until); + config_line_append(&job->labels_in, LABEL_VALID_AFTER, va_str); + config_line_append(&job->labels_in, LABEL_FRESH_UNTIL, fu_str); + config_line_append(&job->labels_in, LABEL_VALID_UNTIL, vu_str); + if (as_parsed->voters) { + smartlist_t *hexvoters = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(as_parsed->voters, + networkstatus_voter_info_t *, vi) { + if (smartlist_len(vi->sigs) == 0) + continue; // didn't sign. + char d[HEX_DIGEST_LEN+1]; + base16_encode(d, sizeof(d), vi->identity_digest, DIGEST_LEN); + smartlist_add_strdup(hexvoters, d); + } SMARTLIST_FOREACH_END(vi); + char *signers = smartlist_join_strings(hexvoters, ",", 0, NULL); + config_line_prepend(&job->labels_in, LABEL_SIGNATORIES, signers); + tor_free(signers); + SMARTLIST_FOREACH(hexvoters, char *, cp, tor_free(cp)); + smartlist_free(hexvoters); + } + + if (background_compression) { + workqueue_entry_t *work; + work = cpuworker_queue_work(WQ_PRI_LOW, + consensus_compress_worker_threadfn, + consensus_compress_worker_replyfn, + job); + if (!work) { + consensus_compress_worker_job_free(job); + return -1; + } + + return 0; + } else { + consensus_compress_worker_threadfn(NULL, job); + consensus_compress_worker_replyfn(job); + return 0; + } +} + +/** + * Tell the consdiffmgr backend to compress consensuses in worker threads. + */ +void +consdiffmgr_enable_background_compression(void) +{ + // This isn't the default behavior because it would break unit tests. + background_compression = 1; +} + +/** Read the set of voters from the cached object <b>ent</b> into + * <b>out</b>, as a list of hex-encoded digests. Return 0 on success, + * -1 if no signatories were recorded. */ +int +consensus_cache_entry_get_voter_id_digests(const consensus_cache_entry_t *ent, + smartlist_t *out) +{ + tor_assert(ent); + tor_assert(out); + const char *s; + s = consensus_cache_entry_get_value(ent, LABEL_SIGNATORIES); + if (s == NULL) + return -1; + smartlist_split_string(out, s, ",", SPLIT_SKIP_SPACE|SPLIT_STRIP_SPACE, 0); + return 0; +} + +/** Read the fresh-until time of cached object <b>ent</b> into *<b>out</b> + * and return 0, or return -1 if no such time was recorded. */ +int +consensus_cache_entry_get_fresh_until(const consensus_cache_entry_t *ent, + time_t *out) +{ + tor_assert(ent); + tor_assert(out); + const char *s; + s = consensus_cache_entry_get_value(ent, LABEL_FRESH_UNTIL); + if (s == NULL || parse_iso_time_nospace(s, out) < 0) + return -1; + else + return 0; +} + +/** Read the valid until timestamp from the cached object <b>ent</b> into + * *<b>out</b> and return 0, or return -1 if no such time was recorded. */ +int +consensus_cache_entry_get_valid_until(const consensus_cache_entry_t *ent, + time_t *out) +{ + tor_assert(ent); + tor_assert(out); + + const char *s; + s = consensus_cache_entry_get_value(ent, LABEL_VALID_UNTIL); + if (s == NULL || parse_iso_time_nospace(s, out) < 0) + return -1; + else + return 0; +} + +/** Read the valid after timestamp from the cached object <b>ent</b> into + * *<b>out</b> and return 0, or return -1 if no such time was recorded. */ +int +consensus_cache_entry_get_valid_after(const consensus_cache_entry_t *ent, + time_t *out) +{ + tor_assert(ent); + tor_assert(out); + + const char *s; + s = consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER); + + if (s == NULL || parse_iso_time_nospace(s, out) < 0) + return -1; + else + return 0; +} + diff --git a/src/or/consdiffmgr.h b/src/or/consdiffmgr.h new file mode 100644 index 0000000000..079f9fe2d2 --- /dev/null +++ b/src/or/consdiffmgr.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_CONSDIFFMGR_H +#define TOR_CONSDIFFMGR_H + +/** + * Possible outcomes from trying to look up a given consensus diff. + */ +typedef enum consdiff_status_t { + CONSDIFF_AVAILABLE, + CONSDIFF_NOT_FOUND, + CONSDIFF_IN_PROGRESS, +} consdiff_status_t; + +typedef struct consdiff_cfg_t { + int32_t cache_max_num; +} consdiff_cfg_t; + +struct consensus_cache_entry_t; // from conscache.h + +int consdiffmgr_add_consensus(const char *consensus, + const networkstatus_t *as_parsed); + +consdiff_status_t consdiffmgr_find_consensus( + struct consensus_cache_entry_t **entry_out, + consensus_flavor_t flavor, + compress_method_t method); + +consdiff_status_t consdiffmgr_find_diff_from( + struct consensus_cache_entry_t **entry_out, + consensus_flavor_t flavor, + int digest_type, + const uint8_t *digest, + size_t digestlen, + compress_method_t method); + +int consensus_cache_entry_get_voter_id_digests( + const struct consensus_cache_entry_t *ent, + smartlist_t *out); +int consensus_cache_entry_get_fresh_until( + const struct consensus_cache_entry_t *ent, + time_t *out); +int consensus_cache_entry_get_valid_until( + const struct consensus_cache_entry_t *ent, + time_t *out); +int consensus_cache_entry_get_valid_after( + const struct consensus_cache_entry_t *ent, + time_t *out); + +void consdiffmgr_rescan(void); +int consdiffmgr_cleanup(void); +void consdiffmgr_enable_background_compression(void); +void consdiffmgr_configure(const consdiff_cfg_t *cfg); +struct sandbox_cfg_elem; +int consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem **cfg); +void consdiffmgr_free_all(void); +int consdiffmgr_validate(void); + +#ifdef CONSDIFFMGR_PRIVATE +STATIC unsigned n_diff_compression_methods(void); +STATIC unsigned n_consensus_compression_methods(void); +STATIC consensus_cache_t *cdm_cache_get(void); +STATIC consensus_cache_entry_t *cdm_cache_lookup_consensus( + consensus_flavor_t flavor, time_t valid_after); +STATIC int cdm_entry_get_sha3_value(uint8_t *digest_out, + consensus_cache_entry_t *ent, + const char *label); +STATIC int uncompress_or_copy(char **out, size_t *outlen, + consensus_cache_entry_t *ent); +#endif + +#endif + diff --git a/src/or/control.c b/src/or/control.c index 879d9bbed9..1837d49025 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -1462,8 +1462,10 @@ handle_control_saveconf(control_connection_t *conn, uint32_t len, const char *body) { (void) len; - (void) body; - if (options_save_current()<0) { + + int force = !strcmpstart(body, "FORCE"); + const or_options_t *options = get_options(); + if ((!force && options->IncludeUsed) || options_save_current() < 0) { connection_write_str_to_buf( "551 Unable to write configuration to disk.\r\n", conn); } else { @@ -1677,6 +1679,8 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, *answer = tor_strdup(a); } else if (!strcmp(question, "config-text")) { *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL); + } else if (!strcmp(question, "config-can-saveconf")) { + *answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1"); } else if (!strcmp(question, "info/names")) { *answer = list_getinfo_options(); } else if (!strcmp(question, "dormant")) { @@ -1873,7 +1877,7 @@ getinfo_helper_listeners(control_connection_t *control_conn, /** Implementation helper for GETINFO: knows the answers for questions about * directory information. */ -static int +STATIC int getinfo_helper_dir(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg) @@ -2047,6 +2051,12 @@ getinfo_helper_dir(control_connection_t *control_conn, } } } else if (!strcmp(question, "network-status")) { /* v1 */ + static int network_status_warned = 0; + if (!network_status_warned) { + log_warn(LD_CONTROL, "GETINFO network-status is deprecated; it will " + "go away in a future version of Tor."); + network_status_warned = 1; + } routerlist_t *routerlist = router_get_routerlist(); if (!routerlist || !routerlist->routers || list_server_status_v1(routerlist->routers, answer, 1) < 0) { @@ -2824,12 +2834,13 @@ getinfo_helper_events(control_connection_t *control_conn, /** Implementation helper for GETINFO: knows how to enumerate hidden services * created via the control port. */ -static int +STATIC int getinfo_helper_onions(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg) { smartlist_t *onion_list = NULL; + (void) errmsg; /* no errors from this method */ if (control_conn && !strcmp(question, "onions/current")) { onion_list = control_conn->ephemeral_onion_services; @@ -2839,13 +2850,13 @@ getinfo_helper_onions(control_connection_t *control_conn, return 0; } if (!onion_list || smartlist_len(onion_list) == 0) { - if (errmsg) { - *errmsg = "No onion services of the specified type."; + if (answer) { + *answer = tor_strdup(""); } - return -1; - } - if (answer) { + } else { + if (answer) { *answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL); + } } return 0; @@ -2924,6 +2935,8 @@ static const getinfo_item_t getinfo_items[] = { ITEM("config-defaults-file", misc, "Current location of the defaults file."), ITEM("config-text", misc, "Return the string that would be written by a saveconf command."), + ITEM("config-can-saveconf", misc, + "Is it possible to save the configuration to the \"torrc\" file?"), ITEM("accounting/bytes", accounting, "Number of bytes read/written so far in the accounting interval."), ITEM("accounting/bytes-left", accounting, @@ -3544,24 +3557,9 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, } /* Is this a single hop circuit? */ if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { - const node_t *node = NULL; - char *exit_digest = NULL; - if (circ->build_state && - circ->build_state->chosen_exit && - !tor_digest_is_zero(circ->build_state->chosen_exit->identity_digest)) { - exit_digest = circ->build_state->chosen_exit->identity_digest; - node = node_get_by_id(exit_digest); - } - /* Do both the client and relay allow one-hop exit circuits? */ - if (!node || - !node_allows_single_hop_exits(node) || - !get_options()->AllowSingleHopCircuits) { - connection_write_str_to_buf( - "551 Can't attach stream to this one-hop circuit.\r\n", conn); - return 0; - } - tor_assert(exit_digest); - ap_conn->chosen_exit_name = tor_strdup(hex_str(exit_digest, DIGEST_LEN)); + connection_write_str_to_buf( + "551 Can't attach stream to this one-hop circuit.\r\n", conn); + return 0; } if (circ && hop>0) { @@ -6516,7 +6514,7 @@ monitor_owning_controller_process(const char *process_spec) msg); owning_controller_process_spec = NULL; tor_cleanup(); - exit(0); + exit(1); } } @@ -6719,8 +6717,8 @@ control_event_bootstrap(bootstrap_status_t status, int progress) * is the connection that caused this problem. */ MOCK_IMPL(void, - control_event_bootstrap_problem, (const char *warn, int reason, - or_connection_t *or_conn)) +control_event_bootstrap_problem, (const char *warn, int reason, + or_connection_t *or_conn)) { int status = bootstrap_percent; const char *tag = "", *summary = ""; @@ -6925,6 +6923,11 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp) goto end; } + /* Without a directory fingerprint at this stage, we can't do much. */ + if (hsdir_fp == NULL) { + goto end; + } + /* OK, we have an onion address so now let's find which descriptor ID * is the one associated with the HSDir fingerprint. */ for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; @@ -7014,10 +7017,9 @@ control_event_hs_descriptor_receive_end(const char *action, char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; const char *desc_id = NULL; - if (!action || !id_digest || !rend_data || !onion_address) { - log_warn(LD_BUG, "Called with action==%p, id_digest==%p, " - "rend_data==%p, onion_address==%p", action, id_digest, - rend_data, onion_address); + 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); return; } @@ -7040,7 +7042,8 @@ control_event_hs_descriptor_receive_end(const char *action, rend_hsaddress_str_or_unknown(onion_address), rend_auth_type_to_string( TO_REND_DATA_V2(rend_data)->auth_type), - node_describe_longname_by_id(id_digest), + id_digest ? + node_describe_longname_by_id(id_digest) : "UNKNOWN", desc_id_field ? desc_id_field : "", reason_field ? reason_field : ""); @@ -7120,19 +7123,18 @@ control_event_hs_descriptor_uploaded(const char *id_digest, id_digest, NULL); } -/** Send HS_DESC event to inform controller that query <b>rend_query</b> - * failed to retrieve hidden service descriptor identified by - * <b>id_digest</b>. If <b>reason</b> is not NULL, add it to REASON= - * field. +/** Send HS_DESC event to inform controller that query <b>rend_data</b> + * failed to retrieve hidden service descriptor from directory identified by + * <b>id_digest</b>. If NULL, "UNKNOWN" is used. If <b>reason</b> is not NULL, + * add it to REASON= field. */ void control_event_hs_descriptor_failed(const rend_data_t *rend_data, const char *id_digest, const char *reason) { - if (!rend_data || !id_digest) { - log_warn(LD_BUG, "Called with rend_data==%p, id_digest==%p", - rend_data, id_digest); + if (!rend_data) { + log_warn(LD_BUG, "Called with rend_data==%p", rend_data); return; } control_event_hs_descriptor_receive_end("FAILED", @@ -7140,8 +7142,11 @@ control_event_hs_descriptor_failed(const rend_data_t *rend_data, rend_data, id_digest, reason); } -/** send HS_DESC_CONTENT event after completion of a successful fetch from - * hs directory. */ +/** Send HS_DESC_CONTENT event after completion of a successful fetch from hs + * directory. If <b>hsdir_id_digest</b> is NULL, it is replaced by "UNKNOWN". + * If <b>content</b> is NULL, it is replaced by an empty string. The + * <b>onion_address</b> or <b>desc_id</b> set to NULL will no trigger the + * control event. */ void control_event_hs_descriptor_content(const char *onion_address, const char *desc_id, @@ -7151,9 +7156,9 @@ control_event_hs_descriptor_content(const char *onion_address, static const char *event_name = "HS_DESC_CONTENT"; char *esc_content = NULL; - if (!onion_address || !desc_id || !hsdir_id_digest) { - log_warn(LD_BUG, "Called with onion_address==%p, desc_id==%p, " - "hsdir_id_digest==%p", onion_address, desc_id, hsdir_id_digest); + if (!onion_address || !desc_id) { + log_warn(LD_BUG, "Called with onion_address==%p, desc_id==%p, ", + onion_address, desc_id); return; } @@ -7168,7 +7173,9 @@ control_event_hs_descriptor_content(const char *onion_address, event_name, rend_hsaddress_str_or_unknown(onion_address), desc_id, - node_describe_longname_by_id(hsdir_id_digest), + hsdir_id_digest ? + node_describe_longname_by_id(hsdir_id_digest) : + "UNKNOWN", esc_content); tor_free(esc_content); } diff --git a/src/or/control.h b/src/or/control.h index 6330c85571..41a194bfcb 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -262,6 +262,11 @@ STATIC crypto_pk_t *add_onion_helper_keyarg(const char *arg, int discard_pk, STATIC rend_authorized_client_t * add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out); +STATIC int getinfo_helper_onions( + control_connection_t *control_conn, + const char *question, + char **answer, + const char **errmsg); STATIC void getinfo_helper_downloads_networkstatus( const char *flavor, download_status_t **dl_to_emit, @@ -285,6 +290,10 @@ STATIC int getinfo_helper_downloads( control_connection_t *control_conn, const char *question, char **answer, const char **errmsg); +STATIC int getinfo_helper_dir( + control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg); #endif diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index fd6de6ea7c..f5fff2b331 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -11,8 +11,11 @@ * The multithreading backend for this module is in workqueue.c; this module * specializes workqueue.c. * - * Right now, we only use this for processing onionskins, and invoke it mostly - * from onion.c. + * Right now, we use this infrastructure + * <ul><li>for processing onionskins in onion.c + * <li>for compressing consensuses in consdiffmgr.c, + * <li>and for calculating diffs and compressing them in consdiffmgr.c. + * </ul> **/ #include "or.h" #include "channel.h" @@ -89,7 +92,14 @@ cpu_init(void) event_add(reply_event, NULL); } if (!threadpool) { - threadpool = threadpool_new(get_num_cpus(get_options()), + /* + In our threadpool implementation, half the threads are permissive and + half are strict (when it comes to running lower-priority tasks). So we + always make sure we have at least two threads, so that there will be at + least one thread of each kind. + */ + const int n_threads = get_num_cpus(get_options()) + 1; + threadpool = threadpool_new(n_threads, replyqueue, worker_state_new, worker_state_free, @@ -475,10 +485,26 @@ queue_pending_tasks(void) return; if (assign_onionskin_to_cpuworker(circ, onionskin)) - log_warn(LD_OR,"assign_to_cpuworker failed. Ignoring."); + log_info(LD_OR,"assign_to_cpuworker failed. Ignoring."); } } +/** DOCDOC */ +MOCK_IMPL(workqueue_entry_t *, +cpuworker_queue_work,(workqueue_priority_t priority, + workqueue_reply_t (*fn)(void *, void *), + void (*reply_fn)(void *), + void *arg)) +{ + tor_assert(threadpool); + + return threadpool_queue_work_priority(threadpool, + priority, + fn, + reply_fn, + arg); +} + /** Try to tell a cpuworker to perform the public key operations necessary to * respond to <b>onionskin</b> for the circuit <b>circ</b>. * @@ -531,7 +557,8 @@ assign_onionskin_to_cpuworker(or_circuit_t *circ, memwipe(&req, 0, sizeof(req)); ++total_pending_tasks; - queue_entry = threadpool_queue_work(threadpool, + queue_entry = threadpool_queue_work_priority(threadpool, + WQ_PRI_HIGH, cpuworker_onion_handshake_threadfn, cpuworker_onion_handshake_replyfn, job); diff --git a/src/or/cpuworker.h b/src/or/cpuworker.h index 62cf0eb164..320de9532f 100644 --- a/src/or/cpuworker.h +++ b/src/or/cpuworker.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,6 +14,14 @@ void cpu_init(void); void cpuworkers_rotate_keyinfo(void); +struct workqueue_entry_s; +enum workqueue_reply_t; +enum workqueue_priority_t; +MOCK_DECL(struct workqueue_entry_s *, cpuworker_queue_work, ( + enum workqueue_priority_t priority, + enum workqueue_reply_t (*fn)(void *, void *), + void (*reply_fn)(void *), + void *arg)); struct create_cell_t; int assign_onionskin_to_cpuworker(or_circuit_t *circ, diff --git a/src/or/dircollate.c b/src/or/dircollate.c index 033a7afe0f..172364c5f5 100644 --- a/src/or/dircollate.c +++ b/src/or/dircollate.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/dircollate.h b/src/or/dircollate.h index 358c730cbb..52214282b9 100644 --- a/src/or/dircollate.h +++ b/src/or/dircollate.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/directory.c b/src/or/directory.c index 48d912bd28..5ceea2fb32 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRECTORY_PRIVATE @@ -13,7 +13,11 @@ #include "config.h" #include "connection.h" #include "connection_edge.h" +#include "conscache.h" +#include "consdiff.h" +#include "consdiffmgr.h" #include "control.h" +#include "compat.h" #define DIRECTORY_PRIVATE #include "directory.h" #include "dirserv.h" @@ -62,7 +66,7 @@ * multi-hop circuits for anonymity. * * Directory requests are launched by calling - * directory_initiate_command_rend() or one of its numerous variants. This + * directory_initiate_request(). This * launch the connection, will construct an HTTP request with * directory_send_command(), send the and wait for a response. The client * later handles the response with connection_dir_client_reached_eof(), @@ -97,9 +101,8 @@ * connection_finished_connecting() in connection.c */ static void directory_send_command(dir_connection_t *conn, - int purpose, int direct, const char *resource, - const char *payload, size_t payload_len, - time_t if_modified_since); + int direct, + const directory_request_t *request); static int body_is_plausible(const char *body, size_t body_len, int purpose); static char *http_get_header(const char *headers, const char *which); static void http_set_address_origin(const char *headers, connection_t *conn); @@ -114,22 +117,10 @@ 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); -static int client_likes_consensus(networkstatus_t *v, const char *want_url); - -static void directory_initiate_command_rend( - const tor_addr_port_t *or_addr_port, - const tor_addr_port_t *dir_addr_port, - const char *digest, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - const rend_data_t *rend_query, - circuit_guard_state_t *guard_state); + int status_code, + const char *dir_id); +static int client_likes_consensus(const struct consensus_cache_entry_t *ent, + const char *want_url); static void connection_dir_close_consensus_fetches( dir_connection_t *except_this_one, const char *resource); @@ -141,6 +132,7 @@ static void connection_dir_close_consensus_fetches( #define ALLOW_DIRECTORY_TIME_SKEW (30*60) #define X_ADDRESS_HEADER "X-Your-Address-Is: " +#define X_OR_DIFF_FROM_CONSENSUS_HEADER "X-Or-Diff-From-Consensus: " /** HTTP cache control: how long do we tell proxies they can cache each * kind of document we serve? */ @@ -420,11 +412,14 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, } else { indirection = DIRIND_DIRECT_CONN; } - directory_initiate_command_routerstatus(rs, dir_purpose, - router_purpose, - indirection, - NULL, payload, upload_len, 0, - NULL); + + directory_request_t *req = directory_request_new(dir_purpose); + directory_request_set_routerstatus(req, rs); + directory_request_set_router_purpose(req, router_purpose); + directory_request_set_indirection(req, indirection); + directory_request_set_payload(req, payload, upload_len); + directory_initiate_request(req); + directory_request_free(req); } SMARTLIST_FOREACH_END(ds); if (!found) { char *s = authdir_type_to_string(type); @@ -469,7 +464,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 { @@ -485,13 +480,94 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, return rs; } +/** + * Set the extra fields in <b>req</b> that are used when requesting a + * consensus of type <b>resource</b>. + * + * Right now, these fields are if-modified-since and x-or-diff-from-consensus. + */ +static void +dir_consensus_request_set_additional_headers(directory_request_t *req, + const char *resource) +{ + time_t if_modified_since = 0; + uint8_t or_diff_from[DIGEST256_LEN]; + int or_diff_from_is_set = 0; + + /* DEFAULT_IF_MODIFIED_SINCE_DELAY is 1/20 of the default consensus + * period of 1 hour. + */ + const int DEFAULT_IF_MODIFIED_SINCE_DELAY = 180; + const int32_t DEFAULT_TRY_DIFF_FOR_CONSENSUS_NEWER = 72; + const int32_t MIN_TRY_DIFF_FOR_CONSENSUS_NEWER = 0; + const int32_t MAX_TRY_DIFF_FOR_CONSENSUS_NEWER = 8192; + const char TRY_DIFF_FOR_CONSENSUS_NEWER_NAME[] = + "try-diff-for-consensus-newer-than"; + + int flav = FLAV_NS; + if (resource) + flav = networkstatus_parse_flavor_name(resource); + + int32_t max_age_for_diff = 3600 * + networkstatus_get_param(NULL, + TRY_DIFF_FOR_CONSENSUS_NEWER_NAME, + DEFAULT_TRY_DIFF_FOR_CONSENSUS_NEWER, + MIN_TRY_DIFF_FOR_CONSENSUS_NEWER, + MAX_TRY_DIFF_FOR_CONSENSUS_NEWER); + + if (flav != -1) { + /* IF we have a parsed consensus of this type, we can do an + * if-modified-time based on it. */ + networkstatus_t *v; + v = networkstatus_get_latest_consensus_by_flavor(flav); + if (v) { + /* In networks with particularly short V3AuthVotingIntervals, + * ask for the consensus if it's been modified since half the + * V3AuthVotingInterval of the most recent consensus. */ + time_t ims_delay = DEFAULT_IF_MODIFIED_SINCE_DELAY; + if (v->fresh_until > v->valid_after + && ims_delay > (v->fresh_until - v->valid_after)/2) { + ims_delay = (v->fresh_until - v->valid_after)/2; + } + if_modified_since = v->valid_after + ims_delay; + if (v->valid_after >= approx_time() - max_age_for_diff) { + memcpy(or_diff_from, v->digest_sha3_as_signed, DIGEST256_LEN); + or_diff_from_is_set = 1; + } + } + } else { + /* Otherwise it might be a consensus we don't parse, but which we + * do cache. Look at the cached copy, perhaps. */ + cached_dir_t *cd = dirserv_get_consensus(resource); + /* We have no method of determining the voting interval from an + * unparsed consensus, so we use the default. */ + if (cd) { + if_modified_since = cd->published + DEFAULT_IF_MODIFIED_SINCE_DELAY; + if (cd->published >= approx_time() - max_age_for_diff) { + memcpy(or_diff_from, cd->digest_sha3_as_signed, DIGEST256_LEN); + or_diff_from_is_set = 1; + } + } + } + + if (if_modified_since > 0) + directory_request_set_if_modified_since(req, if_modified_since); + if (or_diff_from_is_set) { + char hex[HEX_DIGEST256_LEN + 1]; + base16_encode(hex, sizeof(hex), + (const char*)or_diff_from, sizeof(or_diff_from)); + directory_request_add_header(req, X_OR_DIFF_FROM_CONSENSUS_HEADER, hex); + } +} + /** Start a connection to a random running directory server, using * connection purpose <b>dir_purpose</b>, intending to fetch descriptors * of purpose <b>router_purpose</b>, and requesting <b>resource</b>. * Use <b>pds_flags</b> as arguments to router_pick_directory_server() * or router_pick_trusteddirserver(). */ -MOCK_IMPL(void, directory_get_from_dirserver, ( +MOCK_IMPL(void, +directory_get_from_dirserver,( uint8_t dir_purpose, uint8_t router_purpose, const char *resource, @@ -506,47 +582,10 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose, resource); dirinfo_type_t type = dir_fetch_type(dir_purpose, router_purpose, resource); - time_t if_modified_since = 0; if (type == NO_DIRINFO) return; - if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS) { - int flav = FLAV_NS; - networkstatus_t *v; - if (resource) - flav = networkstatus_parse_flavor_name(resource); - - /* DEFAULT_IF_MODIFIED_SINCE_DELAY is 1/20 of the default consensus - * period of 1 hour. - */ -#define DEFAULT_IF_MODIFIED_SINCE_DELAY (180) - if (flav != -1) { - /* IF we have a parsed consensus of this type, we can do an - * if-modified-time based on it. */ - v = networkstatus_get_latest_consensus_by_flavor(flav); - if (v) { - /* In networks with particularly short V3AuthVotingIntervals, - * ask for the consensus if it's been modified since half the - * V3AuthVotingInterval of the most recent consensus. */ - time_t ims_delay = DEFAULT_IF_MODIFIED_SINCE_DELAY; - if (v->fresh_until > v->valid_after - && ims_delay > (v->fresh_until - v->valid_after)/2) { - ims_delay = (v->fresh_until - v->valid_after)/2; - } - if_modified_since = v->valid_after + ims_delay; - } - } else { - /* Otherwise it might be a consensus we don't parse, but which we - * do cache. Look at the cached copy, perhaps. */ - cached_dir_t *cd = dirserv_get_consensus(resource); - /* We have no method of determining the voting interval from an - * unparsed consensus, so we use the default. */ - if (cd) - if_modified_since = cd->published + DEFAULT_IF_MODIFIED_SINCE_DELAY; - } - } - if (!options->FetchServerDescriptors) return; @@ -559,26 +598,26 @@ MOCK_IMPL(void, 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; /* clients always make OR connections to bridges */ tor_addr_port_t or_ap; - tor_addr_port_t nil_dir_ap; + directory_request_t *req = directory_request_new(dir_purpose); /* we are willing to use a non-preferred address if we need to */ fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, &or_ap); - tor_addr_make_null(&nil_dir_ap.addr, AF_INET); - nil_dir_ap.port = 0; - directory_initiate_command_rend(&or_ap, - &nil_dir_ap, - ri->cache_info.identity_digest, - dir_purpose, - router_purpose, - DIRIND_ONEHOP, - resource, NULL, 0, if_modified_since, - NULL, guard_state); + directory_request_set_or_addr_port(req, &or_ap); + directory_request_set_directory_id_digest(req, + ri->cache_info.identity_digest); + directory_request_set_router_purpose(req, router_purpose); + directory_request_set_resource(req, resource); + if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS) + dir_consensus_request_set_additional_headers(req, resource); + directory_request_set_guard_state(req, guard_state); + directory_initiate_request(req); + directory_request_free(req); } else { if (guard_state) { entry_guard_cancel(&guard_state); @@ -638,12 +677,17 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( if (rs) { const dir_indirection_t indirection = get_via_tor ? DIRIND_ANONYMOUS : DIRIND_ONEHOP; - directory_initiate_command_routerstatus(rs, dir_purpose, - router_purpose, - indirection, - resource, NULL, 0, - if_modified_since, - guard_state); + directory_request_t *req = directory_request_new(dir_purpose); + directory_request_set_routerstatus(req, rs); + directory_request_set_router_purpose(req, router_purpose); + directory_request_set_indirection(req, indirection); + directory_request_set_resource(req, resource); + if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS) + dir_consensus_request_set_additional_headers(req, resource); + if (guard_state) + directory_request_set_guard_state(req, guard_state); + directory_initiate_request(req); + directory_request_free(req); } else { log_notice(LD_DIR, "While fetching directory info, " @@ -669,15 +713,17 @@ directory_get_from_all_authorities(uint8_t dir_purpose, SMARTLIST_FOREACH_BEGIN(router_get_trusted_dir_servers(), dir_server_t *, ds) { - routerstatus_t *rs; if (router_digest_is_me(ds->digest)) continue; if (!(ds->type & V3_DIRINFO)) continue; - rs = &ds->fake_status; - directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, - DIRIND_ONEHOP, resource, NULL, - 0, 0, NULL); + const routerstatus_t *rs = &ds->fake_status; + directory_request_t *req = directory_request_new(dir_purpose); + directory_request_set_routerstatus(req, rs); + directory_request_set_router_purpose(req, router_purpose); + directory_request_set_resource(req, resource); + directory_initiate_request(req); + directory_request_free(req); } SMARTLIST_FOREACH_END(ds); } @@ -777,110 +823,6 @@ directory_choose_address_routerstatus(const routerstatus_t *status, return 0; } -/** Same as directory_initiate_command_routerstatus(), but accepts - * rendezvous data to fetch a hidden service descriptor. */ -void -directory_initiate_command_routerstatus_rend(const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - const rend_data_t *rend_query, - circuit_guard_state_t *guard_state) -{ - const or_options_t *options = get_options(); - const node_t *node; - tor_addr_port_t use_or_ap, use_dir_ap; - const int anonymized_connection = dirind_is_anon(indirection); - - tor_assert(status != NULL); - - node = node_get_by_id(status->identity_digest); - - /* XXX The below check is wrong: !node means it's not in the consensus, - * but we haven't checked if we have a descriptor for it -- and also, - * we only care about the descriptor if it's a begindir-style anonymized - * connection. */ - if (!node && anonymized_connection) { - log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we " - "don't have its router descriptor.", - routerstatus_describe(status)); - return; - } - - if (options->ExcludeNodes && options->StrictNodes && - routerset_contains_routerstatus(options->ExcludeNodes, status, -1)) { - log_warn(LD_DIR, "Wanted to contact directory mirror %s for %s, but " - "it's in our ExcludedNodes list and StrictNodes is set. " - "Skipping. This choice might make your Tor not work.", - routerstatus_describe(status), - dir_conn_purpose_to_string(dir_purpose)); - return; - } - - /* At this point, if we are a client making a direct connection to a - * directory server, we have selected a server that has at least one address - * allowed by ClientUseIPv4/6 and Reachable{"",OR,Dir}Addresses. This - * selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if - * possible. (If UseBridges is set, clients always use IPv6, and prefer it - * by default.) - * - * Now choose an address that we can use to connect to the directory server. - */ - if (directory_choose_address_routerstatus(status, indirection, &use_or_ap, - &use_dir_ap) < 0) { - return; - } - - /* We don't retry the alternate OR/Dir address for the same directory if - * the address we choose fails (#6772). - * Instead, we'll retry another directory on failure. */ - - directory_initiate_command_rend(&use_or_ap, &use_dir_ap, - status->identity_digest, - dir_purpose, router_purpose, - indirection, resource, - payload, payload_len, if_modified_since, - rend_query, - guard_state); -} - -/** Launch a new connection to the directory server <b>status</b> to - * upload or download a server or rendezvous - * descriptor. <b>dir_purpose</b> determines what - * kind of directory connection we're launching, and must be one of - * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC_V2}. <b>router_purpose</b> - * specifies the descriptor purposes we have in mind (currently only - * used for FETCH_DIR). - * - * When uploading, <b>payload</b> and <b>payload_len</b> determine the content - * of the HTTP post. Otherwise, <b>payload</b> should be NULL. - * - * When fetching a rendezvous descriptor, <b>resource</b> is the service ID we - * want to fetch. - */ -MOCK_IMPL(void, directory_initiate_command_routerstatus, - (const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - circuit_guard_state_t *guard_state)) -{ - directory_initiate_command_routerstatus_rend(status, dir_purpose, - router_purpose, - indirection, resource, - payload, payload_len, - if_modified_since, NULL, - guard_state); -} - /** Return true iff <b>conn</b> is the client side of a directory connection * we launched to ourself in order to determine the reachability of our * dir_port. */ @@ -1064,6 +1006,47 @@ 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 */ + const rend_data_t *rend_query; + /** Extra headers to append to the request */ + config_line_t *additional_headers; + /** */ + /** 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. @@ -1077,14 +1060,16 @@ directory_must_use_begindir(const or_options_t *options) */ static int directory_command_should_use_begindir(const or_options_t *options, - const tor_addr_t *or_addr, int or_port, - const tor_addr_t *dir_addr, int dir_port, - uint8_t router_purpose, - dir_indirection_t indirection, + const directory_request_t *req, const char **reason) { - (void) router_purpose; - (void) dir_addr; + const tor_addr_t *or_addr = &req->or_addr_port.addr; + //const tor_addr_t *dir_addr = &req->dir_addr_port.addr; + const int or_port = req->or_addr_port.port; + const int dir_port = req->dir_addr_port.port; + + const dir_indirection_t indirection = req->indirection; + tor_assert(reason); *reason = NULL; @@ -1124,70 +1109,290 @@ directory_command_should_use_begindir(const or_options_t *options, return 1; } -/** Helper for directory_initiate_command_rend: send the - * command to a server whose OR address/port is <b>or_addr</b>/<b>or_port</b>, - * whose directory address/port is <b>dir_addr</b>/<b>dir_port</b>, whose - * identity key digest is <b>digest</b>, with purposes <b>dir_purpose</b> and - * <b>router_purpose</b>, making an (in)direct connection as specified in - * <b>indirection</b>, with command <b>resource</b>, <b>payload</b> of - * <b>payload_len</b>, and asking for a result only <b>if_modified_since</b>. - * If <b>guard_state</b> is set, assign it to the directory circuit. +/** + * Create and return a new directory_request_t with purpose + * <b>dir_purpose</b>. + */ +directory_request_t * +directory_request_new(uint8_t dir_purpose) +{ + tor_assert(dir_purpose >= DIR_PURPOSE_MIN_); + tor_assert(dir_purpose <= DIR_PURPOSE_MAX_); + tor_assert(dir_purpose != DIR_PURPOSE_SERVER); + tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2); + + directory_request_t *result = tor_malloc_zero(sizeof(*result)); + tor_addr_make_null(&result->or_addr_port.addr, AF_INET); + result->or_addr_port.port = 0; + tor_addr_make_null(&result->dir_addr_port.addr, AF_INET); + result->dir_addr_port.port = 0; + result->dir_purpose = dir_purpose; + result->router_purpose = ROUTER_PURPOSE_GENERAL; + result->indirection = DIRIND_ONEHOP; + return result; +} +/** + * Release all resources held by <b>req</b>. + */ +void +directory_request_free(directory_request_t *req) +{ + if (req == NULL) + return; + config_free_lines(req->additional_headers); + tor_free(req); +} +/** + * Set the address and OR port to use for this directory request. If there is + * no OR port, we'll have to connect over the dirport. (If there are both, + * the indirection setting determins which to use.) + */ +void +directory_request_set_or_addr_port(directory_request_t *req, + const tor_addr_port_t *p) +{ + memcpy(&req->or_addr_port, p, sizeof(*p)); +} +/** + * Set the address and dirport to use for this directory request. If there + * is no dirport, we'll have to connect over the OR port. (If there are both, + * the indirection setting determins which to use.) + */ +void +directory_request_set_dir_addr_port(directory_request_t *req, + const tor_addr_port_t *p) +{ + memcpy(&req->dir_addr_port, p, sizeof(*p)); +} +/** + * Set the RSA identity digest of the directory to use for this directory + * request. + */ +void +directory_request_set_directory_id_digest(directory_request_t *req, + const char *digest) +{ + memcpy(req->digest, digest, DIGEST_LEN); +} +/** + * Set the router purpose associated with uploaded and downloaded router + * descriptors and extrainfo documents in this directory request. The purpose + * must be one of ROUTER_PURPOSE_GENERAL (the default) or + * ROUTER_PURPOSE_BRIDGE. + */ +void +directory_request_set_router_purpose(directory_request_t *req, + uint8_t router_purpose) +{ + tor_assert(router_purpose == ROUTER_PURPOSE_GENERAL || + router_purpose == ROUTER_PURPOSE_BRIDGE); + // assert that it actually makes sense to set this purpose, given + // the dir_purpose. + req->router_purpose = router_purpose; +} +/** + * Set the indirection to be used for the directory request. The indirection + * parameter configures whether to connect to a DirPort or ORPort, and whether + * to anonymize the connection. DIRIND_ONEHOP (use ORPort, don't anonymize) + * is the default. See dir_indirection_t for more information. + */ +void +directory_request_set_indirection(directory_request_t *req, + dir_indirection_t indirection) +{ + req->indirection = indirection; +} + +/** + * Set a pointer to the resource to request from a directory. Different + * request types use resources to indicate different components of their URL. + * Note that only an alias to <b>resource</b> is stored, so the + * <b>resource</b> must outlive the request. */ void -directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, - const tor_addr_t *dir_addr, uint16_t dir_port, - const char *digest, - uint8_t dir_purpose, uint8_t router_purpose, - dir_indirection_t indirection, const char *resource, - const char *payload, size_t payload_len, - time_t if_modified_since, - circuit_guard_state_t *guard_state) +directory_request_set_resource(directory_request_t *req, + const char *resource) { - tor_addr_port_t or_ap, dir_ap; + req->resource = resource; +} +/** + * Set a pointer to the payload to include with this directory request, along + * with its length. Note that only an alias to <b>payload</b> is stored, so + * the <b>payload</b> must outlive the request. + */ +void +directory_request_set_payload(directory_request_t *req, + const char *payload, + size_t payload_len) +{ + tor_assert(DIR_PURPOSE_IS_UPLOAD(req->dir_purpose)); - /* Use the null tor_addr and 0 port if the address or port isn't valid. */ - if (tor_addr_port_is_valid(or_addr, or_port, 0)) { - tor_addr_copy(&or_ap.addr, or_addr); - or_ap.port = or_port; - } else { - /* the family doesn't matter here, so make it IPv4 */ - tor_addr_make_null(&or_ap.addr, AF_INET); - or_ap.port = or_port = 0; + req->payload = payload; + req->payload_len = payload_len; +} +/** + * Set an if-modified-since date to send along with the request. The + * default is 0 (meaning, send no if-modified-since header). + */ +void +directory_request_set_if_modified_since(directory_request_t *req, + time_t if_modified_since) +{ + req->if_modified_since = if_modified_since; +} + +/** Include a header of name <b>key</b> with content <b>val</b> in the + * request. Neither may include newlines or other odd characters. Their + * ordering is not currently guaranteed. + * + * Note that, as elsewhere in this module, header keys include a trailing + * colon and space. + */ +void +directory_request_add_header(directory_request_t *req, + const char *key, + const char *val) +{ + config_line_prepend(&req->additional_headers, key, val); +} +/** + * Set an object containing HS data to be associated with this request. Note + * that only an alias to <b>query</b> is stored, so the <b>query</b> object + * must outlive the request. + */ +void +directory_request_set_rend_query(directory_request_t *req, + const rend_data_t *query) +{ + if (query) { + tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 || + req->dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2); } + req->rend_query = query; +} +/** Set a static circuit_guard_state_t object to affliate with the request in + * <b>req</b>. This object will receive notification when the attempt to + * connect to the guard either succeeds or fails. */ +void +directory_request_set_guard_state(directory_request_t *req, + circuit_guard_state_t *state) +{ + req->guard_state = state; +} - if (tor_addr_port_is_valid(dir_addr, dir_port, 0)) { - tor_addr_copy(&dir_ap.addr, dir_addr); - dir_ap.port = dir_port; - } else { - /* the family doesn't matter here, so make it IPv4 */ - tor_addr_make_null(&dir_ap.addr, AF_INET); - dir_ap.port = dir_port = 0; +/** + * Internal: Return true if any information for contacting the directory in + * <b>req</b> has been set, other than by the routerstatus. */ +static int +directory_request_dir_contact_info_specified(const directory_request_t *req) +{ + /* We only check for ports here, since we don't use an addr unless the port + * is set */ + return (req->or_addr_port.port || + req->dir_addr_port.port || + ! tor_digest_is_zero(req->digest)); +} + +/** + * Set the routerstatus to use for the directory associated with this + * request. If this option is set, then no other function to set the + * directory's address or identity should be called. + */ +void +directory_request_set_routerstatus(directory_request_t *req, + const routerstatus_t *status) +{ + req->routerstatus = status; +} +/** + * Helper: update the addresses, ports, and identities in <b>req</b> + * from the routerstatus object in <b>req</b>. Return 0 on success. + * On failure, warn and return -1. + */ +static int +directory_request_set_dir_from_routerstatus(directory_request_t *req) + +{ + const routerstatus_t *status = req->routerstatus; + if (BUG(status == NULL)) + return -1; + const or_options_t *options = get_options(); + const node_t *node; + tor_addr_port_t use_or_ap, use_dir_ap; + const int anonymized_connection = dirind_is_anon(req->indirection); + + tor_assert(status != NULL); + + node = node_get_by_id(status->identity_digest); + + /* XXX The below check is wrong: !node means it's not in the consensus, + * but we haven't checked if we have a descriptor for it -- and also, + * we only care about the descriptor if it's a begindir-style anonymized + * connection. */ + if (!node && anonymized_connection) { + log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we " + "don't have its router descriptor.", + routerstatus_describe(status)); + return -1; + } + + if (options->ExcludeNodes && options->StrictNodes && + routerset_contains_routerstatus(options->ExcludeNodes, status, -1)) { + log_warn(LD_DIR, "Wanted to contact directory mirror %s for %s, but " + "it's in our ExcludedNodes list and StrictNodes is set. " + "Skipping. This choice might make your Tor not work.", + routerstatus_describe(status), + dir_conn_purpose_to_string(req->dir_purpose)); + return -1; } - directory_initiate_command_rend(&or_ap, &dir_ap, - digest, dir_purpose, - router_purpose, indirection, - resource, payload, payload_len, - if_modified_since, NULL, guard_state); + /* At this point, if we are a client making a direct connection to a + * directory server, we have selected a server that has at least one address + * allowed by ClientUseIPv4/6 and Reachable{"",OR,Dir}Addresses. This + * selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if + * possible. (If UseBridges is set, clients always use IPv6, and prefer it + * by default.) + * + * Now choose an address that we can use to connect to the directory server. + */ + if (directory_choose_address_routerstatus(status, + req->indirection, &use_or_ap, + &use_dir_ap) < 0) { + return -1; + } + + directory_request_set_or_addr_port(req, &use_or_ap); + directory_request_set_dir_addr_port(req, &use_dir_ap); + directory_request_set_directory_id_digest(req, status->identity_digest); + return 0; } -/** Same as directory_initiate_command(), but accepts rendezvous data to - * fetch a hidden service descriptor, and takes its address & port arguments - * as tor_addr_port_t. */ -static void -directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, - const tor_addr_port_t *dir_addr_port, - const char *digest, - uint8_t dir_purpose, uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, size_t payload_len, - time_t if_modified_since, - const rend_data_t *rend_query, - circuit_guard_state_t *guard_state) +/** + * Launch the provided directory request, configured in <b>request</b>. + * After this function is called, you can free <b>request</b>. + */ +MOCK_IMPL(void, +directory_initiate_request,(directory_request_t *request)) { - tor_assert(or_addr_port); - tor_assert(dir_addr_port); + tor_assert(request); + if (request->routerstatus) { + tor_assert_nonfatal( + ! directory_request_dir_contact_info_specified(request)); + if (directory_request_set_dir_from_routerstatus(request) < 0) { + return; + } + } + + const tor_addr_port_t *or_addr_port = &request->or_addr_port; + const tor_addr_port_t *dir_addr_port = &request->dir_addr_port; + const char *digest = request->digest; + const uint8_t dir_purpose = request->dir_purpose; + const uint8_t router_purpose = request->router_purpose; + const dir_indirection_t indirection = request->indirection; + const char *resource = request->resource; + const rend_data_t *rend_query = request->rend_query; + circuit_guard_state_t *guard_state = request->guard_state; + tor_assert(or_addr_port->port || dir_addr_port->port); tor_assert(digest); @@ -1197,11 +1402,9 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, const char *begindir_reason = NULL; /* Should the connection be to a relay's OR port (and inside that we will * send our directory request)? */ - const int use_begindir = directory_command_should_use_begindir(options, - &or_addr_port->addr, or_addr_port->port, - &dir_addr_port->addr, dir_addr_port->port, - router_purpose, indirection, - &begindir_reason); + const int use_begindir = + directory_command_should_use_begindir(options, request, &begindir_reason); + /* Will the connection go via a three-hop Tor circuit? Note that this * is separate from whether it will use_begindir. */ const int anonymized_connection = dirind_is_anon(indirection); @@ -1302,9 +1505,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, /* fall through */ case 0: /* queue the command on the outbuf */ - directory_send_command(conn, dir_purpose, 1, resource, - payload, payload_len, - if_modified_since); + directory_send_command(conn, 1, request); connection_watch_events(TO_CONN(conn), READ_EVENT | WRITE_EVENT); /* writable indicates finish, readable indicates broken link, error indicates broken link in windowsland. */ @@ -1358,9 +1559,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, } conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* queue the command on the outbuf */ - directory_send_command(conn, dir_purpose, 0, resource, - payload, payload_len, - if_modified_since); + directory_send_command(conn, 0, request); connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); connection_start_reading(ENTRY_TO_CONN(linked_conn)); @@ -1370,7 +1569,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, /** Return true iff anything we say on <b>conn</b> is being encrypted before * we send it to the client/server. */ int -connection_dir_is_encrypted(dir_connection_t *conn) +connection_dir_is_encrypted(const dir_connection_t *conn) { /* Right now it's sufficient to see if conn is or has been linked, since * the only thing it could be linked to is an edge connection on a @@ -1463,15 +1662,23 @@ copy_ipv6_address(char* destination, const char* source, size_t len, } } -/** Queue an appropriate HTTP command on conn-\>outbuf. The other args - * are as in directory_initiate_command(). +/** Queue an appropriate HTTP command for <b>request</b> on + * <b>conn</b>-\>outbuf. If <b>direct</b> is true, we're making a + * non-anonymized connection to the dirport. */ static void directory_send_command(dir_connection_t *conn, - int purpose, int direct, const char *resource, - const char *payload, size_t payload_len, - time_t if_modified_since) + const int direct, + const directory_request_t *req) { + tor_assert(req); + const int purpose = req->dir_purpose; + const char *resource = req->resource; + const char *payload = req->payload; + const size_t payload_len = req->payload_len; + const time_t if_modified_since = req->if_modified_since; + const int anonymized_connection = dirind_is_anon(req->indirection); + char proxystring[256]; char hoststring[128]; /* NEEDS to be the same size hoststring. @@ -1479,7 +1686,10 @@ directory_send_command(dir_connection_t *conn, char decorated_address[128]; smartlist_t *headers = smartlist_new(); char *url; + char *accept_encoding; + size_t url_len; char request[8192]; + size_t request_len, total_request_len = 0; const char *httpcommand = NULL; tor_assert(conn); @@ -1533,6 +1743,22 @@ directory_send_command(dir_connection_t *conn, proxystring[0] = 0; } + if (! anonymized_connection) { + /* Add Accept-Encoding. */ + accept_encoding = accept_encoding_header(); + smartlist_add_asprintf(headers, "Accept-Encoding: %s\r\n", + accept_encoding); + tor_free(accept_encoding); + } + + /* Add additional headers, if any */ + { + config_line_t *h; + for (h = req->additional_headers; h; h = h->next) { + smartlist_add_asprintf(headers, "%s%s\r\n", h->key, h->value); + } + } + switch (purpose) { case DIR_PURPOSE_FETCH_CONSENSUS: /* resource is optional. If present, it's a flavor name */ @@ -1625,8 +1851,14 @@ directory_send_command(dir_connection_t *conn, } tor_snprintf(request, sizeof(request), "%s %s", httpcommand, proxystring); - connection_write_to_buf(request, strlen(request), TO_CONN(conn)); - connection_write_to_buf(url, strlen(url), TO_CONN(conn)); + + request_len = strlen(request); + total_request_len += request_len; + connection_write_to_buf(request, request_len, TO_CONN(conn)); + + url_len = strlen(url); + total_request_len += url_len; + connection_write_to_buf(url, url_len, TO_CONN(conn)); tor_free(url); if (!strcmp(httpcommand, "POST") || payload) { @@ -1641,15 +1873,27 @@ directory_send_command(dir_connection_t *conn, tor_free(header); } - connection_write_to_buf(request, strlen(request), TO_CONN(conn)); + request_len = strlen(request); + total_request_len += request_len; + connection_write_to_buf(request, request_len, TO_CONN(conn)); if (payload) { /* then send the payload afterwards too */ connection_write_to_buf(payload, payload_len, TO_CONN(conn)); + total_request_len += payload_len; } SMARTLIST_FOREACH(headers, char *, h, tor_free(h)); smartlist_free(headers); + + log_debug(LD_DIR, + "Sent request to directory server '%s:%d': " + "(purpose: %d, request size: " U64_FORMAT ", " + "payload size: " U64_FORMAT ")", + conn->base_.address, conn->base_.port, + conn->base_.purpose, + U64_PRINTF_ARG(total_request_len), + U64_PRINTF_ARG(payload ? payload_len : 0)); } /** Parse an HTTP request string <b>headers</b> of the form @@ -1834,16 +2078,15 @@ parse_http_response(const char *headers, int *code, time_t *date, if (!strcmpstart(s, "Content-Encoding: ")) { enc = s+18; break; }); - if (!enc || !strcmp(enc, "identity")) { + + if (enc == NULL) *compression = NO_METHOD; - } else if (!strcmp(enc, "deflate") || !strcmp(enc, "x-deflate")) { - *compression = ZLIB_METHOD; - } else if (!strcmp(enc, "gzip") || !strcmp(enc, "x-gzip")) { - *compression = GZIP_METHOD; - } else { - log_info(LD_HTTP, "Unrecognized content encoding: %s. Trying to deal.", - escaped(enc)); - *compression = UNKNOWN_METHOD; + else { + *compression = compression_method_get_by_name(enc); + + if (*compression == UNKNOWN_METHOD) + log_info(LD_HTTP, "Unrecognized content encoding: %s. Trying to deal.", + escaped(enc)); } } SMARTLIST_FOREACH(parsed_headers, char *, s, tor_free(s)); @@ -1916,6 +2159,154 @@ load_downloaded_routers(const char *body, smartlist_t *which, return added; } +/** A structure to hold arguments passed into each directory response + * handler */ +typedef struct response_handler_args_t { + int status_code; + const char *reason; + const char *body; + size_t body_len; + const char *headers; +} response_handler_args_t; + +static int handle_response_fetch_consensus(dir_connection_t *, + const response_handler_args_t *); +static int handle_response_fetch_certificate(dir_connection_t *, + const response_handler_args_t *); +static int handle_response_fetch_status_vote(dir_connection_t *, + const response_handler_args_t *); +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_upload_dir(dir_connection_t *, + const response_handler_args_t *); +static int handle_response_upload_vote(dir_connection_t *, + const response_handler_args_t *); +static int handle_response_upload_signatures(dir_connection_t *, + const response_handler_args_t *); +static int handle_response_fetch_renddesc_v2(dir_connection_t *, + const response_handler_args_t *); +static int handle_response_upload_renddesc_v2(dir_connection_t *, + const response_handler_args_t *); + +static int +dir_client_decompress_response_body(char **bodyp, size_t *bodylenp, + dir_connection_t *conn, + compress_method_t compression, + int anonymized_connection) +{ + int rv = 0; + const char *body = *bodyp; + size_t body_len = *bodylenp; + int allow_partial = (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || + conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || + conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC); + + int plausible = body_is_plausible(body, body_len, conn->base_.purpose); + + if (plausible && compression == NO_METHOD) { + return 0; + } + + int severity = LOG_DEBUG; + char *new_body = NULL; + size_t new_len = 0; + const char *description1, *description2; + int want_to_try_both = 0; + int tried_both = 0; + compress_method_t guessed = detect_compression_method(body, body_len); + + description1 = compression_method_get_human_name(compression); + + if (BUG(description1 == NULL)) + description1 = compression_method_get_human_name(UNKNOWN_METHOD); + + if (guessed == UNKNOWN_METHOD && !plausible) + description2 = "confusing binary junk"; + else + description2 = compression_method_get_human_name(guessed); + + /* Tell the user if we don't believe what we're told about compression.*/ + want_to_try_both = (compression == UNKNOWN_METHOD || + guessed != compression); + if (want_to_try_both) { + severity = LOG_PROTOCOL_WARN; + } + + tor_log(severity, LD_HTTP, + "HTTP body from server '%s:%d' was labeled as %s, " + "%s it seems to be %s.%s", + conn->base_.address, conn->base_.port, description1, + guessed != compression?"but":"and", + description2, + (compression>0 && guessed>0 && want_to_try_both)? + " Trying both.":""); + + /* Try declared compression first if we can. + * tor_compress_supports_method() also returns true for NO_METHOD. + * Ensure that the server is not sending us data compressed using a + * compression method that is not allowed for anonymous connections. */ + if (anonymized_connection && + ! allowed_anonymous_connection_compression_method(compression)) { + warn_disallowed_anonymous_compression_method(compression); + rv = -1; + goto done; + } + + if (tor_compress_supports_method(compression)) { + tor_uncompress(&new_body, &new_len, body, body_len, compression, + !allow_partial, LOG_PROTOCOL_WARN); + if (new_body) { + /* We succeeded with the declared compression method. Great! */ + rv = 0; + goto done; + } + } + + /* Okay, if that didn't work, and we think that it was compressed + * differently, try that. */ + if (anonymized_connection && + ! allowed_anonymous_connection_compression_method(guessed)) { + warn_disallowed_anonymous_compression_method(guessed); + rv = -1; + goto done; + } + + if (tor_compress_supports_method(guessed) && + compression != guessed) { + tor_uncompress(&new_body, &new_len, body, body_len, guessed, + !allow_partial, LOG_INFO); + tried_both = 1; + } + /* If we're pretty sure that we have a compressed directory, and + * we didn't manage to uncompress it, then warn and bail. */ + if (!plausible && !new_body) { + log_fn(LOG_PROTOCOL_WARN, LD_HTTP, + "Unable to decompress HTTP body (tried %s%s%s, server '%s:%d').", + description1, + tried_both?" and ":"", + tried_both?description2:"", + conn->base_.address, conn->base_.port); + rv = -1; + goto done; + } + + done: + if (new_body) { + if (rv == 0) { + /* success! */ + tor_free(*bodyp); + *bodyp = new_body; + *bodylenp = new_len; + } else { + tor_free(new_body); + } + } + + return rv; +} + /** We are a client, and we've finished reading the server's * response. Parse it and act appropriately. * @@ -1928,21 +2319,26 @@ load_downloaded_routers(const char *body, smartlist_t *which, static int connection_dir_client_reached_eof(dir_connection_t *conn) { - char *body; - char *headers; + char *body = NULL; + char *headers = NULL; char *reason = NULL; size_t body_len = 0; int status_code; time_t date_header = 0; long apparent_skew; compress_method_t compression; - int plausible; int skewed = 0; + int rv; int allow_partial = (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC); - time_t now = time(NULL); - int src_code; + size_t received_bytes; + const int anonymized_connection = + purpose_needs_anonymity(conn->base_.purpose, + conn->router_purpose, + conn->requested_resource); + + received_bytes = connection_get_inbuf_len(TO_CONN(conn)); switch (connection_fetch_from_buf_http(TO_CONN(conn), &headers, MAX_HEADERS_SIZE, @@ -1964,17 +2360,26 @@ connection_dir_client_reached_eof(dir_connection_t *conn) &compression, &reason) < 0) { log_warn(LD_HTTP,"Unparseable headers (server '%s:%d'). Closing.", conn->base_.address, conn->base_.port); - tor_free(body); tor_free(headers); - return -1; + + rv = -1; + goto done; } if (!reason) reason = tor_strdup("[no reason given]"); - log_debug(LD_DIR, + tor_log(LOG_DEBUG, LD_DIR, "Received response from directory server '%s:%d': %d %s " - "(purpose: %d)", + "(purpose: %d, response size: " U64_FORMAT +#ifdef MEASUREMENTS_21206 + ", data cells received: %d, data cells sent: %d" +#endif + ", compression: %d)", conn->base_.address, conn->base_.port, status_code, - escaped(reason), - conn->base_.purpose); + escaped(reason), conn->base_.purpose, + U64_PRINTF_ARG(received_bytes), +#ifdef MEASUREMENTS_21206 + conn->data_cells_received, conn->data_cells_sent, +#endif + compression); if (conn->guard_state) { /* we count the connection as successful once we can read from it. We do @@ -2025,539 +2430,754 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "'%s:%d'. I'll try again soon.", status_code, escaped(reason), conn->base_.address, conn->base_.port); + time_t now = approx_time(); if ((rs = router_get_mutable_consensus_status_by_id(id_digest))) rs->last_dir_503_at = now; if ((ds = router_get_fallback_dirserver_by_digest(id_digest))) ds->fake_status.last_dir_503_at = now; - tor_free(body); tor_free(headers); tor_free(reason); - return -1; + rv = -1; + goto done; } - plausible = body_is_plausible(body, body_len, conn->base_.purpose); - if (compression != NO_METHOD || !plausible) { - char *new_body = NULL; - size_t new_len = 0; - compress_method_t guessed = detect_compression_method(body, body_len); - if (compression == UNKNOWN_METHOD || guessed != compression) { - /* Tell the user if we don't believe what we're told about compression.*/ - const char *description1, *description2; - if (compression == ZLIB_METHOD) - description1 = "as deflated"; - else if (compression == GZIP_METHOD) - description1 = "as gzipped"; - else if (compression == NO_METHOD) - description1 = "as uncompressed"; - else - description1 = "with an unknown Content-Encoding"; - if (guessed == ZLIB_METHOD) - description2 = "deflated"; - else if (guessed == GZIP_METHOD) - description2 = "gzipped"; - else if (!plausible) - description2 = "confusing binary junk"; - else - description2 = "uncompressed"; + if (dir_client_decompress_response_body(&body, &body_len, + conn, compression, anonymized_connection) < 0) { + rv = -1; + goto done; + } - log_info(LD_HTTP, "HTTP body from server '%s:%d' was labeled %s, " - "but it seems to be %s.%s", - conn->base_.address, conn->base_.port, description1, - description2, - (compression>0 && guessed>0)?" Trying both.":""); - } - /* Try declared compression first if we can. */ - if (compression == GZIP_METHOD || compression == ZLIB_METHOD) - tor_gzip_uncompress(&new_body, &new_len, body, body_len, compression, - !allow_partial, LOG_PROTOCOL_WARN); - /* Okay, if that didn't work, and we think that it was compressed - * differently, try that. */ - if (!new_body && - (guessed == GZIP_METHOD || guessed == ZLIB_METHOD) && - compression != guessed) - tor_gzip_uncompress(&new_body, &new_len, body, body_len, guessed, - !allow_partial, LOG_PROTOCOL_WARN); - /* If we're pretty sure that we have a compressed directory, and - * we didn't manage to uncompress it, then warn and bail. */ - if (!plausible && !new_body) { - log_fn(LOG_PROTOCOL_WARN, LD_HTTP, - "Unable to decompress HTTP body (server '%s:%d').", - conn->base_.address, conn->base_.port); - tor_free(body); tor_free(headers); tor_free(reason); - return -1; - } - if (new_body) { - tor_free(body); - body = new_body; - body_len = new_len; - } + response_handler_args_t args; + memset(&args, 0, sizeof(args)); + args.status_code = status_code; + args.reason = reason; + args.body = body; + args.body_len = body_len; + args.headers = headers; + + switch (conn->base_.purpose) { + case DIR_PURPOSE_FETCH_CONSENSUS: + rv = handle_response_fetch_consensus(conn, &args); + break; + case DIR_PURPOSE_FETCH_CERTIFICATE: + rv = handle_response_fetch_certificate(conn, &args); + break; + case DIR_PURPOSE_FETCH_STATUS_VOTE: + rv = handle_response_fetch_status_vote(conn, &args); + break; + case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES: + rv = handle_response_fetch_detached_signatures(conn, &args); + break; + case DIR_PURPOSE_FETCH_SERVERDESC: + case DIR_PURPOSE_FETCH_EXTRAINFO: + rv = handle_response_fetch_desc(conn, &args); + break; + case DIR_PURPOSE_FETCH_MICRODESC: + rv = handle_response_fetch_microdesc(conn, &args); + break; + case DIR_PURPOSE_FETCH_RENDDESC_V2: + rv = handle_response_fetch_renddesc_v2(conn, &args); + break; + case DIR_PURPOSE_UPLOAD_DIR: + rv = handle_response_upload_dir(conn, &args); + break; + case DIR_PURPOSE_UPLOAD_SIGNATURES: + rv = handle_response_upload_signatures(conn, &args); + break; + case DIR_PURPOSE_UPLOAD_VOTE: + rv = handle_response_upload_vote(conn, &args); + break; + case DIR_PURPOSE_UPLOAD_RENDDESC_V2: + rv = handle_response_upload_renddesc_v2(conn, &args); + break; + default: + tor_assert_nonfatal_unreached(); + rv = -1; + break; } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { - int r; - const char *flavname = conn->requested_resource; - if (status_code != 200) { - int severity = (status_code == 304) ? LOG_INFO : LOG_WARN; - tor_log(severity, LD_DIR, - "Received http status code %d (%s) from server " - "'%s:%d' while fetching consensus directory.", - status_code, escaped(reason), conn->base_.address, - conn->base_.port); - tor_free(body); tor_free(headers); tor_free(reason); - networkstatus_consensus_download_failed(status_code, flavname); + done: + tor_free(body); + tor_free(headers); + tor_free(reason); + return rv; +} + +/** + * Handler function: processes a response to a request for a networkstatus + * consensus document by checking the consensus, storing it, and marking + * router requests as reachable. + **/ +static int +handle_response_fetch_consensus(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_CONSENSUS); + const int status_code = args->status_code; + const char *body = args->body; + const size_t body_len = args->body_len; + const char *reason = args->reason; + const time_t now = approx_time(); + + const char *consensus; + char *new_consensus = NULL; + const char *sourcename; + + int r; + const char *flavname = conn->requested_resource; + if (status_code != 200) { + int severity = (status_code == 304) ? LOG_INFO : LOG_WARN; + tor_log(severity, LD_DIR, + "Received http status code %d (%s) from server " + "'%s:%d' while fetching consensus directory.", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + networkstatus_consensus_download_failed(status_code, flavname); + return -1; + } + + if (looks_like_a_consensus_diff(body, body_len)) { + /* First find our previous consensus. Maybe it's in ram, maybe not. */ + cached_dir_t *cd = dirserv_get_consensus(flavname); + const char *consensus_body; + char *owned_consensus = NULL; + if (cd) { + consensus_body = cd->dir; + } else { + owned_consensus = networkstatus_read_cached_consensus(flavname); + consensus_body = owned_consensus; + } + if (!consensus_body) { + log_warn(LD_DIR, "Received a consensus diff, but we can't find " + "any %s-flavored consensus in our current cache.",flavname); + networkstatus_consensus_download_failed(0, flavname); + // XXXX if this happens too much, see below return -1; } - log_info(LD_DIR,"Received consensus directory (size %d) from server " - "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); - if ((r=networkstatus_set_current_consensus(body, flavname, 0, - conn->identity_digest))<0) { - log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, - "Unable to load %s consensus directory downloaded from " - "server '%s:%d'. I'll try again soon.", - flavname, conn->base_.address, conn->base_.port); - tor_free(body); tor_free(headers); tor_free(reason); + + new_consensus = consensus_diff_apply(consensus_body, body); + tor_free(owned_consensus); + if (new_consensus == NULL) { + log_warn(LD_DIR, "Could not apply consensus diff received from server " + "'%s:%d'", conn->base_.address, conn->base_.port); + // XXXX If this happens too many times, we should maybe not use + // XXXX this directory for diffs any more? networkstatus_consensus_download_failed(0, flavname); return -1; } + log_info(LD_DIR, "Applied consensus diff (size %d) from server " + "'%s:%d', resulting in a new consensus document (size %d).", + (int)body_len, conn->base_.address, conn->base_.port, + (int)strlen(new_consensus)); + consensus = new_consensus; + sourcename = "generated based on a diff"; + } else { + log_info(LD_DIR,"Received consensus directory (body size %d) from server " + "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); + consensus = body; + sourcename = "downloaded"; + } + + if ((r=networkstatus_set_current_consensus(consensus, flavname, 0, + conn->identity_digest))<0) { + log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, + "Unable to load %s consensus directory %s from " + "server '%s:%d'. I'll try again soon.", + flavname, sourcename, conn->base_.address, conn->base_.port); + networkstatus_consensus_download_failed(0, flavname); + tor_free(new_consensus); + return -1; + } - /* If we launched other fetches for this consensus, cancel them. */ - connection_dir_close_consensus_fetches(conn, flavname); + /* If we launched other fetches for this consensus, cancel them. */ + connection_dir_close_consensus_fetches(conn, flavname); - /* update the list of routers and directory guards */ - routers_update_all_from_networkstatus(now, 3); - update_microdescs_from_networkstatus(now); - directory_info_has_arrived(now, 0, 0); - if (authdir_mode_v3(get_options())) { - sr_act_post_consensus( - networkstatus_get_latest_consensus_by_flavor(FLAV_NS)); - } - log_info(LD_DIR, "Successfully loaded consensus."); + /* update the list of routers and directory guards */ + routers_update_all_from_networkstatus(now, 3); + update_microdescs_from_networkstatus(now); + directory_info_has_arrived(now, 0, 0); + + if (authdir_mode_v3(get_options())) { + sr_act_post_consensus( + networkstatus_get_latest_consensus_by_flavor(FLAV_NS)); } + log_info(LD_DIR, "Successfully loaded consensus."); - if (conn->base_.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) { - if (status_code != 200) { - log_warn(LD_DIR, - "Received http status code %d (%s) from server " - "'%s:%d' while fetching \"/tor/keys/%s\".", - status_code, escaped(reason), conn->base_.address, - conn->base_.port, conn->requested_resource); - connection_dir_download_cert_failed(conn, status_code); - tor_free(body); tor_free(headers); tor_free(reason); - return -1; - } - log_info(LD_DIR,"Received authority certificates (size %d) from server " - "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); + tor_free(new_consensus); + return 0; +} - /* - * Tell trusted_dirs_load_certs_from_string() whether it was by fp - * or fp-sk pair. - */ - src_code = -1; - if (!strcmpstart(conn->requested_resource, "fp/")) { - src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST; - } else if (!strcmpstart(conn->requested_resource, "fp-sk/")) { - src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_SK_DIGEST; - } +/** + * Handler function: processes a response to a request for one or more + * authority certificates + **/ +static int +handle_response_fetch_certificate(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_CERTIFICATE); + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; - if (src_code != -1) { - if (trusted_dirs_load_certs_from_string(body, src_code, 1, - conn->identity_digest)<0) { - log_warn(LD_DIR, "Unable to parse fetched certificates"); - /* if we fetched more than one and only some failed, the successful - * ones got flushed to disk so it's safe to call this on them */ - connection_dir_download_cert_failed(conn, status_code); - } else { - directory_info_has_arrived(now, 0, 0); - log_info(LD_DIR, "Successfully loaded certificates from fetch."); - } - } else { - log_warn(LD_DIR, - "Couldn't figure out what to do with fetched certificates for " - "unknown resource %s", - conn->requested_resource); + if (status_code != 200) { + log_warn(LD_DIR, + "Received http status code %d (%s) from server " + "'%s:%d' while fetching \"/tor/keys/%s\".", + status_code, escaped(reason), conn->base_.address, + conn->base_.port, conn->requested_resource); + connection_dir_download_cert_failed(conn, status_code); + return -1; + } + log_info(LD_DIR,"Received authority certificates (body size %d) from " + "server '%s:%d'", + (int)body_len, conn->base_.address, conn->base_.port); + + /* + * Tell trusted_dirs_load_certs_from_string() whether it was by fp + * or fp-sk pair. + */ + int src_code = -1; + if (!strcmpstart(conn->requested_resource, "fp/")) { + src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST; + } else if (!strcmpstart(conn->requested_resource, "fp-sk/")) { + src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_SK_DIGEST; + } + + if (src_code != -1) { + if (trusted_dirs_load_certs_from_string(body, src_code, 1, + conn->identity_digest)<0) { + log_warn(LD_DIR, "Unable to parse fetched certificates"); + /* if we fetched more than one and only some failed, the successful + * ones got flushed to disk so it's safe to call this on them */ connection_dir_download_cert_failed(conn, status_code); + } else { + time_t now = approx_time(); + directory_info_has_arrived(now, 0, 0); + log_info(LD_DIR, "Successfully loaded certificates from fetch."); } + } else { + log_warn(LD_DIR, + "Couldn't figure out what to do with fetched certificates for " + "unknown resource %s", + conn->requested_resource); + connection_dir_download_cert_failed(conn, status_code); } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) { - const char *msg; - int st; - log_info(LD_DIR,"Got votes (size %d) from server %s:%d", - (int)body_len, conn->base_.address, conn->base_.port); - if (status_code != 200) { - log_warn(LD_DIR, + return 0; +} + +/** + * Handler function: processes a response to a request for an authority's + * current networkstatus vote. + **/ +static int +handle_response_fetch_status_vote(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE); + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + const char *msg; + int st; + log_info(LD_DIR,"Got votes (body size %d) from server %s:%d", + (int)body_len, conn->base_.address, conn->base_.port); + if (status_code != 200) { + log_warn(LD_DIR, "Received http status code %d (%s) from server " "'%s:%d' while fetching \"/tor/status-vote/next/%s.z\".", status_code, escaped(reason), conn->base_.address, conn->base_.port, conn->requested_resource); - tor_free(body); tor_free(headers); tor_free(reason); - return -1; - } - dirvote_add_vote(body, &msg, &st); - if (st > 299) { - log_warn(LD_DIR, "Error adding retrieved vote: %s", msg); - } else { - log_info(LD_DIR, "Added vote(s) successfully [msg: %s]", msg); - } + return -1; } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) { - const char *msg = NULL; - log_info(LD_DIR,"Got detached signatures (size %d) from server %s:%d", - (int)body_len, conn->base_.address, conn->base_.port); - if (status_code != 200) { - log_warn(LD_DIR, + dirvote_add_vote(body, &msg, &st); + if (st > 299) { + log_warn(LD_DIR, "Error adding retrieved vote: %s", msg); + } else { + log_info(LD_DIR, "Added vote(s) successfully [msg: %s]", msg); + } + + return 0; +} + +/** + * Handler function: processes a response to a request for the signatures + * that an authority knows about on a given consensus. + **/ +static int +handle_response_fetch_detached_signatures(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES); + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + const char *msg = NULL; + log_info(LD_DIR,"Got detached signatures (body size %d) from server %s:%d", + (int)body_len, conn->base_.address, conn->base_.port); + if (status_code != 200) { + log_warn(LD_DIR, "Received http status code %d (%s) from server '%s:%d' while fetching " "\"/tor/status-vote/next/consensus-signatures.z\".", - status_code, escaped(reason), conn->base_.address, - conn->base_.port); - tor_free(body); tor_free(headers); tor_free(reason); - return -1; - } - if (dirvote_add_signatures(body, conn->base_.address, &msg)<0) { - log_warn(LD_DIR, "Problem adding detached signatures from %s:%d: %s", - conn->base_.address, conn->base_.port, msg?msg:"???"); - } + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + return -1; + } + if (dirvote_add_signatures(body, conn->base_.address, &msg)<0) { + log_warn(LD_DIR, "Problem adding detached signatures from %s:%d: %s", + conn->base_.address, conn->base_.port, msg?msg:"???"); } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || - conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { - int was_ei = conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO; - smartlist_t *which = NULL; - int n_asked_for = 0; - int descriptor_digests = conn->requested_resource && - !strcmpstart(conn->requested_resource,"d/"); - log_info(LD_DIR,"Received %s (size %d) from server '%s:%d'", - was_ei ? "extra server info" : "server info", - (int)body_len, conn->base_.address, conn->base_.port); - if (conn->requested_resource && - (!strcmpstart(conn->requested_resource,"d/") || - !strcmpstart(conn->requested_resource,"fp/"))) { - which = smartlist_new(); - dir_split_resource_into_fingerprints(conn->requested_resource + - (descriptor_digests ? 2 : 3), - which, NULL, 0); - n_asked_for = smartlist_len(which); - } - if (status_code != 200) { - int dir_okay = status_code == 404 || - (status_code == 400 && !strcmp(reason, "Servers unavailable.")); - /* 404 means that it didn't have them; no big deal. - * Older (pre-0.1.1.8) servers said 400 Servers unavailable instead. */ - log_fn(dir_okay ? LOG_INFO : LOG_WARN, LD_DIR, - "Received http status code %d (%s) from server '%s:%d' " - "while fetching \"/tor/server/%s\". I'll try again soon.", - status_code, escaped(reason), conn->base_.address, - conn->base_.port, conn->requested_resource); - if (!which) { - connection_dir_download_routerdesc_failed(conn); - } else { - dir_routerdesc_download_failed(which, status_code, - conn->router_purpose, - was_ei, descriptor_digests); - SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); - smartlist_free(which); - } - tor_free(body); tor_free(headers); tor_free(reason); - return dir_okay ? 0 : -1; - } - /* Learn the routers, assuming we requested by fingerprint or "all" - * or "authority". - * - * We use "authority" to fetch our own descriptor for - * testing, and to fetch bridge descriptors for bootstrapping. Ignore - * the output of "authority" requests unless we are using bridges, - * since otherwise they'll be the response from reachability tests, - * and we don't really want to add that to our routerlist. */ - if (which || (conn->requested_resource && - (!strcmpstart(conn->requested_resource, "all") || - (!strcmpstart(conn->requested_resource, "authority") && - get_options()->UseBridges)))) { - /* as we learn from them, we remove them from 'which' */ - if (was_ei) { - router_load_extrainfo_from_string(body, NULL, SAVED_NOWHERE, which, - descriptor_digests); - } else { - //router_load_routers_from_string(body, NULL, SAVED_NOWHERE, which, - // descriptor_digests, conn->router_purpose); - if (load_downloaded_routers(body, which, descriptor_digests, - conn->router_purpose, - conn->base_.address)) - directory_info_has_arrived(now, 0, 0); - } - } - if (which) { /* mark remaining ones as failed */ - log_info(LD_DIR, "Received %d/%d %s requested from %s:%d", - n_asked_for-smartlist_len(which), n_asked_for, - was_ei ? "extra-info documents" : "router descriptors", - conn->base_.address, (int)conn->base_.port); - if (smartlist_len(which)) { - dir_routerdesc_download_failed(which, status_code, - conn->router_purpose, - was_ei, descriptor_digests); - } - SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); - smartlist_free(which); - } - if (directory_conn_is_self_reachability_test(conn)) - router_dirport_found_reachable(); - } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC) { - smartlist_t *which = NULL; - log_info(LD_DIR,"Received answer to microdescriptor request (status %d, " - "size %d) from server '%s:%d'", - status_code, (int)body_len, conn->base_.address, - conn->base_.port); - tor_assert(conn->requested_resource && - !strcmpstart(conn->requested_resource, "d/")); + return 0; +} + +/** + * Handler function: processes a response to a request for a group of server + * descriptors or an extrainfo documents. + **/ +static int +handle_response_fetch_desc(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || + conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO); + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + int was_ei = conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO; + smartlist_t *which = NULL; + int n_asked_for = 0; + int descriptor_digests = conn->requested_resource && + !strcmpstart(conn->requested_resource,"d/"); + log_info(LD_DIR,"Received %s (body size %d) from server '%s:%d'", + was_ei ? "extra server info" : "server info", + (int)body_len, conn->base_.address, conn->base_.port); + if (conn->requested_resource && + (!strcmpstart(conn->requested_resource,"d/") || + !strcmpstart(conn->requested_resource,"fp/"))) { which = smartlist_new(); - dir_split_resource_into_fingerprints(conn->requested_resource+2, - which, NULL, - DSR_DIGEST256|DSR_BASE64); - if (status_code != 200) { - log_info(LD_DIR, "Received status code %d (%s) from server " - "'%s:%d' while fetching \"/tor/micro/%s\". I'll try again " - "soon.", - status_code, escaped(reason), conn->base_.address, - (int)conn->base_.port, conn->requested_resource); - dir_microdesc_download_failed(which, status_code); + dir_split_resource_into_fingerprints(conn->requested_resource + + (descriptor_digests ? 2 : 3), + which, NULL, 0); + n_asked_for = smartlist_len(which); + } + if (status_code != 200) { + int dir_okay = status_code == 404 || + (status_code == 400 && !strcmp(reason, "Servers unavailable.")); + /* 404 means that it didn't have them; no big deal. + * Older (pre-0.1.1.8) servers said 400 Servers unavailable instead. */ + log_fn(dir_okay ? LOG_INFO : LOG_WARN, LD_DIR, + "Received http status code %d (%s) from server '%s:%d' " + "while fetching \"/tor/server/%s\". I'll try again soon.", + status_code, escaped(reason), conn->base_.address, + conn->base_.port, conn->requested_resource); + if (!which) { + connection_dir_download_routerdesc_failed(conn); + } else { + dir_routerdesc_download_failed(which, status_code, + conn->router_purpose, + was_ei, descriptor_digests); SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); smartlist_free(which); - tor_free(body); tor_free(headers); tor_free(reason); - return 0; + } + return dir_okay ? 0 : -1; + } + /* Learn the routers, assuming we requested by fingerprint or "all" + * or "authority". + * + * We use "authority" to fetch our own descriptor for + * testing, and to fetch bridge descriptors for bootstrapping. Ignore + * the output of "authority" requests unless we are using bridges, + * since otherwise they'll be the response from reachability tests, + * and we don't really want to add that to our routerlist. */ + if (which || (conn->requested_resource && + (!strcmpstart(conn->requested_resource, "all") || + (!strcmpstart(conn->requested_resource, "authority") && + get_options()->UseBridges)))) { + /* as we learn from them, we remove them from 'which' */ + if (was_ei) { + router_load_extrainfo_from_string(body, NULL, SAVED_NOWHERE, which, + descriptor_digests); } else { - smartlist_t *mds; - mds = microdescs_add_to_cache(get_microdesc_cache(), - body, body+body_len, SAVED_NOWHERE, 0, - now, which); - if (smartlist_len(which)) { - /* Mark remaining ones as failed. */ - dir_microdesc_download_failed(which, status_code); - } - if (mds && smartlist_len(mds)) { - control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, - count_loading_descriptors_progress()); - directory_info_has_arrived(now, 0, 1); + //router_load_routers_from_string(body, NULL, SAVED_NOWHERE, which, + // descriptor_digests, conn->router_purpose); + if (load_downloaded_routers(body, which, descriptor_digests, + conn->router_purpose, + conn->base_.address)) { + time_t now = approx_time(); + directory_info_has_arrived(now, 0, 0); } - SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); - smartlist_free(which); - smartlist_free(mds); } } + if (which) { /* mark remaining ones as failed */ + log_info(LD_DIR, "Received %d/%d %s requested from %s:%d", + n_asked_for-smartlist_len(which), n_asked_for, + was_ei ? "extra-info documents" : "router descriptors", + conn->base_.address, (int)conn->base_.port); + if (smartlist_len(which)) { + dir_routerdesc_download_failed(which, status_code, + conn->router_purpose, + was_ei, descriptor_digests); + } + SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); + smartlist_free(which); + } + if (directory_conn_is_self_reachability_test(conn)) + router_dirport_found_reachable(); - if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_DIR) { - switch (status_code) { - case 200: { - dir_server_t *ds = - router_get_trusteddirserver_by_digest(conn->identity_digest); - char *rejected_hdr = http_get_header(headers, - "X-Descriptor-Not-New: "); - if (rejected_hdr) { - if (!strcmp(rejected_hdr, "Yes")) { - log_info(LD_GENERAL, - "Authority '%s' declined our descriptor (not new)", - ds->nickname); - /* XXXX use this information; be sure to upload next one - * sooner. -NM */ - /* XXXX++ On further thought, the task above implies that we're - * basing our regenerate-descriptor time on when we uploaded the - * last descriptor, not on the published time of the last - * descriptor. If those are different, that's a bad thing to - * do. -NM */ - } - tor_free(rejected_hdr); - } - log_info(LD_GENERAL,"eof (status 200) after uploading server " - "descriptor: finished."); - control_event_server_status( - LOG_NOTICE, "ACCEPTED_SERVER_DESCRIPTOR DIRAUTH=%s:%d", - conn->base_.address, conn->base_.port); - - ds->has_accepted_serverdesc = 1; - if (directories_have_accepted_server_descriptor()) - control_event_server_status(LOG_NOTICE, "GOOD_SERVER_DESCRIPTOR"); - } - break; - case 400: - log_warn(LD_GENERAL,"http status 400 (%s) response from " - "dirserver '%s:%d'. Please correct.", - escaped(reason), conn->base_.address, conn->base_.port); - control_event_server_status(LOG_WARN, - "BAD_SERVER_DESCRIPTOR DIRAUTH=%s:%d REASON=\"%s\"", - conn->base_.address, conn->base_.port, escaped(reason)); - break; - default: - log_warn(LD_GENERAL, - "http status %d (%s) reason unexpected while uploading " - "descriptor to server '%s:%d').", + return 0; +} + +/** + * Handler function: processes a response to a request for a group of + * microdescriptors + **/ +STATIC int +handle_response_fetch_microdesc(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC); + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + smartlist_t *which = NULL; + log_info(LD_DIR,"Received answer to microdescriptor request (status %d, " + "body size %d) from server '%s:%d'", + status_code, (int)body_len, conn->base_.address, + 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, + DSR_DIGEST256|DSR_BASE64); + if (status_code != 200) { + log_info(LD_DIR, "Received status code %d (%s) from server " + "'%s:%d' while fetching \"/tor/micro/%s\". I'll try again " + "soon.", status_code, escaped(reason), conn->base_.address, - conn->base_.port); - break; + (int)conn->base_.port, conn->requested_resource); + dir_microdesc_download_failed(which, status_code, conn->identity_digest); + SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); + smartlist_free(which); + return 0; + } else { + smartlist_t *mds; + time_t now = approx_time(); + mds = microdescs_add_to_cache(get_microdesc_cache(), + body, body+body_len, SAVED_NOWHERE, 0, + now, which); + if (smartlist_len(which)) { + /* Mark remaining ones as failed. */ + dir_microdesc_download_failed(which, status_code, conn->identity_digest); + } + if (mds && smartlist_len(mds)) { + control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, + count_loading_descriptors_progress()); + directory_info_has_arrived(now, 0, 1); } - /* return 0 in all cases, since we don't want to mark any - * dirservers down just because they don't like us. */ + SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); + smartlist_free(which); + smartlist_free(mds); } - if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_VOTE) { - switch (status_code) { - case 200: { - log_notice(LD_DIR,"Uploaded a vote to dirserver %s:%d", + return 0; +} + +/** + * Handler function: processes a response to a POST request to upload our + * router descriptor. + **/ +static int +handle_response_upload_dir(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_DIR); + const int status_code = args->status_code; + const char *reason = args->reason; + const char *headers = args->headers; + + switch (status_code) { + case 200: { + dir_server_t *ds = + router_get_trusteddirserver_by_digest(conn->identity_digest); + char *rejected_hdr = http_get_header(headers, + "X-Descriptor-Not-New: "); + if (rejected_hdr) { + if (!strcmp(rejected_hdr, "Yes")) { + log_info(LD_GENERAL, + "Authority '%s' declined our descriptor (not new)", + ds->nickname); + /* XXXX use this information; be sure to upload next one + * sooner. -NM */ + /* XXXX++ On further thought, the task above implies that we're + * basing our regenerate-descriptor time on when we uploaded the + * last descriptor, not on the published time of the last + * descriptor. If those are different, that's a bad thing to + * do. -NM */ + } + tor_free(rejected_hdr); + } + log_info(LD_GENERAL,"eof (status 200) after uploading server " + "descriptor: finished."); + control_event_server_status( + LOG_NOTICE, "ACCEPTED_SERVER_DESCRIPTOR DIRAUTH=%s:%d", conn->base_.address, conn->base_.port); - } - break; - case 400: - log_warn(LD_DIR,"http status 400 (%s) response after uploading " - "vote to dirserver '%s:%d'. Please correct.", - escaped(reason), conn->base_.address, conn->base_.port); - break; - default: - log_warn(LD_GENERAL, - "http status %d (%s) reason unexpected while uploading " - "vote to server '%s:%d').", + + ds->has_accepted_serverdesc = 1; + if (directories_have_accepted_server_descriptor()) + control_event_server_status(LOG_NOTICE, "GOOD_SERVER_DESCRIPTOR"); + } + break; + case 400: + log_warn(LD_GENERAL,"http status 400 (%s) response from " + "dirserver '%s:%d'. Please correct.", + escaped(reason), conn->base_.address, conn->base_.port); + control_event_server_status(LOG_WARN, + "BAD_SERVER_DESCRIPTOR DIRAUTH=%s:%d REASON=\"%s\"", + conn->base_.address, conn->base_.port, escaped(reason)); + break; + default: + log_warn(LD_GENERAL, + "HTTP status %d (%s) was unexpected while uploading " + "descriptor to server '%s:%d'. Possibly the server is " + "misconfigured?", status_code, escaped(reason), conn->base_.address, conn->base_.port); - break; - } - /* return 0 in all cases, since we don't want to mark any - * dirservers down just because they don't like us. */ + break; } + /* return 0 in all cases, since we don't want to mark any + * dirservers down just because they don't like us. */ - if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_SIGNATURES) { - switch (status_code) { - case 200: { - log_notice(LD_DIR,"Uploaded signature(s) to dirserver %s:%d", - conn->base_.address, conn->base_.port); - } - break; - case 400: - log_warn(LD_DIR,"http status 400 (%s) response after uploading " - "signatures to dirserver '%s:%d'. Please correct.", - escaped(reason), conn->base_.address, conn->base_.port); - break; - default: - log_warn(LD_GENERAL, - "http status %d (%s) reason unexpected while uploading " - "signatures to server '%s:%d').", + return 0; +} + +/** + * Handler function: processes a response to POST request to upload our + * own networkstatus vote. + **/ +static int +handle_response_upload_vote(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_VOTE); + const int status_code = args->status_code; + const char *reason = args->reason; + + switch (status_code) { + case 200: { + log_notice(LD_DIR,"Uploaded a vote to dirserver %s:%d", + conn->base_.address, conn->base_.port); + } + break; + case 400: + log_warn(LD_DIR,"http status 400 (%s) response after uploading " + "vote to dirserver '%s:%d'. Please correct.", + escaped(reason), conn->base_.address, conn->base_.port); + break; + default: + log_warn(LD_GENERAL, + "HTTP status %d (%s) was unexpected while uploading " + "vote to server '%s:%d'.", status_code, escaped(reason), conn->base_.address, conn->base_.port); - break; - } - /* return 0 in all cases, since we don't want to mark any - * dirservers down just because they don't like us. */ - } - - if (conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) { - #define SEND_HS_DESC_FAILED_EVENT(reason) ( \ - control_event_hs_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), \ - conn->requested_resource, \ + break; + } + /* return 0 in all cases, since we don't want to mark any + * dirservers down just because they don't like us. */ + return 0; +} + +/** + * Handler function: processes a response to POST request to upload our + * view of the signatures on the current consensus. + **/ +static int +handle_response_upload_signatures(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_SIGNATURES); + const int status_code = args->status_code; + const char *reason = args->reason; + + switch (status_code) { + case 200: { + log_notice(LD_DIR,"Uploaded signature(s) to dirserver %s:%d", + conn->base_.address, conn->base_.port); + } + break; + case 400: + log_warn(LD_DIR,"http status 400 (%s) response after uploading " + "signatures to dirserver '%s:%d'. Please correct.", + escaped(reason), conn->base_.address, conn->base_.port); + break; + default: + log_warn(LD_GENERAL, + "HTTP status %d (%s) was unexpected while uploading " + "signatures to server '%s:%d'.", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + break; + } + /* return 0 in all cases, since we don't want to mark any + * dirservers down just because they don't like us. */ + + return 0; +} + +/** + * Handler function: processes a response to a request for a v2 hidden service + * descriptor. + **/ +static int +handle_response_fetch_renddesc_v2(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2); + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + 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, \ - NULL) ) - tor_assert(conn->rend_data); - log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d " - "(%s))", - (int)body_len, status_code, escaped(reason)); - switch (status_code) { - case 200: - { - rend_cache_entry_t *entry = NULL; - - if (rend_cache_store_v2_desc_as_client(body, - conn->requested_resource, conn->rend_data, &entry) < 0) { - log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. " - "Retrying at another directory."); - /* We'll retry when connection_about_to_close_connection() - * cleans this dir conn up. */ - SEND_HS_DESC_FAILED_EVENT("BAD_DESC"); - SEND_HS_DESC_FAILED_CONTENT(); - } else { - char service_id[REND_SERVICE_ID_LEN_BASE32 + 1]; - /* Should never be NULL here if we found the descriptor. */ - tor_assert(entry); - rend_get_service_id(entry->parsed->pk, service_id); - - /* 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_hs_descriptor_content(service_id, - conn->requested_resource, - conn->identity_digest, - body); - conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2; - rend_client_desc_trynow(service_id); - memwipe(service_id, 0, sizeof(service_id)); - } - break; - } - case 404: - /* Not there. We'll retry when - * connection_about_to_close_connection() cleans this conn up. */ - log_info(LD_REND,"Fetching v2 rendezvous descriptor failed: " - "Retrying at another directory."); - SEND_HS_DESC_FAILED_EVENT("NOT_FOUND"); - SEND_HS_DESC_FAILED_CONTENT(); - break; - case 400: - log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: " - "http status 400 (%s). Dirserver didn't like our " - "v2 rendezvous query? Retrying at another directory.", - escaped(reason)); - SEND_HS_DESC_FAILED_EVENT("QUERY_REJECTED"); - SEND_HS_DESC_FAILED_CONTENT(); - break; - default: - log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: " - "http status %d (%s) response unexpected while " - "fetching v2 hidden service descriptor (server '%s:%d'). " - "Retrying at another directory.", - status_code, escaped(reason), conn->base_.address, - conn->base_.port); - SEND_HS_DESC_FAILED_EVENT("UNEXPECTED"); + reason)) +#define SEND_HS_DESC_FAILED_CONTENT() \ + (control_event_hs_descriptor_content( \ + rend_data_get_address(conn->rend_data), \ + conn->requested_resource, \ + conn->identity_digest, \ + NULL)) + + tor_assert(conn->rend_data); + log_info(LD_REND,"Received rendezvous descriptor (body size %d, status %d " + "(%s))", + (int)body_len, status_code, escaped(reason)); + switch (status_code) { + case 200: + { + rend_cache_entry_t *entry = NULL; + + if (rend_cache_store_v2_desc_as_client(body, + conn->requested_resource, + conn->rend_data, &entry) < 0) { + log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. " + "Retrying at another directory."); + /* We'll retry when connection_about_to_close_connection() + * cleans this dir conn up. */ + SEND_HS_DESC_FAILED_EVENT("BAD_DESC"); SEND_HS_DESC_FAILED_CONTENT(); - break; + } else { + char service_id[REND_SERVICE_ID_LEN_BASE32 + 1]; + /* Should never be NULL here if we found the descriptor. */ + tor_assert(entry); + rend_get_service_id(entry->parsed->pk, service_id); + + /* 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_hs_descriptor_content(service_id, + conn->requested_resource, + conn->identity_digest, + body); + conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2; + rend_client_desc_trynow(service_id); + memwipe(service_id, 0, sizeof(service_id)); + } + break; } + case 404: + /* Not there. We'll retry when + * connection_about_to_close_connection() cleans this conn up. */ + log_info(LD_REND,"Fetching v2 rendezvous descriptor failed: " + "Retrying at another directory."); + SEND_HS_DESC_FAILED_EVENT("NOT_FOUND"); + SEND_HS_DESC_FAILED_CONTENT(); + break; + case 400: + log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: " + "http status 400 (%s). Dirserver didn't like our " + "v2 rendezvous query? Retrying at another directory.", + escaped(reason)); + SEND_HS_DESC_FAILED_EVENT("QUERY_REJECTED"); + SEND_HS_DESC_FAILED_CONTENT(); + break; + default: + log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: " + "http status %d (%s) response unexpected while " + "fetching v2 hidden service descriptor (server '%s:%d'). " + "Retrying at another directory.", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + SEND_HS_DESC_FAILED_EVENT("UNEXPECTED"); + SEND_HS_DESC_FAILED_CONTENT(); + break; } - if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) { - #define SEND_HS_DESC_UPLOAD_FAILED_EVENT(reason) ( \ - control_event_hs_descriptor_upload_failed( \ - conn->identity_digest, \ - rend_data_get_address(conn->rend_data), \ - reason) ) - log_info(LD_REND,"Uploaded rendezvous descriptor (status %d " - "(%s))", - status_code, escaped(reason)); - /* Without the rend data, we'll have a problem identifying what has been - * uploaded for which service. */ - tor_assert(conn->rend_data); - switch (status_code) { - case 200: - log_info(LD_REND, - "Uploading rendezvous descriptor: finished with status " - "200 (%s)", escaped(reason)); - control_event_hs_descriptor_uploaded(conn->identity_digest, - rend_data_get_address(conn->rend_data)); - rend_service_desc_has_uploaded(conn->rend_data); - break; - case 400: - log_warn(LD_REND,"http status 400 (%s) response from dirserver " - "'%s:%d'. Malformed rendezvous descriptor?", - escaped(reason), conn->base_.address, conn->base_.port); - SEND_HS_DESC_UPLOAD_FAILED_EVENT("UPLOAD_REJECTED"); - break; - default: - log_warn(LD_REND,"http status %d (%s) response unexpected (server " - "'%s:%d').", - status_code, escaped(reason), conn->base_.address, - conn->base_.port); - SEND_HS_DESC_UPLOAD_FAILED_EVENT("UNEXPECTED"); - break; - } + return 0; +} + +/** + * Handler function: processes a response to a POST request to upload a v2 + * hidden service descriptor. + **/ +static int +handle_response_upload_renddesc_v2(dir_connection_t *conn, + const response_handler_args_t *args) +{ + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2); + const int status_code = args->status_code; + const char *reason = args->reason; + +#define SEND_HS_DESC_UPLOAD_FAILED_EVENT(reason) \ + (control_event_hs_descriptor_upload_failed( \ + conn->identity_digest, \ + rend_data_get_address(conn->rend_data), \ + reason)) + + log_info(LD_REND,"Uploaded rendezvous descriptor (status %d " + "(%s))", + status_code, escaped(reason)); + /* Without the rend data, we'll have a problem identifying what has been + * uploaded for which service. */ + tor_assert(conn->rend_data); + switch (status_code) { + case 200: + log_info(LD_REND, + "Uploading rendezvous descriptor: finished with status " + "200 (%s)", escaped(reason)); + control_event_hs_descriptor_uploaded(conn->identity_digest, + rend_data_get_address(conn->rend_data)); + rend_service_desc_has_uploaded(conn->rend_data); + break; + case 400: + log_warn(LD_REND,"http status 400 (%s) response from dirserver " + "'%s:%d'. Malformed rendezvous descriptor?", + escaped(reason), conn->base_.address, conn->base_.port); + SEND_HS_DESC_UPLOAD_FAILED_EVENT("UPLOAD_REJECTED"); + break; + default: + log_warn(LD_REND,"http status %d (%s) response unexpected (server " + "'%s:%d').", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + SEND_HS_DESC_UPLOAD_FAILED_EVENT("UNEXPECTED"); + break; } - tor_free(body); tor_free(headers); tor_free(reason); + return 0; } @@ -2662,14 +3282,12 @@ static void write_http_status_line(dir_connection_t *conn, int status, const char *reason_phrase) { - char buf[256]; - if (tor_snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\r\n\r\n", - status, reason_phrase ? reason_phrase : "OK") < 0) { - log_warn(LD_BUG,"status line too long."); - return; - } + char *buf = NULL; + tor_asprintf(&buf, "HTTP/1.0 %d %s\r\n\r\n", + status, reason_phrase ? reason_phrase : "OK"); log_debug(LD_DIRSERV,"Wrote status 'HTTP/1.0 %d %s'", status, reason_phrase); connection_write_to_buf(buf, strlen(buf), TO_CONN(conn)); + tor_free(buf); } /** Write the header for an HTTP/1.0 response onto <b>conn</b>-\>outbuf, @@ -2748,14 +3366,114 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, /** As write_http_response_header_impl, but sets encoding and content-typed * based on whether the response will be <b>compressed</b> or not. */ static void -write_http_response_header(dir_connection_t *conn, ssize_t length, - int compressed, long cache_lifetime) +write_http_response_headers(dir_connection_t *conn, ssize_t length, + compress_method_t method, + const char *extra_headers, long cache_lifetime) { + const char *methodname = compression_method_get_name(method); + const char *doctype; + if (method == NO_METHOD) + doctype = "text/plain"; + else + doctype = "application/octet-stream"; write_http_response_header_impl(conn, length, - compressed?"application/octet-stream":"text/plain", - compressed?"deflate":"identity", - NULL, - cache_lifetime); + doctype, + methodname, + extra_headers, + cache_lifetime); +} + +/** As write_http_response_headers, but assumes extra_headers is NULL */ +static void +write_http_response_header(dir_connection_t *conn, ssize_t length, + compress_method_t method, + long cache_lifetime) +{ + write_http_response_headers(conn, length, method, NULL, cache_lifetime); +} + +/** Array of compression methods to use (if supported) for serving + * precompressed data, ordered from best to worst. */ +static compress_method_t srv_meth_pref_precompressed[] = { + LZMA_METHOD, + ZSTD_METHOD, + ZLIB_METHOD, + GZIP_METHOD, + NO_METHOD +}; + +/** Array of compression methods to use (if supported) for serving + * streamed data, ordered from best to worst. */ +static compress_method_t srv_meth_pref_streaming_compression[] = { + ZSTD_METHOD, + ZLIB_METHOD, + GZIP_METHOD, + NO_METHOD +}; + +/** Array of allowed compression methods to use (if supported) when receiving a + * response from a request that was required to be anonymous. */ +static compress_method_t client_meth_allowed_anonymous_compression[] = { + ZLIB_METHOD, + GZIP_METHOD, + NO_METHOD +}; + +/** Parse the compression methods listed in an Accept-Encoding header <b>h</b>, + * and convert them to a bitfield where compression method x is supported if + * and only if 1 << x is set in the bitfield. */ +STATIC unsigned +parse_accept_encoding_header(const char *h) +{ + unsigned result = (1u << NO_METHOD); + smartlist_t *methods = smartlist_new(); + smartlist_split_string(methods, h, ",", + SPLIT_SKIP_SPACE|SPLIT_STRIP_SPACE|SPLIT_IGNORE_BLANK, 0); + + SMARTLIST_FOREACH_BEGIN(methods, const char *, m) { + compress_method_t method = compression_method_get_by_name(m); + if (method != UNKNOWN_METHOD) { + tor_assert(((unsigned)method) < 8*sizeof(unsigned)); + result |= (1u << method); + } + } SMARTLIST_FOREACH_END(m); + SMARTLIST_FOREACH_BEGIN(methods, char *, m) { + tor_free(m); + } SMARTLIST_FOREACH_END(m); + smartlist_free(methods); + return result; +} + +/** Array of compression methods to use (if supported) for requesting + * compressed data, ordered from best to worst. */ +static compress_method_t client_meth_pref[] = { + LZMA_METHOD, + ZSTD_METHOD, + ZLIB_METHOD, + GZIP_METHOD, + NO_METHOD +}; + +/** Return a newly allocated string containing a comma separated list of + * supported encodings. */ +STATIC char * +accept_encoding_header(void) +{ + smartlist_t *methods = smartlist_new(); + char *header = NULL; + compress_method_t method; + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(client_meth_pref); ++i) { + method = client_meth_pref[i]; + if (tor_compress_supports_method(method)) + smartlist_add(methods, (char *)compression_method_get_name(method)); + } + + header = smartlist_join_strings(methods, ", ", 0, NULL); + smartlist_free(methods); + + return header; } /** Decide whether a client would accept the consensus we have. @@ -2773,48 +3491,45 @@ write_http_response_header(dir_connection_t *conn, ssize_t length, * consensus, 0 otherwise. */ int -client_likes_consensus(networkstatus_t *v, const char *want_url) +client_likes_consensus(const struct consensus_cache_entry_t *ent, + const char *want_url) { - smartlist_t *want_authorities = smartlist_new(); + smartlist_t *voters = smartlist_new(); int need_at_least; int have = 0; + if (consensus_cache_entry_get_voter_id_digests(ent, voters) != 0) { + return 1; // We don't know the voters; assume the client won't mind. */ + } + + smartlist_t *want_authorities = smartlist_new(); dir_split_resource_into_fingerprints(want_url, want_authorities, NULL, 0); need_at_least = smartlist_len(want_authorities)/2+1; - SMARTLIST_FOREACH_BEGIN(want_authorities, const char *, d) { - char want_digest[DIGEST_LEN]; - size_t want_len = strlen(d)/2; - if (want_len > DIGEST_LEN) - want_len = DIGEST_LEN; - - if (base16_decode(want_digest, DIGEST_LEN, d, want_len*2) - != (int) want_len) { - log_fn(LOG_PROTOCOL_WARN, LD_DIR, - "Failed to decode requested authority digest %s.", escaped(d)); - continue; - }; - SMARTLIST_FOREACH_BEGIN(v->voters, networkstatus_voter_info_t *, vi) { - if (smartlist_len(vi->sigs) && - tor_memeq(vi->identity_digest, want_digest, want_len)) { + SMARTLIST_FOREACH_BEGIN(want_authorities, const char *, want_digest) { + + SMARTLIST_FOREACH_BEGIN(voters, const char *, digest) { + if (!strcasecmpstart(digest, want_digest)) { have++; break; }; - } SMARTLIST_FOREACH_END(vi); + } SMARTLIST_FOREACH_END(digest); /* early exit, if we already have enough */ if (have >= need_at_least) break; - } SMARTLIST_FOREACH_END(d); + } SMARTLIST_FOREACH_END(want_digest); SMARTLIST_FOREACH(want_authorities, char *, d, tor_free(d)); smartlist_free(want_authorities); + SMARTLIST_FOREACH(voters, char *, cp, tor_free(cp)); + smartlist_free(voters); return (have >= need_at_least); } /** Return the compression level we should use for sending a compressed * response of size <b>n_bytes</b>. */ -STATIC zlib_compression_level_t +STATIC compression_level_t choose_compression_level(ssize_t n_bytes) { if (! have_been_under_memory_pressure()) { @@ -2832,8 +3547,9 @@ choose_compression_level(ssize_t n_bytes) /** Information passed to handle a GET request. */ typedef struct get_handler_args_t { - /** True if the client asked for compressed data. */ - int compressed; + /** Bitmask of compression methods that the client said (or implied) it + * supported. */ + unsigned compression_supported; /** If nonzero, the time included an if-modified-since header with this * value. */ time_t if_modified_since; @@ -2907,8 +3623,9 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, { char *url, *url_mem, *header; time_t if_modified_since = 0; - int compressed; + int zlib_compressed_in_url; size_t url_len; + unsigned compression_methods_supported; /* We ignore the body of a GET request. */ (void)req_body; @@ -2939,17 +3656,31 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, url_mem = url; url_len = strlen(url); - compressed = url_len > 2 && !strcmp(url+url_len-2, ".z"); - if (compressed) { + + zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z"); + if (zlib_compressed_in_url) { url[url_len-2] = '\0'; url_len -= 2; } + if ((header = http_get_header(headers, "Accept-Encoding: "))) { + compression_methods_supported = parse_accept_encoding_header(header); + tor_free(header); + } else { + compression_methods_supported = (1u << NO_METHOD); + } + if (zlib_compressed_in_url) { + compression_methods_supported |= (1u << ZLIB_METHOD); + } + + /* Remove all methods that we don't both support. */ + compression_methods_supported &= tor_compress_get_supported_method_bitmask(); + get_handler_args_t args; args.url = url; args.headers = headers; args.if_modified_since = if_modified_since; - args.compressed = compressed; + args.compression_supported = compression_methods_supported; int i, result = -1; for (i = 0; url_table[i].string; ++i) { @@ -2999,20 +3730,25 @@ handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args) return 0; } -/** Warn that the consensus <b>v</b> of type <b>flavor</b> is too old and will - * not be served to clients. Rate-limit the warning to avoid logging an entry - * on every request. +/** Warn that the cached consensus <b>consensus</b> of type + * <b>flavor</b> is too old and will not be served to clients. Rate-limit the + * warning to avoid logging an entry on every request. */ static void -warn_consensus_is_too_old(networkstatus_t *v, const char *flavor, time_t now) +warn_consensus_is_too_old(const struct consensus_cache_entry_t *consensus, + const char *flavor, time_t now) { #define TOO_OLD_WARNING_INTERVAL (60*60) static ratelim_t warned = RATELIM_INIT(TOO_OLD_WARNING_INTERVAL); char timestamp[ISO_TIME_LEN+1]; + time_t valid_until; char *dupes; + if (consensus_cache_entry_get_valid_until(consensus, &valid_until)) + return; + if ((dupes = rate_limit_log(&warned, now))) { - format_local_iso_time(timestamp, v->valid_until); + format_local_iso_time(timestamp, valid_until); log_warn(LD_DIRSERV, "Our %s%sconsensus is too old, so we will not " "serve it to clients. It was valid until %s local time and we " "continued to serve it for up to 24 hours after it expired.%s", @@ -3021,139 +3757,496 @@ warn_consensus_is_too_old(networkstatus_t *v, const char *flavor, time_t now) } } -/** Helper function for GET /tor/status-vote/current/consensus +/** + * Parse a single hex-encoded sha3-256 digest from <b>hex</b> into + * <b>digest</b>. Return 0 on success. On failure, report that the hash came + * from <b>location</b>, report that we are taking <b>action</b> with it, and + * return -1. */ static int -handle_get_current_consensus(dir_connection_t *conn, - const get_handler_args_t *args) +parse_one_diff_hash(uint8_t *digest, const char *hex, const char *location, + const char *action) { - const char *url = args->url; - const int compressed = args->compressed; - const time_t if_modified_since = args->if_modified_since; + if (base16_decode((char*)digest, DIGEST256_LEN, hex, strlen(hex)) == + DIGEST256_LEN) { + return 0; + } else { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "%s contained bogus digest %s; %s.", + location, escaped(hex), action); + return -1; + } +} - { - /* v3 network status fetch. */ - smartlist_t *dir_fps = smartlist_new(); - long lifetime = NETWORKSTATUS_CACHE_LIFETIME; +/** If there is an X-Or-Diff-From-Consensus header included in <b>headers</b>, + * set <b>digest_out<b> to a new smartlist containing every 256-bit + * hex-encoded digest listed in that header and return 0. Otherwise return + * -1. */ +static int +parse_or_diff_from_header(smartlist_t **digests_out, const char *headers) +{ + char *hdr = http_get_header(headers, X_OR_DIFF_FROM_CONSENSUS_HEADER); + if (hdr == NULL) { + return -1; + } + smartlist_t *hex_digests = smartlist_new(); + *digests_out = smartlist_new(); + smartlist_split_string(hex_digests, hdr, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + SMARTLIST_FOREACH_BEGIN(hex_digests, const char *, hex) { + uint8_t digest[DIGEST256_LEN]; + if (!parse_one_diff_hash(digest, hex, "X-Or-Diff-From-Consensus header", + "ignoring")) { + smartlist_add(*digests_out, tor_memdup(digest, sizeof(digest))); + } + } SMARTLIST_FOREACH_END(hex); + SMARTLIST_FOREACH(hex_digests, char *, cp, tor_free(cp)); + smartlist_free(hex_digests); + tor_free(hdr); + return 0; +} - networkstatus_t *v; - time_t now = time(NULL); - const char *want_fps = NULL; - char *flavor = NULL; - int flav = FLAV_NS; -#define CONSENSUS_URL_PREFIX "/tor/status-vote/current/consensus/" -#define CONSENSUS_FLAVORED_PREFIX "/tor/status-vote/current/consensus-" - /* figure out the flavor if any, and who we wanted to sign the thing */ - if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) { - const char *f, *cp; - f = url + strlen(CONSENSUS_FLAVORED_PREFIX); - cp = strchr(f, '/'); - if (cp) { - want_fps = cp+1; - flavor = tor_strndup(f, cp-f); - } else { - flavor = tor_strdup(f); +/** Fallback compression method. The fallback compression method is used in + * case a client requests a non-compressed document. We only store compressed + * documents, so we use this compression method to fetch the document and let + * the spooling system do the streaming decompression. + */ +#define FALLBACK_COMPRESS_METHOD ZLIB_METHOD + +/** + * Try to find the best consensus diff possible in order to serve a client + * request for a diff from one of the consensuses in <b>digests</b> to the + * current consensus of flavor <b>flav</b>. The client supports the + * compression methods listed in the <b>compression_methods</b> bitfield: + * place the method chosen (if any) into <b>compression_used_out</b>. + */ +static struct consensus_cache_entry_t * +find_best_diff(const smartlist_t *digests, int flav, + unsigned compression_methods, + compress_method_t *compression_used_out) +{ + struct consensus_cache_entry_t *result = NULL; + + SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, diff_from) { + unsigned u; + for (u = 0; u < ARRAY_LENGTH(srv_meth_pref_precompressed); ++u) { + compress_method_t method = srv_meth_pref_precompressed[u]; + if (0 == (compression_methods & (1u<<method))) + continue; // client doesn't like this one, or we don't have it. + if (consdiffmgr_find_diff_from(&result, flav, DIGEST_SHA3_256, + diff_from, DIGEST256_LEN, + method) == CONSDIFF_AVAILABLE) { + tor_assert_nonfatal(result); + *compression_used_out = method; + return result; } - flav = networkstatus_parse_flavor_name(flavor); - if (flav < 0) - flav = FLAV_NS; - } else { - if (!strcmpstart(url, CONSENSUS_URL_PREFIX)) - want_fps = url+strlen(CONSENSUS_URL_PREFIX); } + } SMARTLIST_FOREACH_END(diff_from); + + SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, diff_from) { + if (consdiffmgr_find_diff_from(&result, flav, DIGEST_SHA3_256, diff_from, + DIGEST256_LEN, FALLBACK_COMPRESS_METHOD) == CONSDIFF_AVAILABLE) { + tor_assert_nonfatal(result); + *compression_used_out = FALLBACK_COMPRESS_METHOD; + return result; + } + } SMARTLIST_FOREACH_END(diff_from); - v = networkstatus_get_latest_consensus_by_flavor(flav); + return NULL; +} - if (v && !networkstatus_consensus_reasonably_live(v, now)) { - write_http_status_line(conn, 404, "Consensus is too old"); - warn_consensus_is_too_old(v, flavor, now); - smartlist_free(dir_fps); - geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); - tor_free(flavor); - goto done; - } +/** Lookup the cached consensus document by the flavor found in <b>flav</b>. + * The prefered set of compression methods should be listed in the + * <b>compression_methods</b> bitfield. The compression method chosen (if any) + * is stored in <b>compression_used_out</b>. */ +static struct consensus_cache_entry_t * +find_best_consensus(int flav, + unsigned compression_methods, + compress_method_t *compression_used_out) +{ + struct consensus_cache_entry_t *result = NULL; + unsigned u; - if (v && want_fps && - !client_likes_consensus(v, want_fps)) { - write_http_status_line(conn, 404, "Consensus not signed by sufficient " - "number of requested authorities"); - smartlist_free(dir_fps); - geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS); - tor_free(flavor); - goto done; - } + for (u = 0; u < ARRAY_LENGTH(srv_meth_pref_precompressed); ++u) { + compress_method_t method = srv_meth_pref_precompressed[u]; - { - char *fp = tor_malloc_zero(DIGEST_LEN); - if (flavor) - strlcpy(fp, flavor, DIGEST_LEN); - tor_free(flavor); - smartlist_add(dir_fps, fp); - } - lifetime = (v && v->fresh_until > now) ? v->fresh_until - now : 0; + if (0 == (compression_methods & (1u<<method))) + continue; - if (!smartlist_len(dir_fps)) { /* we failed to create/cache cp */ - write_http_status_line(conn, 503, "Network status object unavailable"); - smartlist_free(dir_fps); - geoip_note_ns_response(GEOIP_REJECT_UNAVAILABLE); - goto done; + if (consdiffmgr_find_consensus(&result, flav, + method) == CONSDIFF_AVAILABLE) { + tor_assert_nonfatal(result); + *compression_used_out = method; + return result; } + } - if (!dirserv_remove_old_statuses(dir_fps, if_modified_since)) { - write_http_status_line(conn, 404, "Not found"); - SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp)); - smartlist_free(dir_fps); - geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); - goto done; - } else if (!smartlist_len(dir_fps)) { - write_http_status_line(conn, 304, "Not modified"); - SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp)); - smartlist_free(dir_fps); - geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); - goto done; - } + if (consdiffmgr_find_consensus(&result, flav, + FALLBACK_COMPRESS_METHOD) == CONSDIFF_AVAILABLE) { + tor_assert_nonfatal(result); + *compression_used_out = FALLBACK_COMPRESS_METHOD; + return result; + } - size_t dlen = dirserv_estimate_data_size(dir_fps, 0, compressed); - if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) { - log_debug(LD_DIRSERV, - "Client asked for network status lists, but we've been " - "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); - SMARTLIST_FOREACH(dir_fps, char *, fp, tor_free(fp)); - smartlist_free(dir_fps); + return NULL; +} - geoip_note_ns_response(GEOIP_REJECT_BUSY); - goto done; +/** Try to find the best supported compression method possible from a given + * <b>compression_methods</b>. Return NO_METHOD if no mutually supported + * compression method could be found. */ +static compress_method_t +find_best_compression_method(unsigned compression_methods, int stream) +{ + unsigned u; + compress_method_t *methods; + size_t length; + + if (stream) { + methods = srv_meth_pref_streaming_compression; + length = ARRAY_LENGTH(srv_meth_pref_streaming_compression); + } else { + methods = srv_meth_pref_precompressed; + length = ARRAY_LENGTH(srv_meth_pref_precompressed); + } + + for (u = 0; u < length; ++u) { + compress_method_t method = methods[u]; + if (compression_methods & (1u<<method)) + return method; + } + + return NO_METHOD; +} + +/** Check if any of the digests in <b>digests</b> matches the latest consensus + * flavor (given in <b>flavor</b>) that we have available. */ +static int +digest_list_contains_best_consensus(consensus_flavor_t flavor, + const smartlist_t *digests) +{ + const networkstatus_t *ns = NULL; + + if (digests == NULL) + return 0; + + ns = networkstatus_get_latest_consensus_by_flavor(flavor); + + if (ns == NULL) + return 0; + + SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, digest) { + if (tor_memeq(ns->digest_sha3_as_signed, digest, DIGEST256_LEN)) + return 1; + } SMARTLIST_FOREACH_END(digest); + + return 0; +} + +/** Check if the given compression method is allowed for a connection that is + * supposed to be anonymous. Returns 1 if the compression method is allowed, + * otherwise 0. */ +STATIC int +allowed_anonymous_connection_compression_method(compress_method_t method) +{ + unsigned u; + + for (u = 0; u < ARRAY_LENGTH(client_meth_allowed_anonymous_compression); + ++u) { + compress_method_t allowed_method = + client_meth_allowed_anonymous_compression[u]; + + if (! tor_compress_supports_method(allowed_method)) + continue; + + if (method == allowed_method) + return 1; + } + + return 0; +} + +/** Log a warning when a remote server has sent us a document using a + * compression method that is not allowed for anonymous directory requests. */ +STATIC void +warn_disallowed_anonymous_compression_method(compress_method_t method) +{ + log_fn(LOG_PROTOCOL_WARN, LD_HTTP, + "Received a %s HTTP response, which is not " + "allowed for anonymous directory requests.", + compression_method_get_human_name(method)); +} + +/** Encodes the results of parsing a consensus request to figure out what + * consensus, and possibly what diffs, the user asked for. */ +typedef struct { + /** name of the flavor to retrieve. */ + char *flavor; + /** flavor to retrive, as enum. */ + consensus_flavor_t flav; + /** plus-separated list of authority fingerprints; see + * client_likes_consensus(). Aliases the URL in the request passed to + * parse_consensus_request(). */ + const char *want_fps; + /** Optionally, a smartlist of sha3 digests-as-signed of the consensuses + * to return a diff from. */ + smartlist_t *diff_from_digests; + /** If true, never send a full consensus. If there is no diff, send + * a 404 instead. */ + int diff_only; +} parsed_consensus_request_t; + +/** Remove all data held in <b>req</b>. Do not free <b>req</b> itself, since + * it is stack-allocated. */ +static void +parsed_consensus_request_clear(parsed_consensus_request_t *req) +{ + if (!req) + return; + tor_free(req->flavor); + if (req->diff_from_digests) { + SMARTLIST_FOREACH(req->diff_from_digests, uint8_t *, d, tor_free(d)); + smartlist_free(req->diff_from_digests); + } + memset(req, 0, sizeof(parsed_consensus_request_t)); +} + +/** + * Parse the URL and relevant headers of <b>args</b> for a current-consensus + * request to learn what flavor of consensus we want, what keys it must be + * signed with, and what diffs we would accept (or demand) instead. Return 0 + * on success and -1 on failure. + */ +static int +parse_consensus_request(parsed_consensus_request_t *out, + const get_handler_args_t *args) +{ + const char *url = args->url; + memset(out, 0, sizeof(parsed_consensus_request_t)); + out->flav = FLAV_NS; + + const char CONSENSUS_URL_PREFIX[] = "/tor/status-vote/current/consensus/"; + const char CONSENSUS_FLAVORED_PREFIX[] = + "/tor/status-vote/current/consensus-"; + + /* figure out the flavor if any, and who we wanted to sign the thing */ + const char *after_flavor = NULL; + + if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) { + const char *f, *cp; + f = url + strlen(CONSENSUS_FLAVORED_PREFIX); + cp = strchr(f, '/'); + if (cp) { + after_flavor = cp+1; + out->flavor = tor_strndup(f, cp-f); + } else { + out->flavor = tor_strdup(f); } + int flav = networkstatus_parse_flavor_name(out->flavor); + if (flav < 0) + flav = FLAV_NS; + out->flav = flav; + } else { + if (!strcmpstart(url, CONSENSUS_URL_PREFIX)) + after_flavor = url+strlen(CONSENSUS_URL_PREFIX); + } + + /* see whether we've been asked explicitly for a diff from an older + * consensus. (The user might also have said that a diff would be okay, + * via X-Or-Diff-From-Consensus */ + const char DIFF_COMPONENT[] = "diff/"; + char *diff_hash_in_url = NULL; + if (after_flavor && !strcmpstart(after_flavor, DIFF_COMPONENT)) { + after_flavor += strlen(DIFF_COMPONENT); + const char *cp = strchr(after_flavor, '/'); + if (cp) { + diff_hash_in_url = tor_strndup(after_flavor, cp-after_flavor); + out->want_fps = cp+1; + } else { + diff_hash_in_url = tor_strdup(after_flavor); + out->want_fps = NULL; + } + } else { + out->want_fps = after_flavor; + } + + if (diff_hash_in_url) { + uint8_t diff_from[DIGEST256_LEN]; + out->diff_from_digests = smartlist_new(); + out->diff_only = 1; + int ok = !parse_one_diff_hash(diff_from, diff_hash_in_url, "URL", + "rejecting"); + tor_free(diff_hash_in_url); + if (ok) { + smartlist_add(out->diff_from_digests, + tor_memdup(diff_from, DIGEST256_LEN)); + } else { + return -1; + } + } else { + parse_or_diff_from_header(&out->diff_from_digests, args->headers); + } - tor_addr_t addr; - if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) { - geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, - &addr, NULL, - time(NULL)); - geoip_note_ns_response(GEOIP_SUCCESS); - /* Note that a request for a network status has started, so that we - * can measure the download time later on. */ - if (conn->dirreq_id) - geoip_start_dirreq(conn->dirreq_id, dlen, DIRREQ_TUNNELED); - else - geoip_start_dirreq(TO_CONN(conn)->global_identifier, dlen, - DIRREQ_DIRECT); + return 0; +} + +/** Helper function for GET /tor/status-vote/current/consensus + */ +static int +handle_get_current_consensus(dir_connection_t *conn, + const get_handler_args_t *args) +{ + const compress_method_t compress_method = + find_best_compression_method(args->compression_supported, 0); + const time_t if_modified_since = args->if_modified_since; + int clear_spool = 0; + + /* v3 network status fetch. */ + long lifetime = NETWORKSTATUS_CACHE_LIFETIME; + + time_t now = time(NULL); + parsed_consensus_request_t req; + + if (parse_consensus_request(&req, args) < 0) { + write_http_status_line(conn, 404, "Couldn't parse request"); + goto done; + } + + if (digest_list_contains_best_consensus(req.flav, + req.diff_from_digests)) { + write_http_status_line(conn, 304, "Not modified"); + geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); + goto done; + } + + struct consensus_cache_entry_t *cached_consensus = NULL; + + compress_method_t compression_used = NO_METHOD; + if (req.diff_from_digests) { + cached_consensus = find_best_diff(req.diff_from_digests, req.flav, + args->compression_supported, + &compression_used); + } + + if (req.diff_only && !cached_consensus) { + write_http_status_line(conn, 404, "No such diff available"); + // XXXX warn_consensus_is_too_old(v, req.flavor, now); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + goto done; + } + + if (! cached_consensus) { + cached_consensus = find_best_consensus(req.flav, + args->compression_supported, + &compression_used); + } + + time_t fresh_until, valid_until; + int have_fresh_until = 0, have_valid_until = 0; + if (cached_consensus) { + have_fresh_until = + !consensus_cache_entry_get_fresh_until(cached_consensus, &fresh_until); + have_valid_until = + !consensus_cache_entry_get_valid_until(cached_consensus, &valid_until); + } + + if (cached_consensus && have_valid_until && + !networkstatus_valid_until_is_reasonably_live(valid_until, now)) { + write_http_status_line(conn, 404, "Consensus is too old"); + warn_consensus_is_too_old(cached_consensus, req.flavor, now); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + goto done; + } + + if (cached_consensus && req.want_fps && + !client_likes_consensus(cached_consensus, req.want_fps)) { + write_http_status_line(conn, 404, "Consensus not signed by sufficient " + "number of requested authorities"); + geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS); + goto done; + } + + conn->spool = smartlist_new(); + clear_spool = 1; + { + spooled_resource_t *spooled; + if (cached_consensus) { + spooled = spooled_resource_new_from_cache_entry(cached_consensus); + smartlist_add(conn->spool, spooled); } + } + + lifetime = (have_fresh_until && fresh_until > now) ? fresh_until - now : 0; - write_http_response_header(conn, -1, compressed, - smartlist_len(dir_fps) == 1 ? lifetime : 0); - conn->fingerprint_stack = dir_fps; - if (! compressed) - conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION); + size_t size_guess = 0; + int n_expired = 0; + dirserv_spool_remove_missing_and_guess_size(conn, if_modified_since, + compress_method != NO_METHOD, + &size_guess, + &n_expired); - /* Prime the connection with some data. */ - conn->dir_spool_src = DIR_SPOOL_NETWORKSTATUS; - connection_dirserv_flushed_some(conn); + if (!smartlist_len(conn->spool) && !n_expired) { + write_http_status_line(conn, 404, "Not found"); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + goto done; + } else if (!smartlist_len(conn->spool)) { + write_http_status_line(conn, 304, "Not modified"); + geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); goto done; } + if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { + log_debug(LD_DIRSERV, + "Client asked for network status lists, but we've been " + "writing too many bytes lately. Sending 503 Dir busy."); + write_http_status_line(conn, 503, "Directory busy, try again later"); + geoip_note_ns_response(GEOIP_REJECT_BUSY); + goto done; + } + + tor_addr_t addr; + if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) { + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, + &addr, NULL, + time(NULL)); + geoip_note_ns_response(GEOIP_SUCCESS); + /* Note that a request for a network status has started, so that we + * can measure the download time later on. */ + if (conn->dirreq_id) + geoip_start_dirreq(conn->dirreq_id, size_guess, DIRREQ_TUNNELED); + else + geoip_start_dirreq(TO_CONN(conn)->global_identifier, size_guess, + DIRREQ_DIRECT); + } + + /* Use this header to tell caches that the response depends on the + * X-Or-Diff-From-Consensus header (or lack thereof). */ + const char vary_header[] = "Vary: X-Or-Diff-From-Consensus\r\n"; + + clear_spool = 0; + + // The compress_method might have been NO_METHOD, but we store the data + // compressed. Decompress them using `compression_used`. See fallback code in + // find_best_consensus() and find_best_diff(). + write_http_response_headers(conn, -1, + compress_method == NO_METHOD ? + NO_METHOD : compression_used, + vary_header, + smartlist_len(conn->spool) == 1 ? lifetime : 0); + + if (compress_method == NO_METHOD && smartlist_len(conn->spool)) + conn->compress_state = tor_compress_new(0, compression_used, + HIGH_COMPRESSION); + + /* Prime the connection with some data. */ + const int initial_flush_result = connection_dirserv_flushed_some(conn); + tor_assert_nonfatal(initial_flush_result == 0); + goto done; + done: + parsed_consensus_request_clear(&req); + if (clear_spool) { + dir_conn_clear_spool(conn); + } return 0; } @@ -3163,12 +4256,14 @@ static int handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; { int current; ssize_t body_len = 0; ssize_t estimated_len = 0; + /* This smartlist holds strings that we can compress on the fly. */ smartlist_t *items = smartlist_new(); + /* This smartlist holds cached_dir_t objects that have a precompressed + * deflated version. */ smartlist_t *dir_items = smartlist_new(); int lifetime = 60; /* XXXX?? should actually use vote intervals. */ url += strlen("/tor/status-vote/"); @@ -3219,12 +4314,33 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) write_http_status_line(conn, 404, "Not found"); goto vote_done; } + + /* We're sending items from at most one kind of source */ + tor_assert_nonfatal(smartlist_len(items) == 0 || + smartlist_len(dir_items) == 0); + + int streaming; + unsigned mask; + if (smartlist_len(items)) { + /* We're taking strings and compressing them on the fly. */ + streaming = 1; + mask = ~0u; + } else { + /* We're taking cached_dir_t objects. We only have them uncompressed + * or deflated. */ + streaming = 0; + mask = (1u<<NO_METHOD) | (1u<<ZLIB_METHOD); + } + const compress_method_t compress_method = find_best_compression_method( + args->compression_supported&mask, streaming); + SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, - body_len += compressed ? d->dir_z_len : d->dir_len); + body_len += compress_method != NO_METHOD ? + d->dir_compressed_len : d->dir_len); estimated_len += body_len; SMARTLIST_FOREACH(items, const char *, item, { size_t ln = strlen(item); - if (compressed) { + if (compress_method != NO_METHOD) { estimated_len += ln/2; } else { body_len += ln; estimated_len += ln; @@ -3235,24 +4351,27 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) write_http_status_line(conn, 503, "Directory busy, try again later"); goto vote_done; } - write_http_response_header(conn, body_len ? body_len : -1, compressed, + write_http_response_header(conn, body_len ? body_len : -1, + compress_method, lifetime); if (smartlist_len(items)) { - if (compressed) { - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, - choose_compression_level(estimated_len)); + if (compress_method != NO_METHOD) { + conn->compress_state = tor_compress_new(1, compress_method, + choose_compression_level(estimated_len)); SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf_zlib(c, strlen(c), conn, 0)); - connection_write_to_buf_zlib("", 0, conn, 1); + connection_write_to_buf_compress(c, strlen(c), conn, 0)); + connection_write_to_buf_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(items, const char *, c, connection_write_to_buf(c, strlen(c), TO_CONN(conn))); } } else { SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, - connection_write_to_buf(compressed ? d->dir_z : d->dir, - compressed ? d->dir_z_len : d->dir_len, + connection_write_to_buf(compress_method != NO_METHOD ? + d->dir_compressed : d->dir, + compress_method != NO_METHOD ? + d->dir_compressed_len : d->dir_len, TO_CONN(conn))); } vote_done: @@ -3270,44 +4389,51 @@ static int handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; + const compress_method_t compress_method = + find_best_compression_method(args->compression_supported, 1); + int clear_spool = 1; { - smartlist_t *fps = smartlist_new(); + conn->spool = smartlist_new(); - dir_split_resource_into_fingerprints(url+strlen("/tor/micro/d/"), - fps, NULL, + dir_split_resource_into_spoolable(url+strlen("/tor/micro/d/"), + DIR_SPOOL_MICRODESC, + conn->spool, NULL, DSR_DIGEST256|DSR_BASE64|DSR_SORT_UNIQ); - if (!dirserv_have_any_microdesc(fps)) { + size_t size_guess = 0; + dirserv_spool_remove_missing_and_guess_size(conn, 0, + compress_method != NO_METHOD, + &size_guess, NULL); + if (smartlist_len(conn->spool) == 0) { write_http_status_line(conn, 404, "Not found"); - SMARTLIST_FOREACH(fps, char *, fp, tor_free(fp)); - smartlist_free(fps); goto done; } - size_t dlen = dirserv_estimate_microdesc_size(fps, compressed); - if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) { + if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); write_http_status_line(conn, 503, "Directory busy, try again later"); - SMARTLIST_FOREACH(fps, char *, fp, tor_free(fp)); - smartlist_free(fps); goto done; } - write_http_response_header(conn, -1, compressed, MICRODESC_CACHE_LIFETIME); - conn->dir_spool_src = DIR_SPOOL_MICRODESC; - conn->fingerprint_stack = fps; + clear_spool = 0; + write_http_response_header(conn, -1, + compress_method, + MICRODESC_CACHE_LIFETIME); - if (compressed) - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, - choose_compression_level(dlen)); + if (compress_method != NO_METHOD) + conn->compress_state = tor_compress_new(1, compress_method, + choose_compression_level(size_guess)); - connection_dirserv_flushed_some(conn); + const int initial_flush_result = connection_dirserv_flushed_some(conn); + tor_assert_nonfatal(initial_flush_result == 0); goto done; } done: + if (clear_spool) { + dir_conn_clear_spool(conn); + } return 0; } @@ -3317,71 +4443,92 @@ static int handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; + const compress_method_t compress_method = + find_best_compression_method(args->compression_supported, 1); const or_options_t *options = get_options(); + int clear_spool = 1; if (!strcmpstart(url,"/tor/server/") || (!options->BridgeAuthoritativeDir && !options->BridgeRelay && !strcmpstart(url,"/tor/extra/"))) { - size_t dlen; int res; - const char *msg; + const char *msg = NULL; int cache_lifetime = 0; int is_extra = !strcmpstart(url,"/tor/extra/"); url += is_extra ? strlen("/tor/extra/") : strlen("/tor/server/"); - conn->fingerprint_stack = smartlist_new(); - res = dirserv_get_routerdesc_fingerprints(conn->fingerprint_stack, url, - &msg, - !connection_dir_is_encrypted(conn), - is_extra); - - if (!strcmpstart(url, "fp/")) { - if (smartlist_len(conn->fingerprint_stack) == 1) - cache_lifetime = ROUTERDESC_CACHE_LIFETIME; - } else if (!strcmpstart(url, "authority")) { - cache_lifetime = ROUTERDESC_CACHE_LIFETIME; - } else if (!strcmpstart(url, "all")) { - cache_lifetime = FULL_DIR_CACHE_LIFETIME; - } else if (!strcmpstart(url, "d/")) { - if (smartlist_len(conn->fingerprint_stack) == 1) - cache_lifetime = ROUTERDESC_BY_DIGEST_CACHE_LIFETIME; - } - if (!strcmpstart(url, "d/")) - conn->dir_spool_src = + dir_spool_source_t source; + time_t publish_cutoff = 0; + if (!strcmpstart(url, "d/")) { + source = is_extra ? DIR_SPOOL_EXTRA_BY_DIGEST : DIR_SPOOL_SERVER_BY_DIGEST; - else - conn->dir_spool_src = + } else { + source = is_extra ? DIR_SPOOL_EXTRA_BY_FP : DIR_SPOOL_SERVER_BY_FP; + /* We only want to apply a publish cutoff when we're requesting + * resources by fingerprint. */ + publish_cutoff = time(NULL) - ROUTER_MAX_AGE_TO_PUBLISH; + } + + conn->spool = smartlist_new(); + res = dirserv_get_routerdesc_spool(conn->spool, url, + source, + connection_dir_is_encrypted(conn), + &msg); - if (!dirserv_have_any_serverdesc(conn->fingerprint_stack, - conn->dir_spool_src)) { - res = -1; - msg = "Not found"; + if (!strcmpstart(url, "all")) { + cache_lifetime = FULL_DIR_CACHE_LIFETIME; + } else if (smartlist_len(conn->spool) == 1) { + cache_lifetime = ROUTERDESC_BY_DIGEST_CACHE_LIFETIME; } - if (res < 0) + size_t size_guess = 0; + int n_expired = 0; + dirserv_spool_remove_missing_and_guess_size(conn, publish_cutoff, + compress_method != NO_METHOD, + &size_guess, &n_expired); + + /* If we are the bridge authority and the descriptor is a bridge + * descriptor, remember that we served this descriptor for desc stats. */ + /* XXXX it's a bit of a kludge to have this here. */ + if (get_options()->BridgeAuthoritativeDir && + source == DIR_SPOOL_SERVER_BY_FP) { + SMARTLIST_FOREACH_BEGIN(conn->spool, spooled_resource_t *, spooled) { + const routerinfo_t *router = + router_get_by_id_digest((const char *)spooled->digest); + /* router can be NULL here when the bridge auth is asked for its own + * descriptor. */ + if (router && router->purpose == ROUTER_PURPOSE_BRIDGE) + rep_hist_note_desc_served(router->cache_info.identity_digest); + } SMARTLIST_FOREACH_END(spooled); + } + + if (res < 0 || size_guess == 0 || smartlist_len(conn->spool) == 0) { + if (msg == NULL) + msg = "Not found"; write_http_status_line(conn, 404, msg); - else { - dlen = dirserv_estimate_data_size(conn->fingerprint_stack, - 1, compressed); - if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) { + } else { + if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); write_http_status_line(conn, 503, "Directory busy, try again later"); - conn->dir_spool_src = DIR_SPOOL_NONE; + dir_conn_clear_spool(conn); goto done; } - write_http_response_header(conn, -1, compressed, cache_lifetime); - if (compressed) - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, - choose_compression_level(dlen)); + write_http_response_header(conn, -1, compress_method, cache_lifetime); + if (compress_method != NO_METHOD) + conn->compress_state = tor_compress_new(1, compress_method, + choose_compression_level(size_guess)); + clear_spool = 0; /* Prime the connection with some data. */ - connection_dirserv_flushed_some(conn); + int initial_flush_result = connection_dirserv_flushed_some(conn); + tor_assert_nonfatal(initial_flush_result == 0); } goto done; } done: - return 0; + if (clear_spool) + dir_conn_clear_spool(conn); + return 0; } /** Helper function for GET /tor/keys/... @@ -3390,7 +4537,8 @@ static int handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; + const compress_method_t compress_method = + find_best_compression_method(args->compression_supported, 1); const time_t if_modified_since = args->if_modified_since; { smartlist_t *certs = smartlist_new(); @@ -3453,20 +4601,26 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) SMARTLIST_FOREACH(certs, authority_cert_t *, c, len += c->cache_info.signed_descriptor_len); - if (global_write_bucket_low(TO_CONN(conn), compressed?len/2:len, 2)) { + if (global_write_bucket_low(TO_CONN(conn), + compress_method != NO_METHOD ? len/2 : len, + 2)) { write_http_status_line(conn, 503, "Directory busy, try again later"); goto keys_done; } - write_http_response_header(conn, compressed?-1:len, compressed, 60*60); - if (compressed) { - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, - choose_compression_level(len)); + write_http_response_header(conn, + compress_method != NO_METHOD ? -1 : len, + compress_method, + 60*60); + if (compress_method != NO_METHOD) { + conn->compress_state = tor_compress_new(1, compress_method, + choose_compression_level(len)); SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf_zlib(c->cache_info.signed_descriptor_body, - c->cache_info.signed_descriptor_len, - conn, 0)); - connection_write_to_buf_zlib("", 0, conn, 1); + connection_write_to_buf_compress( + c->cache_info.signed_descriptor_body, + c->cache_info.signed_descriptor_len, + conn, 0)); + connection_write_to_buf_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(certs, authority_cert_t *, c, connection_write_to_buf(c->cache_info.signed_descriptor_body, @@ -3497,7 +4651,7 @@ handle_get_hs_descriptor_v2(dir_connection_t *conn, safe_str(escaped(query))); switch (rend_cache_lookup_v2_desc_as_dir(query, &descp)) { case 1: /* valid */ - write_http_response_header(conn, strlen(descp), 0, 0); + write_http_response_header(conn, strlen(descp), NO_METHOD, 0); connection_write_to_buf(descp, strlen(descp), TO_CONN(conn)); break; case 0: /* well-formed but not present */ @@ -3549,7 +4703,7 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, } /* Found requested descriptor! Pass it to this nice client. */ - write_http_response_header(conn, strlen(desc_str), 0, 0); + write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0); connection_write_to_buf(desc_str, strlen(desc_str), TO_CONN(conn)); done: @@ -3588,7 +4742,7 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, /* all happy now. send an answer. */ status = networkstatus_getinfo_by_purpose("bridge", time(NULL)); size_t dlen = strlen(status); - write_http_response_header(conn, dlen, 0, 0); + write_http_response_header(conn, dlen, NO_METHOD, 0); connection_write_to_buf(status, dlen, TO_CONN(conn)); tor_free(status); goto done; @@ -3605,7 +4759,7 @@ handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args) { const char robots[] = "User-agent: *\r\nDisallow: /\r\n"; size_t len = strlen(robots); - write_http_response_header(conn, len, 0, ROBOTS_CACHE_LIFETIME); + write_http_response_header(conn, len, NO_METHOD, ROBOTS_CACHE_LIFETIME); connection_write_to_buf(robots, len, TO_CONN(conn)); } return 0; @@ -3728,7 +4882,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, if (connection_dir_is_encrypted(conn) && !strcmpstart(url,"/tor/rendezvous2/publish")) { if (rend_cache_store_v2_desc_as_dir(body) < 0) { - log_warn(LD_REND, "Rejected v2 rend descriptor (length %d) from %s.", + log_warn(LD_REND, "Rejected v2 rend descriptor (body size %d) from %s.", (int)body_len, conn->base_.address); write_http_status_line(conn, 400, "Invalid v2 service descriptor rejected"); @@ -3771,14 +4925,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, conn->base_.address, &msg); tor_assert(msg); - if (r == ROUTER_ADDED_NOTIFY_GENERATOR) { - /* Accepted with a message. */ - log_info(LD_DIRSERV, - "Problematic router descriptor or extra-info from %s " - "(\"%s\").", - conn->base_.address, msg); - write_http_status_line(conn, 400, msg); - } else if (r == ROUTER_ADDED_SUCCESSFULLY) { + if (r == ROUTER_ADDED_SUCCESSFULLY) { write_http_status_line(conn, 200, msg); } else if (WRA_WAS_OUTDATED(r)) { write_http_response_header_impl(conn, -1, NULL, NULL, @@ -3905,7 +5052,7 @@ connection_dir_finished_flushing(dir_connection_t *conn) conn->base_.state = DIR_CONN_STATE_CLIENT_READING; return 0; case DIR_CONN_STATE_SERVER_WRITING: - if (conn->dir_spool_src != DIR_SPOOL_NONE) { + if (conn->spool) { log_warn(LD_BUG, "Emptied a dirserv buffer, but it's still spooling!"); connection_mark_for_close(TO_CONN(conn)); } else { @@ -4399,13 +5546,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); @@ -4416,17 +5564,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); } @@ -4588,3 +5745,34 @@ dir_split_resource_into_fingerprints(const char *resource, return 0; } +/** As dir_split_resource_into_fingerprints, but instead fills + * <b>spool_out</b> with a list of spoolable_resource_t for the resource + * identified through <b>source</b>. */ +int +dir_split_resource_into_spoolable(const char *resource, + dir_spool_source_t source, + smartlist_t *spool_out, + int *compressed_out, + int flags) +{ + smartlist_t *fingerprints = smartlist_new(); + + tor_assert(flags & (DSR_HEX|DSR_BASE64)); + const size_t digest_len = + (flags & DSR_DIGEST256) ? DIGEST256_LEN : DIGEST_LEN; + + int r = dir_split_resource_into_fingerprints(resource, fingerprints, + compressed_out, flags); + /* This is not a very efficient implementation XXXX */ + SMARTLIST_FOREACH_BEGIN(fingerprints, uint8_t *, digest) { + spooled_resource_t *spooled = + spooled_resource_new(source, digest, digest_len); + if (spooled) + smartlist_add(spool_out, spooled); + tor_free(digest); + } SMARTLIST_FOREACH_END(digest); + + smartlist_free(fingerprints); + return r; +} + diff --git a/src/or/directory.h b/src/or/directory.h index 1459c3bbdb..571c30a0fc 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -41,46 +41,53 @@ typedef enum { int directory_must_use_begindir(const or_options_t *options); -MOCK_DECL(void, directory_initiate_command_routerstatus, - (const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - struct circuit_guard_state_t *guard_state)); - -void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - const rend_data_t *rend_query, - struct circuit_guard_state_t *guard_state); +/** + * A directory_request_t describes the information about a directory request + * at the client side. It describes what we're going to ask for, which + * directory we're going to ask for it, how we're going to contact that + * directory, and (in some cases) what to do with it when we're done. + */ +typedef struct directory_request_t directory_request_t; +directory_request_t *directory_request_new(uint8_t dir_purpose); +void directory_request_free(directory_request_t *req); +void directory_request_set_or_addr_port(directory_request_t *req, + const tor_addr_port_t *p); +void directory_request_set_dir_addr_port(directory_request_t *req, + const tor_addr_port_t *p); +void directory_request_set_directory_id_digest(directory_request_t *req, + const char *digest); +void directory_request_set_guard_state(directory_request_t *req, + struct circuit_guard_state_t *state); +void directory_request_set_router_purpose(directory_request_t *req, + uint8_t router_purpose); +void directory_request_set_indirection(directory_request_t *req, + dir_indirection_t indirection); +void directory_request_set_resource(directory_request_t *req, + const char *resource); +void directory_request_set_payload(directory_request_t *req, + const char *payload, + size_t payload_len); +void directory_request_set_if_modified_since(directory_request_t *req, + time_t if_modified_since); +void directory_request_set_rend_query(directory_request_t *req, + const rend_data_t *query); + +void directory_request_set_routerstatus(directory_request_t *req, + const routerstatus_t *rs); +void directory_request_add_header(directory_request_t *req, + const char *key, + const char *val); +MOCK_DECL(void, directory_initiate_request, (directory_request_t *request)); int parse_http_response(const char *headers, int *code, time_t *date, compress_method_t *compression, char **response); -int connection_dir_is_encrypted(dir_connection_t *conn); +int connection_dir_is_encrypted(const dir_connection_t *conn); int connection_dir_reached_eof(dir_connection_t *conn); int connection_dir_process_inbuf(dir_connection_t *conn); int connection_dir_finished_flushing(dir_connection_t *conn); int connection_dir_finished_connecting(dir_connection_t *conn); void connection_dir_about_to_close(dir_connection_t *dir_conn); -void directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, - const tor_addr_t *dir_addr, uint16_t dir_port, - const char *digest, - uint8_t dir_purpose, uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, size_t payload_len, - time_t if_modified_since, - struct circuit_guard_state_t *guard_state); #define DSR_HEX (1<<0) #define DSR_BASE64 (1<<1) @@ -89,7 +96,12 @@ void directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, int dir_split_resource_into_fingerprints(const char *resource, smartlist_t *fp_out, int *compressed_out, int flags); - +enum dir_spool_source_t; +int dir_split_resource_into_spoolable(const char *resource, + enum dir_spool_source_t source, + smartlist_t *spool_out, + int *compressed_out, + int flags); int dir_split_resource_into_fingerprint_pairs(const char *res, smartlist_t *pairs_out); char *directory_dump_request_log(void); @@ -150,8 +162,16 @@ struct get_handler_args_t; STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, const struct get_handler_args_t *args); STATIC int directory_handle_command(dir_connection_t *conn); +STATIC char *accept_encoding_header(void); +STATIC int allowed_anonymous_connection_compression_method(compress_method_t); +STATIC void warn_disallowed_anonymous_compression_method(compress_method_t); -#endif +struct response_handler_args_t; + +STATIC int handle_response_fetch_microdesc(dir_connection_t *conn, + const struct response_handler_args_t *args); + +#endif /* defined(DIRECTORY_PRIVATE) */ #ifdef TOR_UNIT_TESTS /* Used only by test_dir.c */ @@ -177,7 +197,7 @@ STATIC int handle_post_hs_descriptor(const char *url, const char *body); STATIC char* authdir_type_to_string(dirinfo_type_t auth); STATIC const char * dir_conn_purpose_to_string(int purpose); STATIC int should_use_directory_guards(const or_options_t *options); -STATIC zlib_compression_level_t choose_compression_level(ssize_t n_bytes); +STATIC compression_level_t choose_compression_level(ssize_t n_bytes); STATIC const smartlist_t *find_dl_schedule(download_status_t *dls, const or_options_t *options); STATIC void find_dl_min_and_max_delay(download_status_t *dls, @@ -188,6 +208,7 @@ STATIC int next_random_exponential_delay(int delay, int max_delay); STATIC int parse_hs_version_from_post(const char *url, const char *prefix, const char **end_pos); +STATIC unsigned parse_accept_encoding_header(const char *h); #endif #endif diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 0e8a534eaf..acd00322a1 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRSERV_PRIVATE @@ -13,6 +13,8 @@ #include "command.h" #include "connection.h" #include "connection_or.h" +#include "conscache.h" +#include "consdiffmgr.h" #include "control.h" #include "directory.h" #include "dirserv.h" @@ -81,14 +83,23 @@ dirserv_get_status_impl(const char *fp, const char *nickname, int severity); static void clear_cached_dir(cached_dir_t *d); static const signed_descriptor_t *get_signed_descriptor_by_fp( - const char *fp, - int extrainfo, - time_t publish_cutoff); + const uint8_t *fp, + int extrainfo); static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei, const char **msg); static uint32_t dirserv_get_bandwidth_for_router_kb(const routerinfo_t *ri); static uint32_t dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri); +static int spooled_resource_lookup_body(const spooled_resource_t *spooled, + int conn_is_encrypted, + const uint8_t **body_out, + size_t *size_out, + time_t *published_out); +static cached_dir_t *spooled_resource_lookup_cached_dir( + const spooled_resource_t *spooled, + time_t *published_out); +static cached_dir_t *lookup_cached_dir_by_fp(const uint8_t *fp); + /************** Fingerprint handling code ************/ /* 1 Historically used to indicate Named */ @@ -577,6 +588,8 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose, !general ? router_purpose_to_string(purpose) : "", !general ? "\n" : "")<0) { *msg = "Couldn't format annotations"; + /* XXX Not cool: we return -1 below, but (was_router_added_t)-1 is + * ROUTER_BAD_EI, which isn't what's gone wrong here. :( */ return -1; } @@ -707,7 +720,12 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) log_info(LD_DIRSERV, "Dropping descriptor from %s (source: %s) because " "its key did not match an older RSA/Ed25519 keypair", router_describe(ri), source); - *msg = "Looks like your keypair does not match its older value."; + *msg = "Looks like your keypair has changed? This authority previously " + "recorded a different RSA identity for this Ed25519 identity (or vice " + "versa.) Did you replace or copy some of your key files, but not " + "the others? You should either restore the expected keypair, or " + "delete your keys and restart Tor to start your relay with a new " + "identity."; r = ROUTER_AUTHDIR_REJECTS; goto fail; } @@ -867,6 +885,9 @@ directory_remove_invalid(void) * Allocate and return a description of the status of the server <b>desc</b>, * for use in a v1-style router-status line. The server is listed * as running iff <b>is_live</b> is true. + * + * This is deprecated: it's only used for controllers that want outputs in + * the old format. */ static char * list_single_server_status(const routerinfo_t *desc, int is_live) @@ -979,6 +1000,9 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) * *<b>router_status_out</b>. Return 0 on success, -1 on failure. * * If for_controller is true, include the routers with very old descriptors. + * + * This is deprecated: it's only used for controllers that want outputs in + * the old format. */ int list_server_status_v1(smartlist_t *routers, char **router_status_out, @@ -1214,8 +1238,8 @@ new_cached_dir(char *s, time_t published) d->dir = s; d->dir_len = strlen(s); d->published = published; - if (tor_gzip_compress(&(d->dir_z), &(d->dir_z_len), d->dir, d->dir_len, - ZLIB_METHOD)) { + if (tor_compress(&(d->dir_compressed), &(d->dir_compressed_len), + d->dir, d->dir_len, ZLIB_METHOD)) { log_warn(LD_BUG, "Error compressing directory"); } return d; @@ -1226,7 +1250,7 @@ static void clear_cached_dir(cached_dir_t *d) { tor_free(d->dir); - tor_free(d->dir_z); + tor_free(d->dir_compressed); memset(d, 0, sizeof(cached_dir_t)); } @@ -1249,6 +1273,7 @@ void dirserv_set_cached_consensus_networkstatus(const char *networkstatus, const char *flavor_name, const common_digests_t *digests, + const uint8_t *sha3_as_signed, time_t published) { cached_dir_t *new_networkstatus; @@ -1258,6 +1283,8 @@ dirserv_set_cached_consensus_networkstatus(const char *networkstatus, new_networkstatus = new_cached_dir(tor_strdup(networkstatus), published); memcpy(&new_networkstatus->digests, digests, sizeof(common_digests_t)); + memcpy(&new_networkstatus->digest_sha3_as_signed, sha3_as_signed, + DIGEST256_LEN); old_networkstatus = strmap_set(cached_consensuses, flavor_name, new_networkstatus); if (old_networkstatus) @@ -3090,58 +3117,61 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, * requests, adds identity digests. */ int -dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, - const char **msg, int for_unencrypted_conn, - int is_extrainfo) +dirserv_get_routerdesc_spool(smartlist_t *spool_out, + const char *key, + dir_spool_source_t source, + int conn_is_encrypted, + const char **msg_out) { - int by_id = 1; - *msg = NULL; + *msg_out = NULL; if (!strcmp(key, "all")) { - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, - smartlist_add(fps_out, - tor_memdup(r->cache_info.identity_digest, DIGEST_LEN))); - /* Treat "all" requests as if they were unencrypted */ - for_unencrypted_conn = 1; + const routerlist_t *rl = router_get_routerlist(); + SMARTLIST_FOREACH_BEGIN(rl->routers, const routerinfo_t *, r) { + spooled_resource_t *spooled; + spooled = spooled_resource_new(source, + (const uint8_t *)r->cache_info.identity_digest, + DIGEST_LEN); + /* Treat "all" requests as if they were unencrypted */ + conn_is_encrypted = 0; + smartlist_add(spool_out, spooled); + } SMARTLIST_FOREACH_END(r); } else if (!strcmp(key, "authority")) { const routerinfo_t *ri = router_get_my_routerinfo(); if (ri) - smartlist_add(fps_out, - tor_memdup(ri->cache_info.identity_digest, DIGEST_LEN)); + smartlist_add(spool_out, + spooled_resource_new(source, + (const uint8_t *)ri->cache_info.identity_digest, + DIGEST_LEN)); } else if (!strcmpstart(key, "d/")) { - by_id = 0; key += strlen("d/"); - dir_split_resource_into_fingerprints(key, fps_out, NULL, - DSR_HEX|DSR_SORT_UNIQ); + dir_split_resource_into_spoolable(key, source, spool_out, NULL, + DSR_HEX|DSR_SORT_UNIQ); } else if (!strcmpstart(key, "fp/")) { key += strlen("fp/"); - dir_split_resource_into_fingerprints(key, fps_out, NULL, - DSR_HEX|DSR_SORT_UNIQ); + dir_split_resource_into_spoolable(key, source, spool_out, NULL, + DSR_HEX|DSR_SORT_UNIQ); } else { - *msg = "Key not recognized"; + *msg_out = "Not found"; return -1; } - if (for_unencrypted_conn) { + if (! conn_is_encrypted) { /* Remove anything that insists it not be sent unencrypted. */ - SMARTLIST_FOREACH_BEGIN(fps_out, char *, cp) { - const signed_descriptor_t *sd; - if (by_id) - sd = get_signed_descriptor_by_fp(cp,is_extrainfo,0); - else if (is_extrainfo) - sd = extrainfo_get_by_descriptor_digest(cp); - else - sd = router_get_by_descriptor_digest(cp); - if (sd && !sd->send_unencrypted) { - tor_free(cp); - SMARTLIST_DEL_CURRENT(fps_out, cp); - } - } SMARTLIST_FOREACH_END(cp); + SMARTLIST_FOREACH_BEGIN(spool_out, spooled_resource_t *, spooled) { + const uint8_t *body = NULL; + size_t bodylen = 0; + int r = spooled_resource_lookup_body(spooled, conn_is_encrypted, + &body, &bodylen, NULL); + if (r < 0 || body == NULL || bodylen == 0) { + SMARTLIST_DEL_CURRENT(spool_out, spooled); + spooled_resource_free(spooled); + } + } SMARTLIST_FOREACH_END(spooled); } - if (!smartlist_len(fps_out)) { - *msg = "Servers unavailable"; + if (!smartlist_len(spool_out)) { + *msg_out = "Servers unavailable"; return -1; } return 0; @@ -3405,416 +3435,502 @@ dirserv_test_reachability(time_t now) ctr = (ctr + 1) % REACHABILITY_MODULO_PER_TEST; /* increment ctr */ } -/** Given a fingerprint <b>fp</b> which is either set if we're looking for a - * v2 status, or zeroes if we're looking for a v3 status, or a NUL-padded - * flavor name if we want a flavored v3 status, return a pointer to the - * appropriate cached dir object, or NULL if there isn't one available. */ -static cached_dir_t * -lookup_cached_dir_by_fp(const char *fp) +/* ========== + * Spooling code. + * ========== */ + +spooled_resource_t * +spooled_resource_new(dir_spool_source_t source, + const uint8_t *digest, size_t digestlen) { - cached_dir_t *d = NULL; - if (tor_digest_is_zero(fp) && cached_consensuses) { - d = strmap_get(cached_consensuses, "ns"); - } else if (memchr(fp, '\0', DIGEST_LEN) && cached_consensuses && - (d = strmap_get(cached_consensuses, fp))) { - /* this here interface is a nasty hack XXXX */; + spooled_resource_t *spooled = tor_malloc_zero(sizeof(spooled_resource_t)); + spooled->spool_source = source; + switch (source) { + case DIR_SPOOL_NETWORKSTATUS: + spooled->spool_eagerly = 0; + break; + case DIR_SPOOL_SERVER_BY_DIGEST: + case DIR_SPOOL_SERVER_BY_FP: + case DIR_SPOOL_EXTRA_BY_DIGEST: + case DIR_SPOOL_EXTRA_BY_FP: + case DIR_SPOOL_MICRODESC: + default: + spooled->spool_eagerly = 1; + break; + case DIR_SPOOL_CONSENSUS_CACHE_ENTRY: + tor_assert_unreached(); + break; } - return d; + tor_assert(digestlen <= sizeof(spooled->digest)); + if (digest) + memcpy(spooled->digest, digest, digestlen); + return spooled; } -/** Remove from <b>fps</b> every networkstatus key where both - * a) we have a networkstatus document and - * b) it is not newer than <b>cutoff</b>. +/** + * Create a new spooled_resource_t to spool the contents of <b>entry</b> to + * the user. Return the spooled object on success, or NULL on failure (which + * is probably caused by a failure to map the body of the item from disk). * - * Return 1 if any items were present at all; else return 0. + * Adds a reference to entry's reference counter. */ -int -dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff) -{ - int found_any = 0; - SMARTLIST_FOREACH_BEGIN(fps, char *, digest) { - cached_dir_t *d = lookup_cached_dir_by_fp(digest); - if (!d) - continue; - found_any = 1; - if (d->published <= cutoff) { - tor_free(digest); - SMARTLIST_DEL_CURRENT(fps, digest); - } - } SMARTLIST_FOREACH_END(digest); - - return found_any; -} - -/** Return the cache-info for identity fingerprint <b>fp</b>, or - * its extra-info document if <b>extrainfo</b> is true. Return - * NULL if not found or if the descriptor is older than - * <b>publish_cutoff</b>. */ -static const signed_descriptor_t * -get_signed_descriptor_by_fp(const char *fp, int extrainfo, - time_t publish_cutoff) -{ - if (router_digest_is_me(fp)) { - if (extrainfo) - return &(router_get_my_extrainfo()->cache_info); - else - return &(router_get_my_routerinfo()->cache_info); +spooled_resource_t * +spooled_resource_new_from_cache_entry(consensus_cache_entry_t *entry) +{ + spooled_resource_t *spooled = tor_malloc_zero(sizeof(spooled_resource_t)); + spooled->spool_source = DIR_SPOOL_CONSENSUS_CACHE_ENTRY; + spooled->spool_eagerly = 0; + consensus_cache_entry_incref(entry); + spooled->consensus_cache_entry = entry; + + int r = consensus_cache_entry_get_body(entry, + &spooled->cce_body, + &spooled->cce_len); + if (r == 0) { + return spooled; } else { - const routerinfo_t *ri = router_get_by_id_digest(fp); - if (ri && - ri->cache_info.published_on > publish_cutoff) { - if (extrainfo) - return extrainfo_get_by_descriptor_digest( - ri->cache_info.extra_info_digest); - else - return &ri->cache_info; - } + spooled_resource_free(spooled); + return NULL; } - return NULL; } -/** Return true iff we have any of the documents (extrainfo or routerdesc) - * specified by the fingerprints in <b>fps</b> and <b>spool_src</b>. Used to - * decide whether to send a 404. */ -int -dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src) -{ - time_t publish_cutoff = time(NULL)-ROUTER_MAX_AGE_TO_PUBLISH; - SMARTLIST_FOREACH_BEGIN(fps, const char *, fp) { - switch (spool_src) - { - case DIR_SPOOL_EXTRA_BY_DIGEST: - if (extrainfo_get_by_descriptor_digest(fp)) return 1; - break; - case DIR_SPOOL_SERVER_BY_DIGEST: - if (router_get_by_descriptor_digest(fp)) return 1; - break; - case DIR_SPOOL_EXTRA_BY_FP: - case DIR_SPOOL_SERVER_BY_FP: - if (get_signed_descriptor_by_fp(fp, - spool_src == DIR_SPOOL_EXTRA_BY_FP, publish_cutoff)) - return 1; - break; - } - } SMARTLIST_FOREACH_END(fp); - return 0; -} - -/** Return true iff any of the 256-bit elements in <b>fps</b> is the digest of - * a microdescriptor we have. */ -int -dirserv_have_any_microdesc(const smartlist_t *fps) +/** Release all storage held by <b>spooled</b>. */ +void +spooled_resource_free(spooled_resource_t *spooled) { - microdesc_cache_t *cache = get_microdesc_cache(); - SMARTLIST_FOREACH(fps, const char *, fp, - if (microdesc_cache_lookup_by_digest256(cache, fp)) - return 1); - return 0; -} + if (spooled == NULL) + return; -/** Return an approximate estimate of the number of bytes that will - * be needed to transmit the server descriptors (if is_serverdescs -- - * they can be either d/ or fp/ queries) or networkstatus objects (if - * !is_serverdescs) listed in <b>fps</b>. If <b>compressed</b> is set, - * we guess how large the data will be after compression. - * - * The return value is an estimate; it might be larger or smaller. - **/ -size_t -dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs, - int compressed) -{ - size_t result; - tor_assert(fps); - if (is_serverdescs) { - int n = smartlist_len(fps); - const routerinfo_t *me = router_get_my_routerinfo(); - result = (me?me->cache_info.signed_descriptor_len:2048) * n; - if (compressed) - result /= 2; /* observed compressibility is between 35 and 55%. */ - } else { - result = 0; - SMARTLIST_FOREACH(fps, const char *, digest, { - cached_dir_t *dir = lookup_cached_dir_by_fp(digest); - if (dir) - result += compressed ? dir->dir_z_len : dir->dir_len; - }); + if (spooled->cached_dir_ref) { + cached_dir_decref(spooled->cached_dir_ref); } - return result; + + if (spooled->consensus_cache_entry) { + consensus_cache_entry_decref(spooled->consensus_cache_entry); + } + + tor_free(spooled); } -/** Given a list of microdescriptor hashes, guess how many bytes will be - * needed to transmit them, and return the guess. */ -size_t -dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed) +/** When spooling data from a cached_dir_t object, we always add + * at least this much. */ +#define DIRSERV_CACHED_DIR_CHUNK_SIZE 8192 + +/** Return an compression ratio for compressing objects from <b>source</b>. + */ +static double +estimate_compression_ratio(dir_spool_source_t source) { - size_t result = smartlist_len(fps) * microdesc_average_size(NULL); - if (compressed) - result /= 2; - return result; + /* We should put in better estimates here, depending on the number of + objects and their type */ + (void) source; + return 0.5; } -/** When we're spooling data onto our outbuf, add more whenever we dip - * below this threshold. */ -#define DIRSERV_BUFFER_MIN 16384 +/** Return an estimated number of bytes needed for transmitting the + * resource in <b>spooled</b> on <b>conn</b> + * + * As a convenient side-effect, set *<b>published_out</b> to the resource's + * publication time. + */ +static size_t +spooled_resource_estimate_size(const spooled_resource_t *spooled, + dir_connection_t *conn, + int compressed, + time_t *published_out) +{ + if (spooled->spool_eagerly) { + const uint8_t *body = NULL; + size_t bodylen = 0; + int r = spooled_resource_lookup_body(spooled, + connection_dir_is_encrypted(conn), + &body, &bodylen, + published_out); + if (r == -1 || body == NULL || bodylen == 0) + return 0; + if (compressed) { + double ratio = estimate_compression_ratio(spooled->spool_source); + bodylen = (size_t)(bodylen * ratio); + } + return bodylen; + } else { + cached_dir_t *cached; + if (spooled->consensus_cache_entry) { + if (published_out) { + consensus_cache_entry_get_valid_after( + spooled->consensus_cache_entry, published_out); + } -/** Spooling helper: called when we have no more data to spool to <b>conn</b>. - * Flushes any remaining data to be (un)compressed, and changes the spool - * source to NONE. Returns 0 on success, negative on failure. */ -static int -connection_dirserv_finish_spooling(dir_connection_t *conn) -{ - if (conn->zlib_state) { - connection_write_to_buf_zlib("", 0, conn, 1); - tor_zlib_free(conn->zlib_state); - conn->zlib_state = NULL; + return spooled->cce_len; + } + if (spooled->cached_dir_ref) { + cached = spooled->cached_dir_ref; + } else { + cached = spooled_resource_lookup_cached_dir(spooled, + published_out); + } + if (cached == NULL) { + return 0; + } + size_t result = compressed ? cached->dir_compressed_len : cached->dir_len; + return result; } - conn->dir_spool_src = DIR_SPOOL_NONE; - return 0; } -/** Spooling helper: called when we're sending a bunch of server descriptors, - * and the outbuf has become too empty. Pulls some entries from - * fingerprint_stack, and writes the corresponding servers onto outbuf. If we - * run out of entries, flushes the zlib state and sets the spool source to - * NONE. Returns 0 on success, negative on failure. +/** Return code for spooled_resource_flush_some */ +typedef enum { + SRFS_ERR = -1, + SRFS_MORE = 0, + SRFS_DONE +} spooled_resource_flush_status_t; + +/** Flush some or all of the bytes from <b>spooled</b> onto <b>conn</b>. + * Return SRFS_ERR on error, SRFS_MORE if there are more bytes to flush from + * this spooled resource, or SRFS_DONE if we are done flushing this spooled + * resource. */ -static int -connection_dirserv_add_servers_to_outbuf(dir_connection_t *conn) -{ - int by_fp = (conn->dir_spool_src == DIR_SPOOL_SERVER_BY_FP || - conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_FP); - int extra = (conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_FP || - conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_DIGEST); - time_t publish_cutoff = time(NULL)-ROUTER_MAX_AGE_TO_PUBLISH; +static spooled_resource_flush_status_t +spooled_resource_flush_some(spooled_resource_t *spooled, + dir_connection_t *conn) +{ + if (spooled->spool_eagerly) { + /* Spool_eagerly resources are sent all-at-once. */ + const uint8_t *body = NULL; + size_t bodylen = 0; + int r = spooled_resource_lookup_body(spooled, + connection_dir_is_encrypted(conn), + &body, &bodylen, NULL); + if (r == -1 || body == NULL || bodylen == 0) { + /* Absent objects count as "done". */ + return SRFS_DONE; + } + if (conn->compress_state) { + connection_write_to_buf_compress((const char*)body, bodylen, conn, 0); + } else { + connection_write_to_buf((const char*)body, bodylen, TO_CONN(conn)); + } + return SRFS_DONE; + } else { + cached_dir_t *cached = spooled->cached_dir_ref; + consensus_cache_entry_t *cce = spooled->consensus_cache_entry; + if (cached == NULL && cce == NULL) { + /* The cached_dir_t hasn't been materialized yet. So let's look it up. */ + cached = spooled->cached_dir_ref = + spooled_resource_lookup_cached_dir(spooled, NULL); + if (!cached) { + /* Absent objects count as done. */ + return SRFS_DONE; + } + ++cached->refcnt; + tor_assert_nonfatal(spooled->cached_dir_offset == 0); + } - const or_options_t *options = get_options(); + if (BUG(!cached && !cce)) + return SRFS_DONE; - while (smartlist_len(conn->fingerprint_stack) && - connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { - const char *body; - char *fp = smartlist_pop_last(conn->fingerprint_stack); - const signed_descriptor_t *sd = NULL; - if (by_fp) { - sd = get_signed_descriptor_by_fp(fp, extra, publish_cutoff); + int64_t total_len; + const char *ptr; + if (cached) { + total_len = cached->dir_compressed_len; + ptr = cached->dir_compressed; } else { - sd = extra ? extrainfo_get_by_descriptor_digest(fp) - : router_get_by_descriptor_digest(fp); + total_len = spooled->cce_len; + ptr = (const char *)spooled->cce_body; } - tor_free(fp); - if (!sd) - continue; - if (!connection_dir_is_encrypted(conn) && !sd->send_unencrypted) { - /* we did this check once before (so we could have an accurate size - * estimate and maybe send a 404 if somebody asked for only bridges on a - * connection), but we need to do it again in case a previously - * unknown bridge descriptor has shown up between then and now. */ - continue; + /* How many bytes left to flush? */ + int64_t remaining; + remaining = total_len - spooled->cached_dir_offset; + if (BUG(remaining < 0)) + return SRFS_ERR; + ssize_t bytes = (ssize_t) MIN(DIRSERV_CACHED_DIR_CHUNK_SIZE, remaining); + if (conn->compress_state) { + connection_write_to_buf_compress( + ptr + spooled->cached_dir_offset, + bytes, conn, 0); + } else { + connection_write_to_buf(ptr + spooled->cached_dir_offset, + bytes, TO_CONN(conn)); } - - /** If we are the bridge authority and the descriptor is a bridge - * descriptor, remember that we served this descriptor for desc stats. */ - if (options->BridgeAuthoritativeDir && by_fp) { - const routerinfo_t *router = - router_get_by_id_digest(sd->identity_digest); - /* router can be NULL here when the bridge auth is asked for its own - * descriptor. */ - if (router && router->purpose == ROUTER_PURPOSE_BRIDGE) - rep_hist_note_desc_served(sd->identity_digest); - } - body = signed_descriptor_get_body(sd); - if (conn->zlib_state) { - int last = ! smartlist_len(conn->fingerprint_stack); - connection_write_to_buf_zlib(body, sd->signed_descriptor_len, conn, - last); - if (last) { - tor_zlib_free(conn->zlib_state); - conn->zlib_state = NULL; - } + spooled->cached_dir_offset += bytes; + if (spooled->cached_dir_offset >= (off_t)total_len) { + return SRFS_DONE; } else { - connection_write_to_buf(body, - sd->signed_descriptor_len, - TO_CONN(conn)); + return SRFS_MORE; } } +} - if (!smartlist_len(conn->fingerprint_stack)) { - /* We just wrote the last one; finish up. */ - if (conn->zlib_state) { - connection_write_to_buf_zlib("", 0, conn, 1); - tor_zlib_free(conn->zlib_state); - conn->zlib_state = NULL; - } - conn->dir_spool_src = DIR_SPOOL_NONE; - smartlist_free(conn->fingerprint_stack); - conn->fingerprint_stack = NULL; +/** Helper: find the cached_dir_t for a spooled_resource_t, for + * sending it to <b>conn</b>. Set *<b>published_out</b>, if provided, + * to the published time of the cached_dir_t. + * + * DOES NOT increase the reference count on the result. Callers must do that + * themselves if they mean to hang on to it. + */ +static cached_dir_t * +spooled_resource_lookup_cached_dir(const spooled_resource_t *spooled, + time_t *published_out) +{ + tor_assert(spooled->spool_eagerly == 0); + cached_dir_t *d = lookup_cached_dir_by_fp(spooled->digest); + if (d != NULL) { + if (published_out) + *published_out = d->published; } - return 0; + return d; } -/** Spooling helper: called when we're sending a bunch of microdescriptors, - * and the outbuf has become too empty. Pulls some entries from - * fingerprint_stack, and writes the corresponding microdescs onto outbuf. If - * we run out of entries, flushes the zlib state and sets the spool source to - * NONE. Returns 0 on success, negative on failure. - */ +/** Helper: Look up the body for an eagerly-served spooled_resource. If + * <b>conn_is_encrypted</b> is false, don't look up any resource that + * shouldn't be sent over an unencrypted connection. On success, set + * <b>body_out</b>, <b>size_out</b>, and <b>published_out</b> to refer + * to the resource's body, size, and publication date, and return 0. + * On failure return -1. */ static int -connection_dirserv_add_microdescs_to_outbuf(dir_connection_t *conn) -{ - microdesc_cache_t *cache = get_microdesc_cache(); - while (smartlist_len(conn->fingerprint_stack) && - connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { - char *fp256 = smartlist_pop_last(conn->fingerprint_stack); - microdesc_t *md = microdesc_cache_lookup_by_digest256(cache, fp256); - tor_free(fp256); - if (!md || !md->body) - continue; - if (conn->zlib_state) { - int last = !smartlist_len(conn->fingerprint_stack); - connection_write_to_buf_zlib(md->body, md->bodylen, conn, last); - if (last) { - tor_zlib_free(conn->zlib_state); - conn->zlib_state = NULL; +spooled_resource_lookup_body(const spooled_resource_t *spooled, + int conn_is_encrypted, + const uint8_t **body_out, + size_t *size_out, + time_t *published_out) +{ + tor_assert(spooled->spool_eagerly == 1); + + const signed_descriptor_t *sd = NULL; + + switch (spooled->spool_source) { + case DIR_SPOOL_EXTRA_BY_FP: { + sd = get_signed_descriptor_by_fp(spooled->digest, 1); + break; + } + case DIR_SPOOL_SERVER_BY_FP: { + sd = get_signed_descriptor_by_fp(spooled->digest, 0); + break; + } + case DIR_SPOOL_SERVER_BY_DIGEST: { + sd = router_get_by_descriptor_digest((const char *)spooled->digest); + break; + } + case DIR_SPOOL_EXTRA_BY_DIGEST: { + sd = extrainfo_get_by_descriptor_digest((const char *)spooled->digest); + break; + } + case DIR_SPOOL_MICRODESC: { + microdesc_t *md = microdesc_cache_lookup_by_digest256( + get_microdesc_cache(), + (const char *)spooled->digest); + if (! md || ! md->body) { + return -1; } - } else { - connection_write_to_buf(md->body, md->bodylen, TO_CONN(conn)); + *body_out = (const uint8_t *)md->body; + *size_out = md->bodylen; + if (published_out) + *published_out = TIME_MAX; + return 0; } + case DIR_SPOOL_NETWORKSTATUS: + case DIR_SPOOL_CONSENSUS_CACHE_ENTRY: + default: + /* LCOV_EXCL_START */ + tor_assert_nonfatal_unreached(); + return -1; + /* LCOV_EXCL_STOP */ } - if (!smartlist_len(conn->fingerprint_stack)) { - if (conn->zlib_state) { - connection_write_to_buf_zlib("", 0, conn, 1); - tor_zlib_free(conn->zlib_state); - conn->zlib_state = NULL; - } - conn->dir_spool_src = DIR_SPOOL_NONE; - smartlist_free(conn->fingerprint_stack); - conn->fingerprint_stack = NULL; + + /* If we get here, then we tried to set "sd" to a signed_descriptor_t. */ + + if (sd == NULL) { + return -1; } + if (sd->send_unencrypted == 0 && ! conn_is_encrypted) { + /* we did this check once before (so we could have an accurate size + * estimate and maybe send a 404 if somebody asked for only bridges on + * a connection), but we need to do it again in case a previously + * unknown bridge descriptor has shown up between then and now. */ + return -1; + } + *body_out = (const uint8_t *) signed_descriptor_get_body(sd); + *size_out = sd->signed_descriptor_len; + if (published_out) + *published_out = sd->published_on; return 0; } -/** Spooling helper: Called when we're sending a directory or networkstatus, - * and the outbuf has become too empty. Pulls some bytes from - * <b>conn</b>-\>cached_dir-\>dir_z, uncompresses them if appropriate, and - * puts them on the outbuf. If we run out of entries, flushes the zlib state - * and sets the spool source to NONE. Returns 0 on success, negative on - * failure. */ -static int -connection_dirserv_add_dir_bytes_to_outbuf(dir_connection_t *conn) -{ - ssize_t bytes; - int64_t remaining; - - bytes = DIRSERV_BUFFER_MIN - connection_get_outbuf_len(TO_CONN(conn)); - tor_assert(bytes > 0); - tor_assert(conn->cached_dir); - if (bytes < 8192) - bytes = 8192; - remaining = conn->cached_dir->dir_z_len - conn->cached_dir_offset; - if (BUG(remaining < 0)) { - remaining = 0; - } - if (bytes > remaining) { - bytes = (ssize_t) remaining; - if (BUG(bytes < 0)) - return -1; +/** Given a fingerprint <b>fp</b> which is either set if we're looking for a + * v2 status, or zeroes if we're looking for a v3 status, or a NUL-padded + * flavor name if we want a flavored v3 status, return a pointer to the + * appropriate cached dir object, or NULL if there isn't one available. */ +static cached_dir_t * +lookup_cached_dir_by_fp(const uint8_t *fp) +{ + cached_dir_t *d = NULL; + if (tor_digest_is_zero((const char *)fp) && cached_consensuses) { + d = strmap_get(cached_consensuses, "ns"); + } else if (memchr(fp, '\0', DIGEST_LEN) && cached_consensuses) { + /* this here interface is a nasty hack: we're shoving a flavor into + * a digest field. */ + d = strmap_get(cached_consensuses, (const char *)fp); } + return d; +} - if (conn->zlib_state) { - connection_write_to_buf_zlib( - conn->cached_dir->dir_z + conn->cached_dir_offset, - bytes, conn, bytes == remaining); - } else { - connection_write_to_buf(conn->cached_dir->dir_z + conn->cached_dir_offset, - bytes, TO_CONN(conn)); +/** Try to guess the number of bytes that will be needed to send the + * spooled objects for <b>conn</b>'s outgoing spool. In the process, + * remove every element of the spool that refers to an absent object, or + * which was published earlier than <b>cutoff</b>. Set *<b>size_out</b> + * to the number of bytes, and *<b>n_expired_out</b> to the number of + * objects removed for being too old. */ +void +dirserv_spool_remove_missing_and_guess_size(dir_connection_t *conn, + time_t cutoff, + int compression, + size_t *size_out, + int *n_expired_out) +{ + if (BUG(!conn)) + return; + + smartlist_t *spool = conn->spool; + if (!spool) { + if (size_out) + *size_out = 0; + if (n_expired_out) + *n_expired_out = 0; + return; } - conn->cached_dir_offset += bytes; - if (conn->cached_dir_offset >= (off_t)conn->cached_dir->dir_z_len) { - /* We just wrote the last one; finish up. */ - connection_dirserv_finish_spooling(conn); - cached_dir_decref(conn->cached_dir); - conn->cached_dir = NULL; + int n_expired = 0; + uint64_t total = 0; + SMARTLIST_FOREACH_BEGIN(spool, spooled_resource_t *, spooled) { + time_t published = TIME_MAX; + size_t sz = spooled_resource_estimate_size(spooled, conn, + compression, &published); + if (published < cutoff) { + ++n_expired; + SMARTLIST_DEL_CURRENT(spool, spooled); + spooled_resource_free(spooled); + } else if (sz == 0) { + SMARTLIST_DEL_CURRENT(spool, spooled); + spooled_resource_free(spooled); + } else { + total += sz; + } + } SMARTLIST_FOREACH_END(spooled); + + if (size_out) { + *size_out = (total > SIZE_MAX) ? SIZE_MAX : (size_t)total; } - return 0; + if (n_expired_out) + *n_expired_out = n_expired; } -/** Spooling helper: Called when we're spooling networkstatus objects on - * <b>conn</b>, and the outbuf has become too empty. If the current - * networkstatus object (in <b>conn</b>-\>cached_dir) has more data, pull data - * from there. Otherwise, pop the next fingerprint from fingerprint_stack, - * and start spooling the next networkstatus. (A digest of all 0 bytes is - * treated as a request for the current consensus.) If we run out of entries, - * flushes the zlib state and sets the spool source to NONE. Returns 0 on - * success, negative on failure. */ +/** Helper: used to sort a connection's spool. */ static int -connection_dirserv_add_networkstatus_bytes_to_outbuf(dir_connection_t *conn) -{ - - while (connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { - if (conn->cached_dir) { - int uncompressing = (conn->zlib_state != NULL); - int r = connection_dirserv_add_dir_bytes_to_outbuf(conn); - if (conn->dir_spool_src == DIR_SPOOL_NONE) { - /* add_dir_bytes thinks we're done with the cached_dir. But we - * may have more cached_dirs! */ - conn->dir_spool_src = DIR_SPOOL_NETWORKSTATUS; - /* This bit is tricky. If we were uncompressing the last - * networkstatus, we may need to make a new zlib object to - * uncompress the next one. */ - if (uncompressing && ! conn->zlib_state && - conn->fingerprint_stack && - smartlist_len(conn->fingerprint_stack)) { - conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION); - } - } - if (r) return r; - } else if (conn->fingerprint_stack && - smartlist_len(conn->fingerprint_stack)) { - /* Add another networkstatus; start serving it. */ - char *fp = smartlist_pop_last(conn->fingerprint_stack); - cached_dir_t *d = lookup_cached_dir_by_fp(fp); - tor_free(fp); - if (d) { - ++d->refcnt; - conn->cached_dir = d; - conn->cached_dir_offset = 0; - } - } else { - connection_dirserv_finish_spooling(conn); - smartlist_free(conn->fingerprint_stack); - conn->fingerprint_stack = NULL; - return 0; +dirserv_spool_sort_comparison_(const void **a_, const void **b_) +{ + const spooled_resource_t *a = *a_; + const spooled_resource_t *b = *b_; + return fast_memcmp(a->digest, b->digest, sizeof(a->digest)); +} + +/** Sort all the entries in <b>conn</b> by digest. */ +void +dirserv_spool_sort(dir_connection_t *conn) +{ + if (conn->spool == NULL) + return; + smartlist_sort(conn->spool, dirserv_spool_sort_comparison_); +} + +/** Return the cache-info for identity fingerprint <b>fp</b>, or + * its extra-info document if <b>extrainfo</b> is true. Return + * NULL if not found or if the descriptor is older than + * <b>publish_cutoff</b>. */ +static const signed_descriptor_t * +get_signed_descriptor_by_fp(const uint8_t *fp, int extrainfo) +{ + if (router_digest_is_me((const char *)fp)) { + if (extrainfo) + return &(router_get_my_extrainfo()->cache_info); + else + return &(router_get_my_routerinfo()->cache_info); + } else { + const routerinfo_t *ri = router_get_by_id_digest((const char *)fp); + if (ri) { + if (extrainfo) + return extrainfo_get_by_descriptor_digest( + ri->cache_info.extra_info_digest); + else + return &ri->cache_info; } } - return 0; + return NULL; } -/** Called whenever we have flushed some directory data in state - * SERVER_WRITING. */ +/** When we're spooling data onto our outbuf, add more whenever we dip + * below this threshold. */ +#define DIRSERV_BUFFER_MIN 16384 + +/** + * Called whenever we have flushed some directory data in state + * SERVER_WRITING, or whenever we want to fill the buffer with initial + * directory data (so that subsequent writes will occur, and trigger this + * function again.) + * + * Return 0 on success, and -1 on failure. + */ int connection_dirserv_flushed_some(dir_connection_t *conn) { tor_assert(conn->base_.state == DIR_CONN_STATE_SERVER_WRITING); - - if (connection_get_outbuf_len(TO_CONN(conn)) >= DIRSERV_BUFFER_MIN) + if (conn->spool == NULL) return 0; - switch (conn->dir_spool_src) { - case DIR_SPOOL_EXTRA_BY_DIGEST: - case DIR_SPOOL_EXTRA_BY_FP: - case DIR_SPOOL_SERVER_BY_DIGEST: - case DIR_SPOOL_SERVER_BY_FP: - return connection_dirserv_add_servers_to_outbuf(conn); - case DIR_SPOOL_MICRODESC: - return connection_dirserv_add_microdescs_to_outbuf(conn); - case DIR_SPOOL_CACHED_DIR: - return connection_dirserv_add_dir_bytes_to_outbuf(conn); - case DIR_SPOOL_NETWORKSTATUS: - return connection_dirserv_add_networkstatus_bytes_to_outbuf(conn); - case DIR_SPOOL_NONE: - default: + while (connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN && + smartlist_len(conn->spool)) { + spooled_resource_t *spooled = + smartlist_get(conn->spool, smartlist_len(conn->spool)-1); + spooled_resource_flush_status_t status; + status = spooled_resource_flush_some(spooled, conn); + if (status == SRFS_ERR) { + return -1; + } else if (status == SRFS_MORE) { return 0; + } + tor_assert(status == SRFS_DONE); + + /* If we're here, we're done flushing this resource. */ + tor_assert(smartlist_pop_last(conn->spool) == spooled); + spooled_resource_free(spooled); + } + + if (smartlist_len(conn->spool) > 0) { + /* We're still spooling something. */ + return 0; + } + + /* If we get here, we're done. */ + smartlist_free(conn->spool); + conn->spool = NULL; + if (conn->compress_state) { + /* Flush the compression state: there could be more bytes pending in there, + * and we don't want to omit bytes. */ + connection_write_to_buf_compress("", 0, conn, 1); + tor_compress_free(conn->compress_state); + conn->compress_state = NULL; } + return 0; +} + +/** Remove every element from <b>conn</b>'s outgoing spool, and delete + * the spool. */ +void +dir_conn_clear_spool(dir_connection_t *conn) +{ + if (!conn || ! conn->spool) + return; + SMARTLIST_FOREACH(conn->spool, spooled_resource_t *, s, + spooled_resource_free(s)); + smartlist_free(conn->spool); + conn->spool = NULL; } /** Return true iff <b>line</b> is a valid RecommendedPackages line. diff --git a/src/or/dirserv.h b/src/or/dirserv.h index e83da5e5ac..480174d5bb 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -32,6 +32,61 @@ /** Maximum allowable length of a version line in a networkstatus. */ #define MAX_V_LINE_LEN 128 +/** Ways to convert a spoolable_resource_t to a bunch of bytes. */ +typedef enum dir_spool_source_t { + DIR_SPOOL_SERVER_BY_DIGEST=1, DIR_SPOOL_SERVER_BY_FP, + DIR_SPOOL_EXTRA_BY_DIGEST, DIR_SPOOL_EXTRA_BY_FP, + DIR_SPOOL_MICRODESC, + DIR_SPOOL_NETWORKSTATUS, + DIR_SPOOL_CONSENSUS_CACHE_ENTRY, +} dir_spool_source_t; +#define dir_spool_source_bitfield_t ENUM_BF(dir_spool_source_t) + +/** Object to remember the identity of an object that we are spooling, + * or about to spool, in response to a directory request. + * + * (Why do we spool? Because some directory responses are very large, + * and we don't want to just shove the complete answer into the output + * buffer: that would take a ridiculous amount of RAM.) + * + * If the spooled resource is relatively small (like microdescriptors, + * descriptors, etc), we look them up by ID as needed, and add the whole + * thing onto the output buffer at once. If the spooled reseource is + * big (like networkstatus documents), we reference-count it, and add it + * a few K at a time. + */ +typedef struct spooled_resource_t { + /** + * If true, we add the entire object to the outbuf. If false, + * we spool the object a few K at a time. + */ + unsigned spool_eagerly : 1; + /** + * Tells us what kind of object to get, and how to look it up. + */ + dir_spool_source_bitfield_t spool_source : 7; + /** + * Tells us the specific object to spool. + */ + uint8_t digest[DIGEST256_LEN]; + /** + * A large object that we're spooling. Holds a reference count. Only + * used when spool_eagerly is false. + */ + struct cached_dir_t *cached_dir_ref; + /** + * A different kind of large object that we might be spooling. Also + * reference-counted. Also only used when spool_eagerly is false. + */ + struct consensus_cache_entry_t *consensus_cache_entry; + const uint8_t *cce_body; + size_t cce_len; + /** + * The current offset into cached_dir or cce_body. Only used when + * spool_eagerly is false */ + off_t cached_dir_offset; +} spooled_resource_t; + int connection_dirserv_flushed_some(dir_connection_t *conn); int dirserv_add_own_fingerprint(crypto_pk_t *pk); @@ -63,12 +118,13 @@ cached_dir_t *dirserv_get_consensus(const char *flavor_name); void dirserv_set_cached_consensus_networkstatus(const char *consensus, const char *flavor_name, const common_digests_t *digests, + const uint8_t *sha3_as_signed, time_t published); void dirserv_clear_old_networkstatuses(time_t cutoff); -int dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, - const char **msg, - int for_unencrypted_conn, - int is_extrainfo); +int dirserv_get_routerdesc_spool(smartlist_t *spools_out, const char *key, + dir_spool_source_t source, + int conn_is_encrytped, + const char **msg_out); int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, const char **msg); void dirserv_orconn_tls_done(const tor_addr_t *addr, @@ -89,13 +145,6 @@ void dirserv_set_node_flags_from_authoritative_status(node_t *node, uint32_t authstatus); int dirserv_would_reject_router(const routerstatus_t *rs); -int dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff); -int dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src); -int dirserv_have_any_microdesc(const smartlist_t *fps); -size_t dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs, - int compressed); -size_t dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed); - char *routerstatus_format_entry( const routerstatus_t *rs, const char *version, @@ -141,5 +190,19 @@ int dirserv_read_measured_bandwidths(const char *from_file, int dirserv_read_guardfraction_file(const char *fname, smartlist_t *vote_routerstatuses); +spooled_resource_t *spooled_resource_new(dir_spool_source_t source, + const uint8_t *digest, + size_t digestlen); +spooled_resource_t *spooled_resource_new_from_cache_entry( + struct consensus_cache_entry_t *entry); +void spooled_resource_free(spooled_resource_t *spooled); +void dirserv_spool_remove_missing_and_guess_size(dir_connection_t *conn, + time_t cutoff, + int compression, + size_t *size_out, + int *n_expired_out); +void dirserv_spool_sort(dir_connection_t *conn); +void dir_conn_clear_spool(dir_connection_t *conn); + #endif diff --git a/src/or/dirvote.c b/src/or/dirvote.c index e92d3b49dc..f5e29eb786 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRVOTE_PRIVATE diff --git a/src/or/dirvote.h b/src/or/dirvote.h index ac7db69db2..e342dc78ea 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/dns.c b/src/or/dns.c index ed20836aed..b5344469b5 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -160,8 +160,9 @@ evdns_log_cb(int warn, const char *msg) } if (!strcmpstart(msg, "Nameserver ") && (cp=strstr(msg, " has failed: "))) { char *ns = tor_strndup(msg+11, cp-(msg+11)); - const char *err = strchr(cp, ':')+2; - tor_assert(err); + const char *colon = strchr(cp, ':'); + tor_assert(colon); + const char *err = colon+2; /* Don't warn about a single failed nameserver; we'll warn with 'all * nameservers have failed' if we're completely out of nameservers; * otherwise, the situation is tolerable. */ @@ -2103,8 +2104,8 @@ assert_cache_ok_(void) #endif -cached_resolve_t -*dns_get_cache_entry(cached_resolve_t *query) +cached_resolve_t * +dns_get_cache_entry(cached_resolve_t *query) { return HT_FIND(cache_map, &cache_root, query); } diff --git a/src/or/dns.h b/src/or/dns.h index 951a2a3467..a81cbd20da 100644 --- a/src/or/dns.h +++ b/src/or/dns.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/dns_structs.h b/src/or/dns_structs.h index bc6067213d..dc00e9f7b9 100644 --- a/src/or/dns_structs.h +++ b/src/or/dns_structs.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index 8768b2a1d1..54a22a5150 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/dnsserv.h b/src/or/dnsserv.h index ad0e248c83..6c0643b8dc 100644 --- a/src/or/dnsserv.h +++ b/src/or/dnsserv.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 0109da8e01..2cbc8019d4 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -67,7 +67,7 @@ * * While we're building circuits, we track a little "guard state" for * each circuit. We use this to keep track of whether the circuit is - * one that we can use as soon as its done, or whether it's one that + * one that we can use as soon as it's done, or whether it's one that * we should keep around to see if we can do better. In the latter case, * a periodic call to entry_guards_upgrade_waiting_circuits() will * eventually upgrade it. @@ -967,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; @@ -1461,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.) @@ -1473,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; } /** @@ -2106,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); @@ -2125,6 +2214,23 @@ circuit_guard_state_free(circuit_guard_state_t *state) tor_free(state); } +/** Allocate and return a new circuit_guard_state_t to track the result + * of using <b>guard</b> for a given operation. */ +static circuit_guard_state_t * +circuit_guard_state_new(entry_guard_t *guard, unsigned state, + entry_guard_restriction_t *rst) +{ + circuit_guard_state_t *result; + + result = tor_malloc_zero(sizeof(circuit_guard_state_t)); + result->guard = entry_guard_handle_new(guard); + result->state = state; + result->state_set_at = approx_time(); + result->restrictions = rst; + + return result; +} + /** * Pick a suitable entry guard for a circuit in, and place that guard * in *<b>chosen_node_out</b>. Set *<b>guard_state_out</b> to an opaque @@ -2163,11 +2269,7 @@ entry_guard_pick_for_circuit(guard_selection_t *gs, goto fail; *chosen_node_out = node; - *guard_state_out = tor_malloc_zero(sizeof(circuit_guard_state_t)); - (*guard_state_out)->guard = entry_guard_handle_new(guard); - (*guard_state_out)->state = state; - (*guard_state_out)->state_set_at = approx_time(); - (*guard_state_out)->restrictions = rst; + *guard_state_out = circuit_guard_state_new(guard, state, rst); return 0; fail: @@ -2997,11 +3099,9 @@ get_guard_state_for_bridge_desc_fetch(const char *digest) guard->last_tried_to_connect = approx_time(); /* Create the guard state */ - guard_state = tor_malloc_zero(sizeof(circuit_guard_state_t)); - guard_state->guard = entry_guard_handle_new(guard); - guard_state->state = GUARD_CIRC_STATE_USABLE_ON_COMPLETION; - guard_state->state_set_at = approx_time(); - guard_state->restrictions = NULL; + guard_state = circuit_guard_state_new(guard, + GUARD_CIRC_STATE_USABLE_ON_COMPLETION, + NULL); return guard_state; } @@ -3348,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, @@ -3401,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 32cfff73be..39bff14381 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -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 @@ -514,7 +521,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); /** @@ -550,7 +557,17 @@ 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); -#endif + +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); void remove_all_entry_guards(void); diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c index 676adfd8bf..b60d2e55c8 100644 --- a/src/or/ext_orport.c +++ b/src/or/ext_orport.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h index 33d954e8d0..b2cd05db8f 100644 --- a/src/or/ext_orport.h +++ b/src/or/ext_orport.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef EXT_ORPORT_H diff --git a/src/or/fp_pair.c b/src/or/fp_pair.c index eeeb0f1de3..f730106d06 100644 --- a/src/or/fp_pair.c +++ b/src/or/fp_pair.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/fp_pair.h b/src/or/fp_pair.h index b1466581d2..4cea3eda6d 100644 --- a/src/or/fp_pair.h +++ b/src/or/fp_pair.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/geoip.c b/src/or/geoip.c index 2f0047fa06..65d00b8659 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/geoip.h b/src/or/geoip.h index 070296dd07..55ca8ca28c 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 8f28abb980..8c48a6f47d 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -587,7 +587,10 @@ accounting_set_wakeup_time(void) char buf[ISO_TIME_LEN+1]; format_iso_time(buf, interval_start_time); - crypto_pk_get_digest(get_server_identity_key(), digest); + if (crypto_pk_get_digest(get_server_identity_key(), digest) < 0) { + log_err(LD_BUG, "Error getting our key's digest."); + tor_assert(0); + } d_env = crypto_digest_new(); crypto_digest_add_bytes(d_env, buf, ISO_TIME_LEN); diff --git a/src/or/hibernate.h b/src/or/hibernate.h index fa9da6de39..8bdb65a927 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 43cd8c3258..29681b42b5 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index ba95e73338..ed00424234 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c index 5003b4b593..ea66fb5194 100644 --- a/src/or/hs_circuitmap.c +++ b/src/or/hs_circuitmap.c @@ -1,11 +1,12 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file hs_circuitmap.c * - * \brief Manage the hidden service circuitmap: A hash table that maps binary - * tokens to introduction and rendezvous circuits. + * \brief Hidden service circuitmap: A hash table that maps binary tokens to + * introduction and rendezvous circuits; it's used both by relays acting as + * intro points and rendezvous points, and also by hidden services themselves. **/ #define HS_CIRCUITMAP_PRIVATE @@ -25,8 +26,8 @@ static struct hs_circuitmap_ht *the_hs_circuitmap = NULL; /* This is a helper function used by the hash table code (HT_). It returns 1 if * two circuits have the same HS token. */ static int -hs_circuits_have_same_token(const or_circuit_t *first_circuit, - const or_circuit_t *second_circuit) +hs_circuits_have_same_token(const circuit_t *first_circuit, + const circuit_t *second_circuit) { const hs_token_t *first_token; const hs_token_t *second_token; @@ -57,7 +58,7 @@ hs_circuits_have_same_token(const or_circuit_t *first_circuit, /* This is a helper function for the hash table code (HT_). It hashes a circuit * HS token into an unsigned int for use as a key by the hash table routines.*/ static inline unsigned int -hs_circuit_hash_token(const or_circuit_t *circuit) +hs_circuit_hash_token(const circuit_t *circuit) { tor_assert(circuit->hs_token); @@ -67,11 +68,11 @@ hs_circuit_hash_token(const or_circuit_t *circuit) /* Register the circuitmap hash table */ HT_PROTOTYPE(hs_circuitmap_ht, // The name of the hashtable struct - or_circuit_t, // The name of the element struct, + circuit_t, // The name of the element struct, hs_circuitmap_node, // The name of HT_ENTRY member hs_circuit_hash_token, hs_circuits_have_same_token) -HT_GENERATE2(hs_circuitmap_ht, or_circuit_t, hs_circuitmap_node, +HT_GENERATE2(hs_circuitmap_ht, circuit_t, hs_circuitmap_node, hs_circuit_hash_token, hs_circuits_have_same_token, 0.6, tor_reallocarray, tor_free_) @@ -116,13 +117,13 @@ hs_token_free(hs_token_t *hs_token) } /** Return the circuit from the circuitmap with token <b>search_token</b>. */ -static or_circuit_t * +static circuit_t * get_circuit_with_token(hs_token_t *search_token) { tor_assert(the_hs_circuitmap); /* We use a dummy circuit object for the hash table search routine. */ - or_circuit_t search_circ; + circuit_t search_circ; search_circ.hs_token = search_token; return HT_FIND(hs_circuitmap_ht, the_hs_circuitmap, &search_circ); } @@ -130,7 +131,7 @@ get_circuit_with_token(hs_token_t *search_token) /* Helper function that registers <b>circ</b> with <b>token</b> on the HS circuitmap. This function steals reference of <b>token</b>. */ static void -hs_circuitmap_register_impl(or_circuit_t *circ, hs_token_t *token) +hs_circuitmap_register_impl(circuit_t *circ, hs_token_t *token) { tor_assert(circ); tor_assert(token); @@ -145,13 +146,12 @@ hs_circuitmap_register_impl(or_circuit_t *circ, hs_token_t *token) take precedence over old ones, so that HSes and clients and reestablish killed circuits without changing the HS token. */ { - or_circuit_t *found_circ; + circuit_t *found_circ; found_circ = get_circuit_with_token(token); if (found_circ) { hs_circuitmap_remove_circuit(found_circ); - if (!found_circ->base_.marked_for_close) { - circuit_mark_for_close(TO_CIRCUIT(found_circ), - END_CIRC_REASON_FINISHED); + if (!found_circ->marked_for_close) { + circuit_mark_for_close(found_circ, END_CIRC_REASON_FINISHED); } } } @@ -165,7 +165,7 @@ hs_circuitmap_register_impl(or_circuit_t *circ, hs_token_t *token) * circuitmap. Use the HS <b>token</b> as the key to the hash table. If * <b>token</b> is not set, clear the circuit of any HS tokens. */ static void -hs_circuitmap_register_circuit(or_circuit_t *circ, +hs_circuitmap_register_circuit(circuit_t *circ, hs_token_type_t type, size_t token_len, const uint8_t *token) { @@ -178,17 +178,19 @@ hs_circuitmap_register_circuit(or_circuit_t *circ, hs_circuitmap_register_impl(circ, hs_token); } -/* Query circuitmap for circuit with <b>token</b> of size <b>token_len</b>. - * Only returns a circuit with purpose equal to the <b>wanted_circ_purpose</b> - * parameter and if it is NOT marked for close. Return NULL if no such circuit - * is found. */ -static or_circuit_t * -hs_circuitmap_get_circuit(hs_token_type_t type, - size_t token_len, - const uint8_t *token, - uint8_t wanted_circ_purpose) +/* Helper function for hs_circuitmap_get_origin_circuit() and + * hs_circuitmap_get_or_circuit(). Because only circuit_t are indexed in the + * circuitmap, this function returns object type so the specialized functions + * using this helper can upcast it to the right type. + * + * Return NULL if not such circuit is found. */ +static circuit_t * +hs_circuitmap_get_circuit_impl(hs_token_type_t type, + size_t token_len, + const uint8_t *token, + uint8_t wanted_circ_purpose) { - or_circuit_t *found_circ = NULL; + circuit_t *found_circ = NULL; tor_assert(the_hs_circuitmap); @@ -202,87 +204,247 @@ hs_circuitmap_get_circuit(hs_token_type_t type, /* Check that the circuit is useful to us */ if (!found_circ || - found_circ->base_.purpose != wanted_circ_purpose || - found_circ->base_.marked_for_close) { + found_circ->purpose != wanted_circ_purpose || + found_circ->marked_for_close) { return NULL; } return found_circ; } -/************** Public circuitmap API ****************************************/ +/* Helper function: Query circuitmap for origin circuit with <b>token</b> of + * size <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose + * equal to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked + * for close. Return NULL if no such circuit is found. */ +static origin_circuit_t * +hs_circuitmap_get_origin_circuit(hs_token_type_t type, + size_t token_len, + const uint8_t *token, + uint8_t wanted_circ_purpose) +{ + circuit_t *circ; + tor_assert(token); + tor_assert(CIRCUIT_PURPOSE_IS_ORIGIN(wanted_circ_purpose)); -/* Public function: Return v3 introduction circuit with <b>auth_key</b>. Return - * NULL if no such circuit is found in the circuitmap. */ -or_circuit_t * -hs_circuitmap_get_intro_circ_v3(const ed25519_public_key_t *auth_key) + circ = hs_circuitmap_get_circuit_impl(type, token_len, token, + wanted_circ_purpose); + if (!circ) { + return NULL; + } + + tor_assert(CIRCUIT_IS_ORIGIN(circ)); + return TO_ORIGIN_CIRCUIT(circ); +} + +/* Helper function: Query circuitmap for OR circuit with <b>token</b> of size + * <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose equal + * to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked for + * close. Return NULL if no such circuit is found. */ +static or_circuit_t * +hs_circuitmap_get_or_circuit(hs_token_type_t type, + size_t token_len, + const uint8_t *token, + uint8_t wanted_circ_purpose) { - tor_assert(auth_key); + circuit_t *circ; + tor_assert(token); + tor_assert(!CIRCUIT_PURPOSE_IS_ORIGIN(wanted_circ_purpose)); + + circ = hs_circuitmap_get_circuit_impl(type, token_len, token, + wanted_circ_purpose); + if (!circ) { + return NULL; + } - return hs_circuitmap_get_circuit(HS_TOKEN_INTRO_V3, - ED25519_PUBKEY_LEN, auth_key->pubkey, - CIRCUIT_PURPOSE_INTRO_POINT); + tor_assert(CIRCUIT_IS_ORCIRC(circ)); + return TO_OR_CIRCUIT(circ); } -/* Public function: Return v2 introduction circuit with <b>digest</b>. Return - * NULL if no such circuit is found in the circuitmap. */ +/************** Public circuitmap API ****************************************/ + +/**** Public relay-side getters: */ + +/* Public function: Return a v3 introduction circuit to this relay with + * <b>auth_key</b>. Return NULL if no such circuit is found in the + * circuitmap. */ or_circuit_t * -hs_circuitmap_get_intro_circ_v2(const uint8_t *digest) +hs_circuitmap_get_intro_circ_v3_relay_side( + const ed25519_public_key_t *auth_key) { - tor_assert(digest); + return hs_circuitmap_get_or_circuit(HS_TOKEN_INTRO_V3_RELAY_SIDE, + ED25519_PUBKEY_LEN, auth_key->pubkey, + CIRCUIT_PURPOSE_INTRO_POINT); +} - return hs_circuitmap_get_circuit(HS_TOKEN_INTRO_V2, - REND_TOKEN_LEN, digest, - CIRCUIT_PURPOSE_INTRO_POINT); +/* Public function: Return v2 introduction circuit to this relay with + * <b>digest</b>. Return NULL if no such circuit is found in the circuitmap. */ +or_circuit_t * +hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest) +{ + return hs_circuitmap_get_or_circuit(HS_TOKEN_INTRO_V2_RELAY_SIDE, + REND_TOKEN_LEN, digest, + CIRCUIT_PURPOSE_INTRO_POINT); } -/* Public function: Return rendezvous circuit with rendezvous +/* Public function: Return rendezvous circuit to this relay with rendezvous * <b>cookie</b>. Return NULL if no such circuit is found in the circuitmap. */ or_circuit_t * -hs_circuitmap_get_rend_circ(const uint8_t *cookie) +hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie) { - tor_assert(cookie); - - return hs_circuitmap_get_circuit(HS_TOKEN_REND, - REND_TOKEN_LEN, cookie, - CIRCUIT_PURPOSE_REND_POINT_WAITING); + return hs_circuitmap_get_or_circuit(HS_TOKEN_REND_RELAY_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_REND_POINT_WAITING); } +/** Public relay-side setters: */ + /* Public function: Register rendezvous circuit with key <b>cookie</b> to the * circuitmap. */ void -hs_circuitmap_register_rend_circ(or_circuit_t *circ, const uint8_t *cookie) +hs_circuitmap_register_rend_circ_relay_side(or_circuit_t *circ, + const uint8_t *cookie) { - hs_circuitmap_register_circuit(circ, - HS_TOKEN_REND, + hs_circuitmap_register_circuit(TO_CIRCUIT(circ), + HS_TOKEN_REND_RELAY_SIDE, REND_TOKEN_LEN, cookie); } +/* Public function: Register v2 intro circuit with key <b>digest</b> to the + * circuitmap. */ +void +hs_circuitmap_register_intro_circ_v2_relay_side(or_circuit_t *circ, + const uint8_t *digest) +{ + hs_circuitmap_register_circuit(TO_CIRCUIT(circ), + HS_TOKEN_INTRO_V2_RELAY_SIDE, + REND_TOKEN_LEN, digest); +} + +/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the + * circuitmap. */ +void +hs_circuitmap_register_intro_circ_v3_relay_side(or_circuit_t *circ, + const ed25519_public_key_t *auth_key) +{ + hs_circuitmap_register_circuit(TO_CIRCUIT(circ), + HS_TOKEN_INTRO_V3_RELAY_SIDE, + ED25519_PUBKEY_LEN, auth_key->pubkey); +} + +/**** Public servide-side getters: */ + +/* Public function: Return v3 introduction circuit with <b>auth_key</b> + * originating from this hidden service. Return NULL if no such circuit is + * found in the circuitmap. */ +origin_circuit_t * +hs_circuitmap_get_intro_circ_v3_service_side(const + ed25519_public_key_t *auth_key) +{ + origin_circuit_t *circ = NULL; + + /* Check first for established intro circuits */ + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V3_SERVICE_SIDE, + ED25519_PUBKEY_LEN, auth_key->pubkey, + CIRCUIT_PURPOSE_S_INTRO); + if (circ) { + return circ; + } + + /* ...if nothing found, check for pending intro circs */ + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V3_SERVICE_SIDE, + ED25519_PUBKEY_LEN, auth_key->pubkey, + CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + return circ; +} + +/* Public function: Return v2 introduction circuit originating from this hidden + * service with <b>digest</b>. Return NULL if no such circuit is found in the + * circuitmap. */ +origin_circuit_t * +hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest) +{ + origin_circuit_t *circ = NULL; + + /* Check first for established intro circuits */ + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V2_SERVICE_SIDE, + REND_TOKEN_LEN, digest, + CIRCUIT_PURPOSE_S_INTRO); + if (circ) { + return circ; + } + + /* ...if nothing found, check for pending intro circs */ + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V2_SERVICE_SIDE, + REND_TOKEN_LEN, digest, + CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + return circ; +} + +/* Public function: Return rendezvous circuit originating from this hidden + * service with rendezvous <b>cookie</b>. Return NULL if no such circuit is + * found in the circuitmap. */ +origin_circuit_t * +hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie) +{ + origin_circuit_t *circ = NULL; + + /* Try to check if we have a connecting circuit. */ + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_SERVICE_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_S_CONNECT_REND); + if (circ) { + return circ; + } + + /* Then try for connected circuit. */ + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_SERVICE_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_S_REND_JOINED); + return circ; +} + +/**** Public servide-side setters: */ /* Public function: Register v2 intro circuit with key <b>digest</b> to the * circuitmap. */ void -hs_circuitmap_register_intro_circ_v2(or_circuit_t *circ, const uint8_t *digest) +hs_circuitmap_register_intro_circ_v2_service_side(origin_circuit_t *circ, + const uint8_t *digest) { - hs_circuitmap_register_circuit(circ, - HS_TOKEN_INTRO_V2, + hs_circuitmap_register_circuit(TO_CIRCUIT(circ), + HS_TOKEN_INTRO_V2_SERVICE_SIDE, REND_TOKEN_LEN, digest); } /* Public function: Register v3 intro circuit with key <b>auth_key</b> to the * circuitmap. */ void -hs_circuitmap_register_intro_circ_v3(or_circuit_t *circ, - const ed25519_public_key_t *auth_key) +hs_circuitmap_register_intro_circ_v3_service_side(origin_circuit_t *circ, + const ed25519_public_key_t *auth_key) { - hs_circuitmap_register_circuit(circ, - HS_TOKEN_INTRO_V3, + hs_circuitmap_register_circuit(TO_CIRCUIT(circ), + HS_TOKEN_INTRO_V3_SERVICE_SIDE, ED25519_PUBKEY_LEN, auth_key->pubkey); } -/** Remove this circuit from the HS circuitmap. Clear its HS token, and remove - * it from the hashtable. */ +/* Public function: Register rendezvous circuit with key <b>cookie</b> to the + * circuitmap. */ +void +hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ, + const uint8_t *cookie) +{ + hs_circuitmap_register_circuit(TO_CIRCUIT(circ), + HS_TOKEN_REND_SERVICE_SIDE, + REND_TOKEN_LEN, cookie); +} + +/**** Misc public functions: */ + +/** Public function: Remove this circuit from the HS circuitmap. Clear its HS + * token, and remove it from the hashtable. */ void -hs_circuitmap_remove_circuit(or_circuit_t *circ) +hs_circuitmap_remove_circuit(circuit_t *circ) { tor_assert(the_hs_circuitmap); @@ -291,14 +453,14 @@ hs_circuitmap_remove_circuit(or_circuit_t *circ) } /* Remove circ from circuitmap */ - or_circuit_t *tmp; + circuit_t *tmp; tmp = HT_REMOVE(hs_circuitmap_ht, the_hs_circuitmap, circ); /* ... and ensure the removal was successful. */ if (tmp) { tor_assert(tmp == circ); } else { log_warn(LD_BUG, "Could not find circuit (%u) in circuitmap.", - circ->p_circ_id); + circ->n_circ_id); } /* Clear token from circ */ @@ -306,7 +468,7 @@ hs_circuitmap_remove_circuit(or_circuit_t *circ) circ->hs_token = NULL; } -/* Initialize the global HS circuitmap. */ +/* Public function: Initialize the global HS circuitmap. */ void hs_circuitmap_init(void) { @@ -316,7 +478,7 @@ hs_circuitmap_init(void) HT_INIT(hs_circuitmap_ht, the_hs_circuitmap); } -/* Free all memory allocated by the global HS circuitmap. */ +/* Public function: Free all memory allocated by the global HS circuitmap. */ void hs_circuitmap_free_all(void) { diff --git a/src/or/hs_circuitmap.h b/src/or/hs_circuitmap.h index b587039310..33d5b64117 100644 --- a/src/or/hs_circuitmap.h +++ b/src/or/hs_circuitmap.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -9,26 +9,52 @@ #ifndef TOR_HS_CIRCUITMAP_H #define TOR_HS_CIRCUITMAP_H -typedef HT_HEAD(hs_circuitmap_ht, or_circuit_t) hs_circuitmap_ht; +typedef HT_HEAD(hs_circuitmap_ht, circuit_t) hs_circuitmap_ht; typedef struct hs_token_s hs_token_t; struct or_circuit_t; +struct origin_circuit_t; /** Public HS circuitmap API: */ -struct or_circuit_t *hs_circuitmap_get_rend_circ(const uint8_t *cookie); -struct or_circuit_t *hs_circuitmap_get_intro_circ_v3( - const ed25519_public_key_t *auth_key); -struct or_circuit_t *hs_circuitmap_get_intro_circ_v2(const uint8_t *digest); - -void hs_circuitmap_register_rend_circ(struct or_circuit_t *circ, - const uint8_t *cookie); -void hs_circuitmap_register_intro_circ_v2(struct or_circuit_t *circ, - const uint8_t *digest); -void hs_circuitmap_register_intro_circ_v3(struct or_circuit_t *circ, +/** Public relay-side API: */ + +struct or_circuit_t * +hs_circuitmap_get_intro_circ_v3_relay_side(const + ed25519_public_key_t *auth_key); +struct or_circuit_t * +hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest); +struct or_circuit_t * +hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie); + +void hs_circuitmap_register_rend_circ_relay_side(struct or_circuit_t *circ, + const uint8_t *cookie); +void hs_circuitmap_register_intro_circ_v2_relay_side(struct or_circuit_t *circ, + const uint8_t *digest); +void hs_circuitmap_register_intro_circ_v3_relay_side(struct or_circuit_t *circ, const ed25519_public_key_t *auth_key); -void hs_circuitmap_remove_circuit(struct or_circuit_t *circ); +/** Public service-side API: */ + +struct origin_circuit_t * +hs_circuitmap_get_intro_circ_v3_service_side(const + ed25519_public_key_t *auth_key); +struct origin_circuit_t * +hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest); +struct origin_circuit_t * +hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie); + +void hs_circuitmap_register_intro_circ_v2_service_side( + struct origin_circuit_t *circ, + const uint8_t *digest); +void hs_circuitmap_register_intro_circ_v3_service_side( + struct origin_circuit_t *circ, + const ed25519_public_key_t *auth_key); +void hs_circuitmap_register_rend_circ_service_side( + struct origin_circuit_t *circ, + const uint8_t *cookie); + +void hs_circuitmap_remove_circuit(struct circuit_t *circ); void hs_circuitmap_init(void); void hs_circuitmap_free_all(void); @@ -37,12 +63,19 @@ void hs_circuitmap_free_all(void); /** Represents the type of HS token. */ typedef enum { - /** A rendezvous cookie (128bit)*/ - HS_TOKEN_REND, - /** A v2 introduction point pubkey (160bit) */ - HS_TOKEN_INTRO_V2, - /** A v3 introduction point pubkey (256bit) */ - HS_TOKEN_INTRO_V3, + /** A rendezvous cookie on a relay (128bit)*/ + HS_TOKEN_REND_RELAY_SIDE, + /** A v2 introduction point pubkey on a relay (160bit) */ + HS_TOKEN_INTRO_V2_RELAY_SIDE, + /** A v3 introduction point pubkey on a relay (256bit) */ + HS_TOKEN_INTRO_V3_RELAY_SIDE, + + /** A rendezvous cookie on a hidden service (128bit)*/ + HS_TOKEN_REND_SERVICE_SIDE, + /** A v2 introduction point pubkey on a hidden service (160bit) */ + HS_TOKEN_INTRO_V2_SERVICE_SIDE, + /** A v3 introduction point pubkey on a hidden service (256bit) */ + HS_TOKEN_INTRO_V3_SERVICE_SIDE, } hs_token_type_t; /** Represents a token used in the HS protocol. Each such token maps to a diff --git a/src/or/hs_common.c b/src/or/hs_common.c index de96946ab5..7cef5a8e2c 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -9,6 +9,8 @@ * protocol. **/ +#define HS_COMMON_PRIVATE + #include "or.h" #include "config.h" @@ -16,6 +18,80 @@ #include "hs_common.h" #include "rendcommon.h" +/* Make sure that the directory for <b>service</b> is private, using the config + * <b>username</b>. + * If <b>create</b> is true: + * - if the directory exists, change permissions if needed, + * - if the directory does not exist, create it with the correct permissions. + * If <b>create</b> is false: + * - if the directory exists, check permissions, + * - if the directory does not exist, check if we think we can create it. + * Return 0 on success, -1 on failure. */ +int +hs_check_service_private_dir(const char *username, const char *path, + unsigned int dir_group_readable, + unsigned int create) +{ + cpd_check_t check_opts = CPD_NONE; + + tor_assert(path); + + if (create) { + check_opts |= CPD_CREATE; + } else { + check_opts |= CPD_CHECK_MODE_ONLY; + check_opts |= CPD_CHECK; + } + if (dir_group_readable) { + check_opts |= CPD_GROUP_READ; + } + /* Check/create directory */ + if (check_private_dir(path, check_opts, username) < 0) { + return -1; + } + return 0; +} + +/** Get the default HS time period length in minutes from the consensus. */ +STATIC uint64_t +get_time_period_length(void) +{ + int32_t time_period_length = networkstatus_get_param(NULL, "hsdir_interval", + HS_TIME_PERIOD_LENGTH_DEFAULT, + HS_TIME_PERIOD_LENGTH_MIN, + HS_TIME_PERIOD_LENGTH_MAX); + /* Make sure it's a positive value. */ + tor_assert(time_period_length >= 0); + /* uint64_t will always be able to contain a int32_t */ + return (uint64_t) time_period_length; +} + +/** Get the HS time period number at time <b>now</b> */ +STATIC uint64_t +get_time_period_num(time_t now) +{ + uint64_t time_period_num; + uint64_t time_period_length = get_time_period_length(); + uint64_t minutes_since_epoch = now / 60; + + /* Now subtract half a day to fit the prop224 time period schedule (see + * section [TIME-PERIODS]). */ + tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET); + minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET; + + /* Calculate the time period */ + time_period_num = minutes_since_epoch / time_period_length; + return time_period_num; +} + +/** Get the number of the _upcoming_ HS time period, given that the current + * time is <b>now</b>. */ +uint64_t +hs_get_next_time_period_num(time_t now) +{ + return get_time_period_num(now) + 1; +} + /* Create a new rend_data_t for a specific given <b>version</b>. * Return a pointer to the newly allocated data structure. */ static rend_data_t * diff --git a/src/or/hs_common.h b/src/or/hs_common.h index e0ab510ea4..a8fded652a 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,12 +17,42 @@ /* Version 3 of the protocol (prop224). */ #define HS_VERSION_THREE 3 -/* Denotes ed25519 authentication key on ESTABLISH_INTRO cell. */ -#define AUTH_KEY_ED25519 0x02 +/** Try to maintain this many intro points per service by default. */ +#define NUM_INTRO_POINTS_DEFAULT 3 +/** Maximum number of intro points per service. */ +#define NUM_INTRO_POINTS_MAX 10 +/** Number of extra intro points we launch if our set of intro nodes is empty. + * See proposal 155, section 4. */ +#define NUM_INTRO_POINTS_EXTRA 2 + +/** If we can't build our intro circuits, don't retry for this long. */ +#define INTRO_CIRC_RETRY_PERIOD (60*5) +/** Don't try to build more than this many circuits before giving up for a + * while.*/ +#define MAX_INTRO_CIRCS_PER_PERIOD 10 +/** How many times will a hidden service operator attempt to connect to a + * requested rendezvous point before giving up? */ +#define MAX_REND_FAILURES 1 +/** How many seconds should we spend trying to connect to a requested + * rendezvous point before giving up? */ +#define MAX_REND_TIMEOUT 30 /* String prefix for the signature of ESTABLISH_INTRO */ #define ESTABLISH_INTRO_SIG_PREFIX "Tor establish-intro cell v1" +/* The default HS time period length */ +#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ +/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ + +int hs_check_service_private_dir(const char *username, const char *path, + unsigned int dir_group_readable, + unsigned int create); + void rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); rend_data_t *rend_data_client_create(const char *onion_address, @@ -39,5 +69,18 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data, const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out); +uint64_t hs_get_next_time_period_num(time_t now); + +#ifdef HS_COMMON_PRIVATE + +#ifdef TOR_UNIT_TESTS + +STATIC uint64_t get_time_period_length(void); +STATIC uint64_t get_time_period_num(time_t now); + +#endif /* TOR_UNIT_TESTS */ + +#endif /* HS_COMMON_PRIVATE */ + #endif /* TOR_HS_COMMON_H */ diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 938b7a77df..fae527b2db 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -1,9 +1,55 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file hs_descriptor.c * \brief Handle hidden service descriptor encoding/decoding. + * + * \details + * Here is a graphical depiction of an HS descriptor and its layers: + * + * +------------------------------------------------------+ + * |DESCRIPTOR HEADER: | + * | hs-descriptor 3 | + * | descriptor-lifetime 180 | + * | ... | + * | superencrypted | + * |+---------------------------------------------------+ | + * ||SUPERENCRYPTED LAYER (aka OUTER ENCRYPTED LAYER): | | + * || desc-auth-type x25519 | | + * || desc-auth-ephemeral-key | | + * || auth-client | | + * || auth-client | | + * || ... | | + * || encrypted | | + * ||+-------------------------------------------------+| | + * |||ENCRYPTED LAYER (aka INNER ENCRYPTED LAYER): || | + * ||| create2-formats || | + * ||| intro-auth-required || | + * ||| introduction-point || | + * ||| introduction-point || | + * ||| ... || | + * ||+-------------------------------------------------+| | + * |+---------------------------------------------------+ | + * +------------------------------------------------------+ + * + * The DESCRIPTOR HEADER section is completely unencrypted and contains generic + * descriptor metadata. + * + * The SUPERENCRYPTED LAYER section is the first layer of encryption, and it's + * encrypted using the blinded public key of the hidden service to protect + * against entities who don't know its onion address. The clients of the hidden + * service know its onion address and blinded public key, whereas third-parties + * (like HSDirs) don't know it (except if it's a public hidden service). + * + * The ENCRYPTED LAYER section is the second layer of encryption, and it's + * encrypted using the client authorization key material (if those exist). When + * client authorization is enabled, this second layer of encryption protects + * the descriptor content from unauthorized entities. If client authorization + * is disabled, this second layer of encryption does not provide any extra + * security but is still present. The plaintext of this layer contains all the + * information required to connect to the hidden service like its list of + * introduction points. **/ /* For unit tests.*/ @@ -23,29 +69,36 @@ #define str_desc_cert "descriptor-signing-key-cert" #define str_rev_counter "revision-counter" #define str_superencrypted "superencrypted" +#define str_encrypted "encrypted" #define str_signature "signature" #define str_lifetime "descriptor-lifetime" /* Constant string value for the encrypted part of the descriptor. */ #define str_create2_formats "create2-formats" -#define str_auth_required "authentication-required" +#define str_intro_auth_required "intro-auth-required" #define str_single_onion "single-onion-service" #define str_intro_point "introduction-point" #define str_ip_auth_key "auth-key" #define str_ip_enc_key "enc-key" -#define str_ip_enc_key_cert "enc-key-certification" +#define str_ip_enc_key_cert "enc-key-cert" +#define str_ip_legacy_key "legacy-key" +#define str_ip_legacy_key_cert "legacy-key-cert" #define str_intro_point_start "\n" str_intro_point " " /* Constant string value for the construction to encrypt the encrypted data * section. */ -#define str_enc_hsdir_data "hsdir-superencrypted-data" +#define str_enc_const_superencryption "hsdir-superencrypted-data" +#define str_enc_const_encryption "hsdir-encrypted-data" /* Prefix required to compute/verify HS desc signatures */ #define str_desc_sig_prefix "Tor onion service descriptor sig v3" +#define str_desc_auth_type "desc-auth-type" +#define str_desc_auth_key "desc-auth-ephemeral-key" +#define str_desc_auth_client "auth-client" +#define str_encrypted "encrypted" /* Authentication supported types. */ static const struct { hs_desc_auth_type_t type; const char *identifier; -} auth_types[] = { - { HS_DESC_AUTH_PASSWORD, "password" }, +} intro_auth_types[] = { { HS_DESC_AUTH_ED25519, "ed25519" }, /* Indicate end of array. */ { 0, NULL } @@ -62,10 +115,19 @@ static token_rule_t hs_desc_v3_token_table[] = { END_OF_TABLE }; +/* Descriptor ruleset for the superencrypted section. */ +static token_rule_t hs_desc_superencrypted_v3_token_table[] = { + T1_START(str_desc_auth_type, R3_DESC_AUTH_TYPE, GE(1), NO_OBJ), + T1(str_desc_auth_key, R3_DESC_AUTH_KEY, GE(1), NO_OBJ), + T1N(str_desc_auth_client, R3_DESC_AUTH_CLIENT, GE(3), NO_OBJ), + T1(str_encrypted, R3_ENCRYPTED, NO_ARGS, NEED_OBJ), + END_OF_TABLE +}; + /* Descriptor ruleset for the encrypted section. */ static token_rule_t hs_desc_encrypted_v3_token_table[] = { T1_START(str_create2_formats, R3_CREATE2_FORMATS, CONCAT_ARGS, NO_OBJ), - T01(str_auth_required, R3_AUTHENTICATION_REQUIRED, ARGS, NO_OBJ), + T01(str_intro_auth_required, R3_INTRO_AUTH_REQUIRED, ARGS, NO_OBJ), T01(str_single_onion, R3_SINGLE_ONION_SERVICE, ARGS, NO_OBJ), END_OF_TABLE }; @@ -74,9 +136,10 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = { static token_rule_t hs_desc_intro_point_v3_token_table[] = { T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ), - T1(str_ip_enc_key, R3_INTRO_ENC_KEY, ARGS, OBJ_OK), - T1_END(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERTIFICATION, - NO_ARGS, NEED_OBJ), + T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK), + T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK), + T01(str_ip_legacy_key, R3_INTRO_LEGACY_KEY, ARGS, NEED_KEY_1024), + T01(str_ip_legacy_key_cert, R3_INTRO_LEGACY_KEY_CERT, ARGS, OBJ_OK), END_OF_TABLE }; @@ -93,22 +156,26 @@ desc_intro_point_free(hs_desc_intro_point_t *ip) smartlist_free(ip->link_specifiers); } tor_cert_free(ip->auth_key_cert); - if (ip->enc_key_type == HS_DESC_KEY_TYPE_LEGACY) { - crypto_pk_free(ip->enc_key.legacy); + tor_cert_free(ip->enc_key_cert); + if (ip->legacy.key) { + crypto_pk_free(ip->legacy.key); + } + if (ip->legacy.cert.encoded) { + tor_free(ip->legacy.cert.encoded); } tor_free(ip); } /* Free the content of the plaintext section of a descriptor. */ -static void +STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) { if (!desc) { return; } - if (desc->encrypted_blob) { - tor_free(desc->encrypted_blob); + if (desc->superencrypted_blob) { + tor_free(desc->superencrypted_blob); } tor_cert_free(desc->signing_key_cert); @@ -123,9 +190,9 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) return; } - if (desc->auth_types) { - SMARTLIST_FOREACH(desc->auth_types, char *, a, tor_free(a)); - smartlist_free(desc->auth_types); + if (desc->intro_auth_types) { + SMARTLIST_FOREACH(desc->intro_auth_types, char *, a, tor_free(a)); + smartlist_free(desc->intro_auth_types); } if (desc->intro_points) { SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, @@ -135,6 +202,135 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) memwipe(desc, 0, sizeof(*desc)); } +/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out. + * We use SHA3-256 for the MAC computation. + * This function can't fail. */ +static void +build_mac(const uint8_t *mac_key, size_t mac_key_len, + const uint8_t *salt, size_t salt_len, + const uint8_t *encrypted, size_t encrypted_len, + uint8_t *mac_out, size_t mac_len) +{ + crypto_digest_t *digest; + + const uint64_t mac_len_netorder = tor_htonll(mac_key_len); + const uint64_t salt_len_netorder = tor_htonll(salt_len); + + tor_assert(mac_key); + tor_assert(salt); + tor_assert(encrypted); + tor_assert(mac_out); + + digest = crypto_digest256_new(DIGEST_SHA3_256); + /* As specified in section 2.5 of proposal 224, first add the mac key + * then add the salt first and then the encrypted section. */ + + crypto_digest_add_bytes(digest, (const char *) &mac_len_netorder, 8); + crypto_digest_add_bytes(digest, (const char *) mac_key, mac_key_len); + crypto_digest_add_bytes(digest, (const char *) &salt_len_netorder, 8); + crypto_digest_add_bytes(digest, (const char *) salt, salt_len); + crypto_digest_add_bytes(digest, (const char *) encrypted, encrypted_len); + crypto_digest_get_digest(digest, (char *) mac_out, mac_len); + crypto_digest_free(digest); +} + +/* Using a given decriptor object, build the secret input needed for the + * KDF and put it in the dst pointer which is an already allocated buffer + * of size dstlen. */ +static void +build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) +{ + size_t offset = 0; + + tor_assert(desc); + tor_assert(dst); + tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen); + + /* XXX use the destination length as the memcpy length */ + /* Copy blinded public key. */ + memcpy(dst, desc->plaintext_data.blinded_pubkey.pubkey, + sizeof(desc->plaintext_data.blinded_pubkey.pubkey)); + offset += sizeof(desc->plaintext_data.blinded_pubkey.pubkey); + /* Copy subcredential. */ + memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); + offset += sizeof(desc->subcredential); + /* Copy revision counter value. */ + set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter)); + offset += sizeof(uint64_t); + tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); +} + +/* Do the KDF construction and put the resulting data in key_out which is of + * key_out_len length. It uses SHAKE-256 as specified in the spec. */ +static void +build_kdf_key(const hs_descriptor_t *desc, + const uint8_t *salt, size_t salt_len, + uint8_t *key_out, size_t key_out_len, + int is_superencrypted_layer) +{ + uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN]; + crypto_xof_t *xof; + + tor_assert(desc); + tor_assert(salt); + tor_assert(key_out); + + /* Build the secret input for the KDF computation. */ + build_secret_input(desc, secret_input, sizeof(secret_input)); + + xof = crypto_xof_new(); + /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */ + crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input)); + crypto_xof_add_bytes(xof, salt, salt_len); + + /* Feed in the right string constant based on the desc layer */ + if (is_superencrypted_layer) { + crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_const_superencryption, + strlen(str_enc_const_superencryption)); + } else { + crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_const_encryption, + strlen(str_enc_const_encryption)); + } + + /* Eat from our KDF. */ + crypto_xof_squeeze_bytes(xof, key_out, key_out_len); + crypto_xof_free(xof); + memwipe(secret_input, 0, sizeof(secret_input)); +} + +/* Using the given descriptor and salt, run it through our KDF function and + * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out. + * This function can't fail. */ +static void +build_secret_key_iv_mac(const hs_descriptor_t *desc, + const uint8_t *salt, size_t salt_len, + uint8_t *key_out, size_t key_len, + uint8_t *iv_out, size_t iv_len, + uint8_t *mac_out, size_t mac_len, + int is_superencrypted_layer) +{ + size_t offset = 0; + uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN]; + + tor_assert(desc); + tor_assert(salt); + tor_assert(key_out); + tor_assert(iv_out); + tor_assert(mac_out); + + build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key), + is_superencrypted_layer); + /* Copy the bytes we need for both the secret key and IV. */ + memcpy(key_out, kdf_key, key_len); + offset += key_len; + memcpy(iv_out, kdf_key + offset, iv_len); + offset += iv_len; + memcpy(mac_out, kdf_key + offset, mac_len); + /* Extra precaution to make sure we are not out of bound. */ + tor_assert((offset + mac_len) == sizeof(kdf_key)); + memwipe(kdf_key, 0, sizeof(kdf_key)); +} + /* === ENCODING === */ /* Encode the given link specifier objects into a newly allocated string. @@ -217,101 +413,68 @@ encode_link_specifiers(const smartlist_t *specs) return encoded_b64; } -/* Encode an introduction point encryption key and return a newly allocated - * string with it. On failure, return NULL. */ +/* Encode an introduction point legacy key and certificate. Return a newly + * allocated string with it. On failure, return NULL. */ static char * -encode_enc_key(const ed25519_public_key_t *sig_key, - const hs_desc_intro_point_t *ip) +encode_legacy_key(const hs_desc_intro_point_t *ip) { - char *encoded = NULL; - time_t now = time(NULL); + char *key_str, b64_cert[256], *encoded = NULL; + size_t key_str_len; - tor_assert(sig_key); tor_assert(ip); - switch (ip->enc_key_type) { - case HS_DESC_KEY_TYPE_LEGACY: - { - char *key_str, b64_cert[256]; - ssize_t cert_len; - size_t key_str_len; - uint8_t *cert_data = NULL; - - /* Create cross certification cert. */ - cert_len = tor_make_rsa_ed25519_crosscert(sig_key, ip->enc_key.legacy, - now + HS_DESC_CERT_LIFETIME, - &cert_data); - if (cert_len < 0) { - log_warn(LD_REND, "Unable to create legacy crosscert."); - goto err; - } - /* Encode cross cert. */ - if (base64_encode(b64_cert, sizeof(b64_cert), (const char *) cert_data, - cert_len, BASE64_ENCODE_MULTILINE) < 0) { - tor_free(cert_data); - log_warn(LD_REND, "Unable to encode legacy crosscert."); - goto err; - } - tor_free(cert_data); - /* Convert the encryption key to a string. */ - if (crypto_pk_write_public_key_to_string(ip->enc_key.legacy, &key_str, - &key_str_len) < 0) { - log_warn(LD_REND, "Unable to encode legacy encryption key."); - goto err; - } - tor_asprintf(&encoded, - "%s legacy\n%s" /* Newline is added by the call above. */ - "%s\n" - "-----BEGIN CROSSCERT-----\n" - "%s" - "-----END CROSSCERT-----", - str_ip_enc_key, key_str, - str_ip_enc_key_cert, b64_cert); - tor_free(key_str); - break; - } - case HS_DESC_KEY_TYPE_CURVE25519: - { - int signbit, ret; - char *encoded_cert, key_fp_b64[CURVE25519_BASE64_PADDED_LEN + 1]; - ed25519_keypair_t curve_kp; + /* Encode cross cert. */ + if (base64_encode(b64_cert, sizeof(b64_cert), + (const char *) ip->legacy.cert.encoded, + ip->legacy.cert.len, BASE64_ENCODE_MULTILINE) < 0) { + log_warn(LD_REND, "Unable to encode legacy crosscert."); + goto done; + } + /* Convert the encryption key to PEM format NUL terminated. */ + if (crypto_pk_write_public_key_to_string(ip->legacy.key, &key_str, + &key_str_len) < 0) { + log_warn(LD_REND, "Unable to encode legacy encryption key."); + goto done; + } + tor_asprintf(&encoded, + "%s \n%s" /* Newline is added by the call above. */ + "%s\n" + "-----BEGIN CROSSCERT-----\n" + "%s" + "-----END CROSSCERT-----", + str_ip_legacy_key, key_str, + str_ip_legacy_key_cert, b64_cert); + tor_free(key_str); - if (ed25519_keypair_from_curve25519_keypair(&curve_kp, &signbit, - &ip->enc_key.curve25519)) { - goto err; - } - tor_cert_t *cross_cert = tor_cert_create(&curve_kp, - CERT_TYPE_CROSS_HS_IP_KEYS, - sig_key, now, - HS_DESC_CERT_LIFETIME, - CERT_FLAG_INCLUDE_SIGNING_KEY); - memwipe(&curve_kp, 0, sizeof(curve_kp)); - if (!cross_cert) { - goto err; - } - ret = tor_cert_encode_ed22519(cross_cert, &encoded_cert); - tor_cert_free(cross_cert); - if (ret) { - goto err; - } - if (curve25519_public_to_base64(key_fp_b64, - &ip->enc_key.curve25519.pubkey) < 0) { - tor_free(encoded_cert); - goto err; - } - tor_asprintf(&encoded, - "%s ntor %s\n" - "%s\n%s", - str_ip_enc_key, key_fp_b64, - str_ip_enc_key_cert, encoded_cert); - tor_free(encoded_cert); - break; + done: + return encoded; +} + +/* Encode an introduction point encryption key and certificate. Return a newly + * allocated string with it. On failure, return NULL. */ +static char * +encode_enc_key(const hs_desc_intro_point_t *ip) +{ + char *encoded = NULL, *encoded_cert; + char key_b64[CURVE25519_BASE64_PADDED_LEN + 1]; + + tor_assert(ip); + + /* Base64 encode the encryption key for the "enc-key" field. */ + if (curve25519_public_to_base64(key_b64, &ip->enc_key) < 0) { + goto done; } - default: - tor_assert(0); + if (tor_cert_encode_ed22519(ip->enc_key_cert, &encoded_cert) < 0) { + goto done; } + tor_asprintf(&encoded, + "%s ntor %s\n" + "%s\n%s", + str_ip_enc_key, key_b64, + str_ip_enc_key_cert, encoded_cert); + tor_free(encoded_cert); - err: + done: return encoded; } @@ -346,7 +509,7 @@ encode_intro_point(const ed25519_public_key_t *sig_key, /* Encryption key encoding. */ { - char *encoded_enc_key = encode_enc_key(sig_key, ip); + char *encoded_enc_key = encode_enc_key(ip); if (encoded_enc_key == NULL) { goto err; } @@ -354,6 +517,18 @@ encode_intro_point(const ed25519_public_key_t *sig_key, tor_free(encoded_enc_key); } + /* Legacy key if any. */ + if (ip->legacy.key != NULL) { + /* Strong requirement else the IP creation was badly done. */ + tor_assert(ip->legacy.cert.encoded); + char *encoded_legacy_key = encode_legacy_key(ip); + if (encoded_legacy_key == NULL) { + goto err; + } + smartlist_add_asprintf(lines, "%s", encoded_legacy_key); + tor_free(encoded_legacy_key); + } + /* Join them all in one blob of text. */ encoded_ip = smartlist_join_strings(lines, "\n", 1, NULL); @@ -363,142 +538,23 @@ encode_intro_point(const ed25519_public_key_t *sig_key, return encoded_ip; } -/* Using a given decriptor object, build the secret input needed for the - * KDF and put it in the dst pointer which is an already allocated buffer - * of size dstlen. */ -static void -build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) -{ - size_t offset = 0; - - tor_assert(desc); - tor_assert(dst); - tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen); - - /* XXX use the destination length as the memcpy length */ - /* Copy blinded public key. */ - memcpy(dst, desc->plaintext_data.blinded_pubkey.pubkey, - sizeof(desc->plaintext_data.blinded_pubkey.pubkey)); - offset += sizeof(desc->plaintext_data.blinded_pubkey.pubkey); - /* Copy subcredential. */ - memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); - offset += sizeof(desc->subcredential); - /* Copy revision counter value. */ - set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter)); - offset += sizeof(uint64_t); - tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); -} - -/* Do the KDF construction and put the resulting data in key_out which is of - * key_out_len length. It uses SHAKE-256 as specified in the spec. */ -static void -build_kdf_key(const hs_descriptor_t *desc, - const uint8_t *salt, size_t salt_len, - uint8_t *key_out, size_t key_out_len) -{ - uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN]; - crypto_xof_t *xof; - - tor_assert(desc); - tor_assert(salt); - tor_assert(key_out); - - /* Build the secret input for the KDF computation. */ - build_secret_input(desc, secret_input, sizeof(secret_input)); - - xof = crypto_xof_new(); - /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */ - crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input)); - crypto_xof_add_bytes(xof, salt, salt_len); - crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_hsdir_data, - strlen(str_enc_hsdir_data)); - /* Eat from our KDF. */ - crypto_xof_squeeze_bytes(xof, key_out, key_out_len); - crypto_xof_free(xof); - memwipe(secret_input, 0, sizeof(secret_input)); -} - -/* Using the given descriptor and salt, run it through our KDF function and - * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out. - * This function can't fail. */ -static void -build_secret_key_iv_mac(const hs_descriptor_t *desc, - const uint8_t *salt, size_t salt_len, - uint8_t *key_out, size_t key_len, - uint8_t *iv_out, size_t iv_len, - uint8_t *mac_out, size_t mac_len) -{ - size_t offset = 0; - uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN]; - - tor_assert(desc); - tor_assert(salt); - tor_assert(key_out); - tor_assert(iv_out); - tor_assert(mac_out); - - build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key)); - /* Copy the bytes we need for both the secret key and IV. */ - memcpy(key_out, kdf_key, key_len); - offset += key_len; - memcpy(iv_out, kdf_key + offset, iv_len); - offset += iv_len; - memcpy(mac_out, kdf_key + offset, mac_len); - /* Extra precaution to make sure we are not out of bound. */ - tor_assert((offset + mac_len) == sizeof(kdf_key)); - memwipe(kdf_key, 0, sizeof(kdf_key)); -} - -/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out. - * We use SHA3-256 for the MAC computation. - * This function can't fail. */ -static void -build_mac(const uint8_t *mac_key, size_t mac_key_len, - const uint8_t *salt, size_t salt_len, - const uint8_t *encrypted, size_t encrypted_len, - uint8_t *mac_out, size_t mac_len) -{ - crypto_digest_t *digest; - - const uint64_t mac_len_netorder = tor_htonll(mac_key_len); - const uint64_t salt_len_netorder = tor_htonll(salt_len); - - tor_assert(mac_key); - tor_assert(salt); - tor_assert(encrypted); - tor_assert(mac_out); - - digest = crypto_digest256_new(DIGEST_SHA3_256); - /* As specified in section 2.5 of proposal 224, first add the mac key - * then add the salt first and then the encrypted section. */ - - crypto_digest_add_bytes(digest, (const char *) &mac_len_netorder, 8); - crypto_digest_add_bytes(digest, (const char *) mac_key, mac_key_len); - crypto_digest_add_bytes(digest, (const char *) &salt_len_netorder, 8); - crypto_digest_add_bytes(digest, (const char *) salt, salt_len); - crypto_digest_add_bytes(digest, (const char *) encrypted, encrypted_len); - crypto_digest_get_digest(digest, (char *) mac_out, mac_len); - crypto_digest_free(digest); -} - /* Given a source length, return the new size including padding for the * plaintext encryption. */ static size_t compute_padded_plaintext_length(size_t plaintext_len) { size_t plaintext_padded_len; + const int padding_block_length = HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE; /* Make sure we won't overflow. */ - tor_assert(plaintext_len <= - (SIZE_T_CEILING - HS_DESC_PLAINTEXT_PADDING_MULTIPLE)); - - /* Get the extra length we need to add. For example, if srclen is 234 bytes, - * this will expand to (2 * 128) == 256 thus an extra 22 bytes. */ - plaintext_padded_len = CEIL_DIV(plaintext_len, - HS_DESC_PLAINTEXT_PADDING_MULTIPLE) * - HS_DESC_PLAINTEXT_PADDING_MULTIPLE; + tor_assert(plaintext_len <= (SIZE_T_CEILING - padding_block_length)); + + /* Get the extra length we need to add. For example, if srclen is 10200 + * bytes, this will expand to (2 * 10k) == 20k thus an extra 9800 bytes. */ + plaintext_padded_len = CEIL_DIV(plaintext_len, padding_block_length) * + padding_block_length; /* Can never be extra careful. Make sure we are _really_ padded. */ - tor_assert(!(plaintext_padded_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE)); + tor_assert(!(plaintext_padded_len % padding_block_length)); return plaintext_padded_len; } @@ -530,7 +586,8 @@ build_plaintext_padding(const char *plaintext, size_t plaintext_len, * data. Return size of the encrypted data buffer. */ static size_t build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext, - size_t plaintext_len, uint8_t **encrypted_out) + size_t plaintext_len, uint8_t **encrypted_out, + int is_superencrypted_layer) { size_t encrypted_len; uint8_t *padded_plaintext, *encrypted; @@ -541,15 +598,21 @@ build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext, tor_assert(plaintext); tor_assert(encrypted_out); + /* If we are encrypting the middle layer of the descriptor, we need to first + pad the plaintext */ + if (is_superencrypted_layer) { + encrypted_len = build_plaintext_padding(plaintext, plaintext_len, + &padded_plaintext); + /* Extra precautions that we have a valid padding length. */ + tor_assert(!(encrypted_len % HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE)); + } else { /* No padding required for inner layers */ + padded_plaintext = tor_memdup(plaintext, plaintext_len); + encrypted_len = plaintext_len; + } + /* This creates a cipher for AES. It can't fail. */ cipher = crypto_cipher_new_with_iv_and_bits(key, iv, HS_DESC_ENCRYPTED_BIT_SIZE); - /* This can't fail. */ - encrypted_len = build_plaintext_padding(plaintext, plaintext_len, - &padded_plaintext); - /* Extra precautions that we have a valie padding length. */ - tor_assert(encrypted_len <= HS_DESC_PADDED_PLAINTEXT_MAX_LEN); - tor_assert(!(encrypted_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE)); /* We use a stream cipher so the encrypted length will be the same as the * plaintext padded length. */ encrypted = tor_malloc_zero(encrypted_len); @@ -563,12 +626,13 @@ build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext, return encrypted_len; } -/* Encrypt the given plaintext buffer and using the descriptor to get the +/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> to get the * keys. Set encrypted_out with the encrypted data and return the length of - * it. */ + * it. <b>is_superencrypted_layer</b> is set if this is the outer encrypted + * layer of the descriptor. */ static size_t encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, - char **encrypted_out) + char **encrypted_out, int is_superencrypted_layer) { char *final_blob; size_t encrypted_len, final_blob_len, offset = 0; @@ -589,11 +653,13 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, build_secret_key_iv_mac(desc, salt, sizeof(salt), secret_key, sizeof(secret_key), secret_iv, sizeof(secret_iv), - mac_key, sizeof(mac_key)); + mac_key, sizeof(mac_key), + is_superencrypted_layer); /* Build the encrypted part that is do the actual encryption. */ encrypted_len = build_encrypted(secret_key, secret_iv, plaintext, - strlen(plaintext), &encrypted); + strlen(plaintext), &encrypted, + is_superencrypted_layer); memwipe(secret_key, 0, sizeof(secret_key)); memwipe(secret_iv, 0, sizeof(secret_iv)); /* This construction is specified in section 2.5 of proposal 224. */ @@ -625,20 +691,89 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, return final_blob_len; } -/* Take care of encoding the encrypted data section and then encrypting it - * with the descriptor's key. A newly allocated NUL terminated string pointer - * containing the encrypted encoded blob is put in encrypted_blob_out. Return - * 0 on success else a negative value. */ -static int -encode_encrypted_data(const hs_descriptor_t *desc, - char **encrypted_blob_out) +/* Create and return a string containing a fake client-auth entry. It's the + * responsibility of the caller to free the returned string. This function will + * never fail. */ +static char * +get_fake_auth_client_str(void) { - int ret = -1; - char *encoded_str, *encrypted_blob; - smartlist_t *lines = smartlist_new(); + char *auth_client_str = NULL; + /* We are gonna fill these arrays with fake base64 data. They are all double + * the size of their binary representation to fit the base64 overhead. */ + char client_id_b64[8*2]; + char iv_b64[16*2]; + char encrypted_cookie_b64[16*2]; + int retval; + + /* This is a macro to fill a field with random data and then base64 it. */ +#define FILL_WITH_FAKE_DATA_AND_BASE64(field) STMT_BEGIN \ + crypto_rand((char *)field, sizeof(field)); \ + retval = base64_encode_nopad(field##_b64, sizeof(field##_b64), \ + field, sizeof(field)); \ + tor_assert(retval > 0); \ + STMT_END + + { /* Get those fakes! */ + uint8_t client_id[8]; /* fake client-id */ + uint8_t iv[16]; /* fake IV (initialization vector) */ + uint8_t encrypted_cookie[16]; /* fake encrypted cookie */ + + FILL_WITH_FAKE_DATA_AND_BASE64(client_id); + FILL_WITH_FAKE_DATA_AND_BASE64(iv); + FILL_WITH_FAKE_DATA_AND_BASE64(encrypted_cookie); + } + + /* Build the final string */ + tor_asprintf(&auth_client_str, "%s %s %s %s", str_desc_auth_client, + client_id_b64, iv_b64, encrypted_cookie_b64); + +#undef FILL_WITH_FAKE_DATA_AND_BASE64 + + return auth_client_str; +} - tor_assert(desc); - tor_assert(encrypted_blob_out); +/** How many lines of "client-auth" we want in our descriptors; fake or not. */ +#define CLIENT_AUTH_ENTRIES_BLOCK_SIZE 16 + +/** Create the "client-auth" part of the descriptor and return a + * newly-allocated string with it. It's the responsibility of the caller to + * free the returned string. */ +static char * +get_fake_auth_client_lines(void) +{ + /* XXX: Client authorization is still not implemented, so all this function + does is make fake clients */ + int i = 0; + smartlist_t *auth_client_lines = smartlist_new(); + char *auth_client_lines_str = NULL; + + /* Make a line for each fake client */ + const int num_fake_clients = CLIENT_AUTH_ENTRIES_BLOCK_SIZE; + for (i = 0; i < num_fake_clients; i++) { + char *auth_client_str = get_fake_auth_client_str(); + tor_assert(auth_client_str); + smartlist_add(auth_client_lines, auth_client_str); + } + + /* Join all lines together to form final string */ + auth_client_lines_str = smartlist_join_strings(auth_client_lines, + "\n", 1, NULL); + /* Cleanup the mess */ + SMARTLIST_FOREACH(auth_client_lines, char *, a, tor_free(a)); + smartlist_free(auth_client_lines); + + return auth_client_lines_str; +} + +/* Create the inner layer of the descriptor (which includes the intro points, + * etc.). Return a newly-allocated string with the layer plaintext, or NULL if + * an error occured. It's the responsibility of the caller to free the returned + * string. */ +static char * +get_inner_encrypted_layer_plaintext(const hs_descriptor_t *desc) +{ + char *encoded_str = NULL; + smartlist_t *lines = smartlist_new(); /* Build the start of the section prior to the introduction points. */ { @@ -649,12 +784,12 @@ encode_encrypted_data(const hs_descriptor_t *desc, smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats, ONION_HANDSHAKE_TYPE_NTOR); - if (desc->encrypted_data.auth_types && - smartlist_len(desc->encrypted_data.auth_types)) { + if (desc->encrypted_data.intro_auth_types && + smartlist_len(desc->encrypted_data.intro_auth_types)) { /* Put the authentication-required line. */ - char *buf = smartlist_join_strings(desc->encrypted_data.auth_types, " ", - 0, NULL); - smartlist_add_asprintf(lines, "%s %s\n", str_auth_required, buf); + char *buf = smartlist_join_strings(desc->encrypted_data.intro_auth_types, + " ", 0, NULL); + smartlist_add_asprintf(lines, "%s %s\n", str_intro_auth_required, buf); tor_free(buf); } @@ -679,31 +814,159 @@ encode_encrypted_data(const hs_descriptor_t *desc, * then encrypt it. */ encoded_str = smartlist_join_strings(lines, "", 0, NULL); - /* Encrypt the section into an encrypted blob that we'll base64 encode - * before returning it. */ + err: + SMARTLIST_FOREACH(lines, char *, l, tor_free(l)); + smartlist_free(lines); + + return encoded_str; +} + +/* Create the middle layer of the descriptor, which includes the client auth + * data and the encrypted inner layer (provided as a base64 string at + * <b>layer2_b64_ciphertext</b>). Return a newly-allocated string with the + * layer plaintext, or NULL if an error occured. It's the responsibility of the + * caller to free the returned string. */ +static char * +get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, + const char *layer2_b64_ciphertext) +{ + char *layer1_str = NULL; + smartlist_t *lines = smartlist_new(); + + /* XXX: Disclaimer: This function generates only _fake_ client auth + * data. Real client auth is not yet implemented, but client auth data MUST + * always be present in descriptors. In the future this function will be + * refactored to use real client auth data if they exist (#20700). */ + (void) *desc; + + /* Specify auth type */ + smartlist_add_asprintf(lines, "%s %s\n", str_desc_auth_type, "x25519"); + + { /* Create fake ephemeral x25519 key */ + char fake_key_base64[CURVE25519_BASE64_PADDED_LEN + 1]; + curve25519_keypair_t fake_x25519_keypair; + if (curve25519_keypair_generate(&fake_x25519_keypair, 0) < 0) { + goto done; + } + if (curve25519_public_to_base64(fake_key_base64, + &fake_x25519_keypair.pubkey) < 0) { + goto done; + } + smartlist_add_asprintf(lines, "%s %s\n", + str_desc_auth_key, fake_key_base64); + /* No need to memwipe any of these fake keys. They will go unused. */ + } + + { /* Create fake auth-client lines. */ + char *auth_client_lines = get_fake_auth_client_lines(); + tor_assert(auth_client_lines); + smartlist_add(lines, auth_client_lines); + } + + /* create encrypted section */ { - char *enc_b64; - ssize_t enc_b64_len, ret_len, enc_len; + smartlist_add_asprintf(lines, + "%s\n" + "-----BEGIN MESSAGE-----\n" + "%s" + "-----END MESSAGE-----", + str_encrypted, layer2_b64_ciphertext); + } - enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob); - tor_free(encoded_str); - /* Get the encoded size plus a NUL terminating byte. */ - enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1; - enc_b64 = tor_malloc_zero(enc_b64_len); - /* Base64 the encrypted blob before returning it. */ - ret_len = base64_encode(enc_b64, enc_b64_len, encrypted_blob, enc_len, - BASE64_ENCODE_MULTILINE); - /* Return length doesn't count the NUL byte. */ - tor_assert(ret_len == (enc_b64_len - 1)); - tor_free(encrypted_blob); - *encrypted_blob_out = enc_b64; + layer1_str = smartlist_join_strings(lines, "", 0, NULL); + + done: + SMARTLIST_FOREACH(lines, char *, a, tor_free(a)); + smartlist_free(lines); + + return layer1_str; +} + +/* Encrypt <b>encoded_str</b> into an encrypted blob and then base64 it before + * returning it. <b>desc</b> is provided to derive the encryption + * keys. <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the + * middle (superencrypted) layer of the descriptor. It's the responsibility of + * the caller to free the returned string. */ +static char * +encrypt_desc_data_and_base64(const hs_descriptor_t *desc, + const char *encoded_str, + int is_superencrypted_layer) +{ + char *enc_b64; + ssize_t enc_b64_len, ret_len, enc_len; + char *encrypted_blob = NULL; + + enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob, + is_superencrypted_layer); + /* Get the encoded size plus a NUL terminating byte. */ + enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1; + enc_b64 = tor_malloc_zero(enc_b64_len); + /* Base64 the encrypted blob before returning it. */ + ret_len = base64_encode(enc_b64, enc_b64_len, encrypted_blob, enc_len, + BASE64_ENCODE_MULTILINE); + /* Return length doesn't count the NUL byte. */ + tor_assert(ret_len == (enc_b64_len - 1)); + tor_free(encrypted_blob); + + return enc_b64; +} + +/* Generate and encode the superencrypted portion of <b>desc</b>. This also + * involves generating the encrypted portion of the descriptor, and performing + * the superencryption. A newly allocated NUL-terminated string pointer + * containing the encrypted encoded blob is put in encrypted_blob_out. Return 0 + * on success else a negative value. */ +static int +encode_superencrypted_data(const hs_descriptor_t *desc, + char **encrypted_blob_out) +{ + int ret = -1; + char *layer2_str = NULL; + char *layer2_b64_ciphertext = NULL; + char *layer1_str = NULL; + char *layer1_b64_ciphertext = NULL; + + tor_assert(desc); + tor_assert(encrypted_blob_out); + + /* Func logic: We first create the inner layer of the descriptor (layer2). + * We then encrypt it and use it to create the middle layer of the descriptor + * (layer1). Finally we superencrypt the middle layer and return it to our + * caller. */ + + /* Create inner descriptor layer */ + layer2_str = get_inner_encrypted_layer_plaintext(desc); + if (!layer2_str) { + goto err; } + + /* Encrypt and b64 the inner layer */ + layer2_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer2_str, 0); + if (!layer2_b64_ciphertext) { + goto err; + } + + /* Now create middle descriptor layer given the inner layer */ + layer1_str = get_outer_encrypted_layer_plaintext(desc,layer2_b64_ciphertext); + if (!layer1_str) { + goto err; + } + + /* Encrypt and base64 the middle layer */ + layer1_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer1_str, 1); + if (!layer1_b64_ciphertext) { + goto err; + } + /* Success! */ ret = 0; err: - SMARTLIST_FOREACH(lines, char *, l, tor_free(l)); - smartlist_free(lines); + tor_free(layer1_str); + tor_free(layer2_str); + tor_free(layer2_b64_ciphertext); + + *encrypted_blob_out = layer1_b64_ciphertext; return ret; } @@ -756,7 +1019,7 @@ desc_encode_v3(const hs_descriptor_t *desc, /* Build the superencrypted data section. */ { char *enc_b64_blob=NULL; - if (encode_encrypted_data(desc, &enc_b64_blob) < 0) { + if (encode_superencrypted_data(desc, &enc_b64_blob) < 0) { goto err; } smartlist_add_asprintf(lines, @@ -796,6 +1059,13 @@ desc_encode_v3(const hs_descriptor_t *desc, encoded_str = smartlist_join_strings(lines, "\n", 1, NULL); *encoded_out = encoded_str; + if (strlen(encoded_str) >= hs_cache_get_max_descriptor_size()) { + log_warn(LD_GENERAL, "We just made an HS descriptor that's too big (%d)." + "Failing.", (int)strlen(encoded_str)); + tor_free(encoded_str); + goto err; + } + /* XXX: Trigger a control port event. */ /* Success! */ @@ -894,14 +1164,14 @@ decode_auth_type(hs_desc_encrypted_data_t *desc, const char *list) tor_assert(desc); tor_assert(list); - desc->auth_types = smartlist_new(); - smartlist_split_string(desc->auth_types, list, " ", 0, 0); + desc->intro_auth_types = smartlist_new(); + smartlist_split_string(desc->intro_auth_types, list, " ", 0, 0); /* Validate the types that we at least know about one. */ - SMARTLIST_FOREACH_BEGIN(desc->auth_types, const char *, auth) { - for (int idx = 0; auth_types[idx].identifier; idx++) { - if (!strncmp(auth, auth_types[idx].identifier, - strlen(auth_types[idx].identifier))) { + SMARTLIST_FOREACH_BEGIN(desc->intro_auth_types, const char *, auth) { + for (int idx = 0; intro_auth_types[idx].identifier; idx++) { + if (!strncmp(auth, intro_auth_types[idx].identifier, + strlen(intro_auth_types[idx].identifier))) { match = 1; break; } @@ -971,7 +1241,7 @@ cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type) } /* The following will not only check if the signature matches but also the * expiration date and overall validity. */ - if (tor_cert_checksig(cert, &cert->signing_key, time(NULL)) < 0) { + if (tor_cert_checksig(cert, &cert->signing_key, approx_time()) < 0) { log_warn(LD_REND, "Invalid signature for %s.", log_obj_type); goto err; } @@ -1024,7 +1294,7 @@ STATIC int encrypted_data_length_is_valid(size_t len) { /* Make sure there is enough data for the salt and the mac. The equality is - * there to ensure that there is at least one byte of encrypted data. */ + there to ensure that there is at least one byte of encrypted data. */ if (len <= HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN) { log_warn(LD_REND, "Length of descriptor's encrypted data is too small. " "Got %lu but minimum value is %d", @@ -1037,12 +1307,17 @@ encrypted_data_length_is_valid(size_t len) return 0; } -/* Decrypt the encrypted section of the descriptor using the given descriptor - * object desc. A newly allocated NUL terminated string is put in - * decrypted_out. Return the length of decrypted_out on success else 0 is - * returned and decrypted_out is set to NULL. */ +/** Decrypt an encrypted descriptor layer at <b>encrypted_blob</b> of size + * <b>encrypted_blob_size</b>. Use the descriptor object <b>desc</b> to + * generate the right decryption keys; set <b>decrypted_out</b> to the + * plaintext. If <b>is_superencrypted_layer</b> is set, this is the outter + * encrypted layer of the descriptor. */ static size_t -desc_decrypt_data_v3(const hs_descriptor_t *desc, char **decrypted_out) +decrypt_desc_layer(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out) { uint8_t *decrypted = NULL; uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN]; @@ -1052,41 +1327,33 @@ desc_decrypt_data_v3(const hs_descriptor_t *desc, char **decrypted_out) tor_assert(decrypted_out); tor_assert(desc); - tor_assert(desc->plaintext_data.encrypted_blob); + tor_assert(encrypted_blob); - /* Construction is as follow: SALT | ENCRYPTED_DATA | MAC */ - if (!encrypted_data_length_is_valid( - desc->plaintext_data.encrypted_blob_size)) { + /* Construction is as follow: SALT | ENCRYPTED_DATA | MAC . + * Make sure we have enough space for all these things. */ + if (!encrypted_data_length_is_valid(encrypted_blob_size)) { goto err; } /* Start of the blob thus the salt. */ - salt = desc->plaintext_data.encrypted_blob; + salt = encrypted_blob; + /* Next is the encrypted data. */ - encrypted = desc->plaintext_data.encrypted_blob + - HS_DESC_ENCRYPTED_SALT_LEN; - encrypted_len = desc->plaintext_data.encrypted_blob_size - + encrypted = encrypted_blob + HS_DESC_ENCRYPTED_SALT_LEN; + encrypted_len = encrypted_blob_size - (HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN); + tor_assert(encrypted_len > 0); /* guaranteed by the check above */ - /* At the very end is the MAC. Make sure it's of the right size. */ - { - desc_mac = encrypted + encrypted_len; - size_t desc_mac_size = desc->plaintext_data.encrypted_blob_size - - (desc_mac - desc->plaintext_data.encrypted_blob); - if (desc_mac_size != DIGEST256_LEN) { - log_warn(LD_REND, "Service descriptor MAC length of encrypted data " - "is invalid (%lu, expected %u)", - (unsigned long) desc_mac_size, DIGEST256_LEN); - goto err; - } - } + /* And last comes the MAC. */ + desc_mac = encrypted_blob + encrypted_blob_size - DIGEST256_LEN; /* KDF construction resulting in a key from which the secret key, IV and MAC * key are extracted which is what we need for the decryption. */ build_secret_key_iv_mac(desc, salt, HS_DESC_ENCRYPTED_SALT_LEN, secret_key, sizeof(secret_key), secret_iv, sizeof(secret_iv), - mac_key, sizeof(mac_key)); + mac_key, sizeof(mac_key), + is_superencrypted_layer); /* Build MAC. */ build_mac(mac_key, sizeof(mac_key), salt, HS_DESC_ENCRYPTED_SALT_LEN, @@ -1116,7 +1383,7 @@ desc_decrypt_data_v3(const hs_descriptor_t *desc, char **decrypted_out) } { - /* Adjust length to remove NULL padding bytes */ + /* Adjust length to remove NUL padding bytes */ uint8_t *end = memchr(decrypted, 0, encrypted_len); result_len = encrypted_len; if (end) { @@ -1142,6 +1409,222 @@ desc_decrypt_data_v3(const hs_descriptor_t *desc, char **decrypted_out) return result_len; } +/* Basic validation that the superencrypted client auth portion of the + * descriptor is well-formed and recognized. Return True if so, otherwise + * return False. */ +static int +superencrypted_auth_data_is_valid(smartlist_t *tokens) +{ + /* XXX: This is just basic validation for now. When we implement client auth, + we can refactor this function so that it actually parses and saves the + data. */ + + { /* verify desc auth type */ + const directory_token_t *tok; + tok = find_by_keyword(tokens, R3_DESC_AUTH_TYPE); + tor_assert(tok->n_args >= 1); + if (strcmp(tok->args[0], "x25519")) { + log_warn(LD_DIR, "Unrecognized desc auth type"); + return 0; + } + } + + { /* verify desc auth key */ + const directory_token_t *tok; + curve25519_public_key_t k; + tok = find_by_keyword(tokens, R3_DESC_AUTH_KEY); + tor_assert(tok->n_args >= 1); + if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { + log_warn(LD_DIR, "Bogus desc auth key in HS desc"); + return 0; + } + } + + /* verify desc auth client items */ + SMARTLIST_FOREACH_BEGIN(tokens, const directory_token_t *, tok) { + if (tok->tp == R3_DESC_AUTH_CLIENT) { + tor_assert(tok->n_args >= 3); + } + } SMARTLIST_FOREACH_END(tok); + + return 1; +} + +/* Parse <b>message</b>, the plaintext of the superencrypted portion of an HS + * descriptor. Set <b>encrypted_out</b> to the encrypted blob, and return its + * size */ +STATIC size_t +decode_superencrypted(const char *message, size_t message_len, + uint8_t **encrypted_out) +{ + int retval = 0; + memarea_t *area = NULL; + smartlist_t *tokens = NULL; + + area = memarea_new(); + tokens = smartlist_new(); + if (tokenize_string(area, message, message + message_len, tokens, + hs_desc_superencrypted_v3_token_table, 0) < 0) { + log_warn(LD_REND, "Superencrypted portion is not parseable"); + goto err; + } + + /* Do some rudimentary validation of the authentication data */ + if (!superencrypted_auth_data_is_valid(tokens)) { + log_warn(LD_REND, "Invalid auth data"); + goto err; + } + + /* Extract the encrypted data section. */ + { + const directory_token_t *tok; + tok = find_by_keyword(tokens, R3_ENCRYPTED); + tor_assert(tok->object_body); + if (strcmp(tok->object_type, "MESSAGE") != 0) { + log_warn(LD_REND, "Desc superencrypted data section is invalid"); + goto err; + } + /* Make sure the length of the encrypted blob is valid. */ + if (!encrypted_data_length_is_valid(tok->object_size)) { + goto err; + } + + /* Copy the encrypted blob to the descriptor object so we can handle it + * latter if needed. */ + tor_assert(tok->object_size <= INT_MAX); + *encrypted_out = tor_memdup(tok->object_body, tok->object_size); + retval = (int) tok->object_size; + } + + err: + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + if (area) { + memarea_drop_all(area); + } + + return retval; +} + +/* Decrypt both the superencrypted and the encrypted section of the descriptor + * using the given descriptor object <b>desc</b>. A newly allocated NUL + * terminated string is put in decrypted_out which contains the inner encrypted + * layer of the descriptor. Return the length of decrypted_out on success else + * 0 is returned and decrypted_out is set to NULL. */ +static size_t +desc_decrypt_all(const hs_descriptor_t *desc, char **decrypted_out) +{ + size_t decrypted_len = 0; + size_t encrypted_len = 0; + size_t superencrypted_len = 0; + char *superencrypted_plaintext = NULL; + uint8_t *encrypted_blob = NULL; + + /** Function logic: This function takes us from the descriptor header to the + * inner encrypted layer, by decrypting and decoding the middle descriptor + * layer. In the end we return the contents of the inner encrypted layer to + * our caller. */ + + /* 1. Decrypt middle layer of descriptor */ + superencrypted_len = decrypt_desc_layer(desc, + desc->plaintext_data.superencrypted_blob, + desc->plaintext_data.superencrypted_blob_size, + 1, + &superencrypted_plaintext); + if (!superencrypted_len) { + log_warn(LD_REND, "Decrypting superencrypted desc failed."); + goto err; + } + tor_assert(superencrypted_plaintext); + + /* 2. Parse "superencrypted" */ + encrypted_len = decode_superencrypted(superencrypted_plaintext, + superencrypted_len, + &encrypted_blob); + if (!encrypted_len) { + log_warn(LD_REND, "Decrypting encrypted desc failed."); + goto err; + } + tor_assert(encrypted_blob); + + /* 3. Decrypt "encrypted" and set decrypted_out */ + char *decrypted_desc; + decrypted_len = decrypt_desc_layer(desc, + encrypted_blob, encrypted_len, + 0, &decrypted_desc); + if (!decrypted_len) { + log_warn(LD_REND, "Decrypting encrypted desc failed."); + goto err; + } + tor_assert(decrypted_desc); + + *decrypted_out = decrypted_desc; + + err: + tor_free(superencrypted_plaintext); + tor_free(encrypted_blob); + + return decrypted_len; +} + +/* Given the token tok for an intro point legacy key, the list of tokens, the + * introduction point ip being decoded and the descriptor desc from which it + * comes from, decode the legacy key and set the intro point object. Return 0 + * on success else -1 on failure. */ +static int +decode_intro_legacy_key(const directory_token_t *tok, + smartlist_t *tokens, + hs_desc_intro_point_t *ip, + const hs_descriptor_t *desc) +{ + tor_assert(tok); + tor_assert(tokens); + tor_assert(ip); + tor_assert(desc); + + if (!crypto_pk_public_exponent_ok(tok->key)) { + log_warn(LD_REND, "Introduction point legacy key is invalid"); + goto err; + } + ip->legacy.key = crypto_pk_dup_key(tok->key); + /* Extract the legacy cross certification cert which MUST be present if we + * have a legacy key. */ + tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY_CERT); + if (!tok) { + log_warn(LD_REND, "Introduction point legacy key cert is missing"); + goto err; + } + tor_assert(tok->object_body); + if (strcmp(tok->object_type, "CROSSCERT")) { + /* Info level because this might be an unknown field that we should + * ignore. */ + log_info(LD_REND, "Introduction point legacy encryption key " + "cross-certification has an unknown format."); + goto err; + } + /* Keep a copy of the certificate. */ + ip->legacy.cert.encoded = tor_memdup(tok->object_body, tok->object_size); + ip->legacy.cert.len = tok->object_size; + /* The check on the expiration date is for the entire lifetime of a + * certificate which is 24 hours. However, a descriptor has a maximum + * lifetime of 12 hours meaning we have a 12h difference between the two + * which ultimately accomodate the clock skewed client. */ + if (rsa_ed25519_crosscert_check(ip->legacy.cert.encoded, + ip->legacy.cert.len, ip->legacy.key, + &desc->plaintext_data.signing_pubkey, + approx_time() - HS_DESC_CERT_LIFETIME)) { + log_warn(LD_REND, "Unable to check cross-certification on the " + "introduction point legacy encryption key."); + ip->cross_certified = 0; + goto err; + } + + /* Success. */ + return 0; + err: + return -1; +} + /* Given the start of a section and the end of it, decode a single * introduction point from that section. Return a newly allocated introduction * point object containing the decoded data. Return NULL if the section can't @@ -1152,7 +1635,6 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) hs_desc_intro_point_t *ip = NULL; memarea_t *area = NULL; smartlist_t *tokens = NULL; - tor_cert_t *cross_cert = NULL; const directory_token_t *tok; tor_assert(desc); @@ -1186,84 +1668,67 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) log_warn(LD_REND, "Unexpected object type for introduction auth key"); goto err; } - /* Parse cert and do some validation. */ if (cert_parse_and_validate(&ip->auth_key_cert, tok->object_body, tok->object_size, CERT_TYPE_AUTH_HS_IP_KEY, "introduction point auth-key") < 0) { goto err; } + /* Validate authentication certificate with descriptor signing key. */ + if (tor_cert_checksig(ip->auth_key_cert, + &desc->plaintext_data.signing_pubkey, 0) < 0) { + log_warn(LD_REND, "Invalid authentication key signature"); + goto err; + } - /* Exactly one "enc-key" ... */ + /* Exactly one "enc-key" SP "ntor" SP key NL */ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY); if (!strcmp(tok->args[0], "ntor")) { - /* "enc-key" SP "ntor" SP key NL */ - if (tok->n_args != 2 || tok->object_body) { - log_warn(LD_REND, "Introduction point ntor encryption key is invalid"); - goto err; - } + /* This field is using GE(2) so for possible forward compatibility, we + * accept more fields but must be at least 2. */ + tor_assert(tok->n_args >= 2); - if (curve25519_public_from_base64(&ip->enc_key.curve25519.pubkey, - tok->args[1]) < 0) { - log_warn(LD_REND, "Introduction point ntor encryption key is invalid"); - goto err; - } - ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519; - } else if (!strcmp(tok->args[0], "legacy")) { - /* "enc-key" SP "legacy" NL key NL */ - if (!tok->key) { - log_warn(LD_REND, "Introduction point legacy encryption key is " - "invalid"); + if (curve25519_public_from_base64(&ip->enc_key, tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor enc-key is invalid"); goto err; } - ip->enc_key.legacy = crypto_pk_dup_key(tok->key); - ip->enc_key_type = HS_DESC_KEY_TYPE_LEGACY; } else { /* Unknown key type so we can't use that introduction point. */ log_warn(LD_REND, "Introduction point encryption key is unrecognized."); goto err; } - /* "enc-key-certification" NL certificate NL */ - tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERTIFICATION); + /* Exactly once "enc-key-cert" NL certificate NL */ + tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERT); tor_assert(tok->object_body); /* Do the cross certification. */ - switch (ip->enc_key_type) { - case HS_DESC_KEY_TYPE_CURVE25519: - { - if (strcmp(tok->object_type, "ED25519 CERT")) { + if (strcmp(tok->object_type, "ED25519 CERT")) { log_warn(LD_REND, "Introduction point ntor encryption key " "cross-certification has an unknown format."); goto err; - } - if (cert_parse_and_validate(&cross_cert, tok->object_body, - tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS, - "introduction point enc-key-certification") < 0) { - goto err; - } - break; } - case HS_DESC_KEY_TYPE_LEGACY: - if (strcmp(tok->object_type, "CROSSCERT")) { - log_warn(LD_REND, "Introduction point legacy encryption key " - "cross-certification has an unknown format."); - goto err; - } - if (rsa_ed25519_crosscert_check((const uint8_t *) tok->object_body, - tok->object_size, ip->enc_key.legacy, - &desc->plaintext_data.signing_key_cert->signed_key, - approx_time()-86400)) { - log_warn(LD_REND, "Unable to check cross-certification on the " - "introduction point legacy encryption key."); - goto err; - } - break; - default: - tor_assert(0); - break; + if (cert_parse_and_validate(&ip->enc_key_cert, tok->object_body, + tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS, + "introduction point enc-key-cert") < 0) { + goto err; + } + if (tor_cert_checksig(ip->enc_key_cert, + &desc->plaintext_data.signing_pubkey, 0) < 0) { + log_warn(LD_REND, "Invalid encryption key signature"); + goto err; } /* It is successfully cross certified. Flag the object. */ ip->cross_certified = 1; + + /* Do we have a "legacy-key" SP key NL ?*/ + tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY); + if (tok) { + if (decode_intro_legacy_key(tok, tokens, ip, desc) < 0) { + goto err; + } + } + + /* Introduction point has been parsed successfully. */ goto done; err: @@ -1271,10 +1736,11 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) ip = NULL; done: - tor_cert_free(cross_cert); SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); smartlist_free(tokens); - memarea_drop_all(area); + if (area) { + memarea_drop_all(area); + } return ip; } @@ -1386,7 +1852,8 @@ desc_sig_is_valid(const char *b64_sig, sig_start = tor_memstr(encoded_desc, encoded_len, "\n" str_signature); /* Getting here means the token parsing worked for the signature so if we * can't find the start of the signature, we have a code flow issue. */ - if (BUG(!sig_start)) { + if (!sig_start) { + log_warn(LD_GENERAL, "Malformed signature line. Rejecting."); goto err; } /* Skip newline, it has to go in the signature check. */ @@ -1493,8 +1960,8 @@ desc_decode_plaintext_v3(smartlist_t *tokens, /* Copy the encrypted blob to the descriptor object so we can handle it * latter if needed. */ - desc->encrypted_blob = tor_memdup(tok->object_body, tok->object_size); - desc->encrypted_blob_size = tok->object_size; + desc->superencrypted_blob = tor_memdup(tok->object_body, tok->object_size); + desc->superencrypted_blob_size = tok->object_size; /* Extract signature and verify it. */ tok = find_by_keyword(tokens, R3_SIGNATURE); @@ -1528,10 +1995,9 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, tor_assert(desc); tor_assert(desc_encrypted_out); - /* Decrypt the encrypted data that is located in the plaintext section in - * the descriptor as a blob of bytes. The following functions will use the - * keys found in the same section. */ - message_len = desc_decrypt_data_v3(desc, &message); + /* Decrypt the superencrypted data that is located in the plaintext section + * in the descriptor as a blob of bytes. */ + message_len = desc_decrypt_all(desc, &message); if (!message_len) { log_warn(LD_REND, "Service descriptor decryption failed."); goto err; @@ -1557,7 +2023,7 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, } /* Authentication type. It's optional but only once. */ - tok = find_opt_by_keyword(tokens, R3_AUTHENTICATION_REQUIRED); + tok = find_opt_by_keyword(tokens, R3_INTRO_AUTH_REQUIRED); if (tok) { if (!decode_auth_type(desc_encrypted_out, tok->args[0])) { log_warn(LD_REND, "Service descriptor authentication type has " @@ -1639,7 +2105,7 @@ hs_desc_decode_encrypted(const hs_descriptor_t *desc, /* Calling this function without an encrypted blob to parse is a code flow * error. The plaintext parsing should never succeed in the first place * without an encrypted section. */ - tor_assert(desc->plaintext_data.encrypted_blob); + tor_assert(desc->plaintext_data.superencrypted_blob); /* Let's make sure we have a supported version as well. By correctly parsing * the plaintext, this should not fail. */ if (BUG(!hs_desc_is_supported_version(version))) { @@ -1891,6 +2357,6 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) { tor_assert(data); return (sizeof(*data) + sizeof(*data->signing_key_cert) + - data->encrypted_blob_size); + data->superencrypted_blob_size); } diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index b520d24471..136477ae3a 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -41,24 +41,11 @@ * the secret IV and MAC key length which is the length of H() output. */ #define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \ CIPHER256_KEY_LEN + CIPHER_IV_LEN + DIGEST256_LEN -/* We need to pad the plaintext version of the encrypted data section before - * encryption and it has to be a multiple of this value. */ -#define HS_DESC_PLAINTEXT_PADDING_MULTIPLE 128 -/* XXX: Let's make sure this makes sense as an upper limit for the padded - * plaintext section. Then we should enforce it as now only an assert will be - * triggered if we are above it. */ -/* Once padded, this is the maximum length in bytes for the plaintext. */ -#define HS_DESC_PADDED_PLAINTEXT_MAX_LEN 8192 -/* Minimum length in bytes of the encrypted portion of the descriptor. */ -#define HS_DESC_ENCRYPTED_MIN_LEN \ - HS_DESC_ENCRYPTED_SALT_LEN + \ - HS_DESC_PLAINTEXT_PADDING_MULTIPLE + DIGEST256_LEN +/* Pad plaintext of superencrypted data section before encryption so that its + * length is a multiple of this value. */ +#define HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE 10000 /* Maximum length in bytes of a full hidden service descriptor. */ #define HS_DESC_MAX_LEN 50000 /* 50kb max size */ -/* The minimum amount of fields a descriptor should contain. The parsing of - * the fields are version specific so the only required field, as a generic - * view of a descriptor, is 1 that is the version field. */ -#define HS_DESC_PLAINTEXT_MIN_FIELDS 1 /* Key length for the descriptor symmetric encryption. As specified in the * protocol, we use AES-256 for the encrypted section of the descriptor. The @@ -68,16 +55,9 @@ /* Type of authentication in the descriptor. */ typedef enum { - HS_DESC_AUTH_PASSWORD = 1, - HS_DESC_AUTH_ED25519 = 2, + HS_DESC_AUTH_ED25519 = 1 } hs_desc_auth_type_t; -/* Type of encryption key in the descriptor. */ -typedef enum { - HS_DESC_KEY_TYPE_LEGACY = 1, - HS_DESC_KEY_TYPE_CURVE25519 = 2, -} hs_desc_key_type_t; - /* Link specifier object that contains information on how to extend to the * relay that is the address, port and handshake type. */ typedef struct hs_desc_link_specifier_t { @@ -105,18 +85,29 @@ typedef struct hs_desc_intro_point_t { * the blinded key and in turn signs it. */ tor_cert_t *auth_key_cert; - /* Encryption key type so we know which one to use in the union below. */ - hs_desc_key_type_t enc_key_type; - - /* Keys are mutually exclusive thus the union. */ - union { - /* Encryption key used to encrypt request to hidden service. */ - curve25519_keypair_t curve25519; - - /* Backward compat: RSA 1024 encryption key for legacy purposes. - * Mutually exclusive with enc_key. */ - crypto_pk_t *legacy; - } enc_key; + /* Encryption key for the "ntor" type. */ + curve25519_public_key_t enc_key; + + /* Certificate cross certifying the descriptor signing key by the encryption + * curve25519 key. This certificate contains the signing key and is of type + * CERT_TYPE_CROSS_HS_IP_KEYS [0B]. */ + tor_cert_t *enc_key_cert; + + /* (Optional): If this introduction point is a legacy one that is version <= + * 0.2.9.x (HSIntro=3), we use this extra key for the intro point to be able + * to relay the cells to the service correctly. */ + struct { + /* RSA public key. */ + crypto_pk_t *key; + + /* Cross certified cert with the descriptor signing key (RSA->Ed). Because + * of the cross certification API, we need to keep the certificate binary + * blob and its length in order to properly encode it after. */ + struct { + uint8_t *encoded; + size_t len; + } cert; + } legacy; /* True iff the introduction point has passed the cross certification. Upon * decoding an intro point, this must be true. */ @@ -132,7 +123,7 @@ typedef struct hs_desc_encrypted_data_t { /* A list of authentication types that a client must at least support one * in order to contact the service. Contains NULL terminated strings. */ - smartlist_t *auth_types; + smartlist_t *intro_auth_types; /* Is this descriptor a single onion service? */ unsigned int single_onion_service : 1; @@ -167,11 +158,11 @@ typedef struct hs_desc_plaintext_data_t { * has changed. Spec specifies this as a 8 bytes positive integer. */ uint64_t revision_counter; - /* Decoding only: The base64-decoded encrypted blob from the descriptor */ - uint8_t *encrypted_blob; + /* Decoding only: The b64-decoded superencrypted blob from the descriptor */ + uint8_t *superencrypted_blob; - /* Decoding only: Size of the encrypted_blob */ - size_t encrypted_blob_size; + /* Decoding only: Size of the superencrypted_blob */ + size_t superencrypted_blob_size; } hs_desc_plaintext_data_t; /* Service descriptor in its decoded form. */ @@ -242,6 +233,10 @@ STATIC int desc_sig_is_valid(const char *b64_sig, const ed25519_public_key_t *signing_pubkey, const char *encoded_desc, size_t encoded_len); STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip); +STATIC size_t decode_superencrypted(const char *message, size_t message_len, + uint8_t **encrypted_out); +STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc); + #endif /* HS_DESCRIPTOR_PRIVATE */ #endif /* TOR_HS_DESCRIPTOR_H */ diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c index bc493e297e..06f8a2c3ad 100644 --- a/src/or/hs_intropoint.c +++ b/src/or/hs_intropoint.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -43,16 +43,16 @@ get_auth_key_from_cell(ed25519_public_key_t *auth_key_out, switch (cell_type) { case RELAY_COMMAND_ESTABLISH_INTRO: { - const hs_cell_establish_intro_t *c_cell = cell; - key_array = hs_cell_establish_intro_getconstarray_auth_key(c_cell); - auth_key_len = hs_cell_establish_intro_getlen_auth_key(c_cell); + const trn_cell_establish_intro_t *c_cell = cell; + key_array = trn_cell_establish_intro_getconstarray_auth_key(c_cell); + auth_key_len = trn_cell_establish_intro_getlen_auth_key(c_cell); break; } case RELAY_COMMAND_INTRODUCE1: { - const hs_cell_introduce1_t *c_cell = cell; - key_array = hs_cell_introduce1_getconstarray_auth_key(cell); - auth_key_len = hs_cell_introduce1_getlen_auth_key(c_cell); + const trn_cell_introduce1_t *c_cell = cell; + key_array = trn_cell_introduce1_getconstarray_auth_key(cell); + auth_key_len = trn_cell_introduce1_getlen_auth_key(c_cell); break; } default: @@ -68,22 +68,22 @@ get_auth_key_from_cell(ed25519_public_key_t *auth_key_out, /** We received an ESTABLISH_INTRO <b>cell</b>. Verify its signature and MAC, * given <b>circuit_key_material</b>. Return 0 on success else -1 on error. */ STATIC int -verify_establish_intro_cell(const hs_cell_establish_intro_t *cell, +verify_establish_intro_cell(const trn_cell_establish_intro_t *cell, const uint8_t *circuit_key_material, size_t circuit_key_material_len) { /* We only reach this function if the first byte of the cell is 0x02 which - * means that auth_key_type is AUTH_KEY_ED25519, hence this check should + * means that auth_key_type is of ed25519 type, hence this check should * always pass. See hs_intro_received_establish_intro(). */ - if (BUG(cell->auth_key_type != AUTH_KEY_ED25519)) { + if (BUG(cell->auth_key_type != HS_INTRO_AUTH_KEY_TYPE_ED25519)) { return -1; } /* Make sure the auth key length is of the right size for this type. For * EXTRA safety, we check both the size of the array and the length which * must be the same. Safety first!*/ - if (hs_cell_establish_intro_getlen_auth_key(cell) != ED25519_PUBKEY_LEN || - hs_cell_establish_intro_get_auth_key_len(cell) != ED25519_PUBKEY_LEN) { + if (trn_cell_establish_intro_getlen_auth_key(cell) != ED25519_PUBKEY_LEN || + trn_cell_establish_intro_get_auth_key_len(cell) != ED25519_PUBKEY_LEN) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "ESTABLISH_INTRO auth key length is invalid"); return -1; @@ -94,13 +94,14 @@ verify_establish_intro_cell(const hs_cell_establish_intro_t *cell, /* Verify the sig */ { ed25519_signature_t sig_struct; - const uint8_t *sig_array = hs_cell_establish_intro_getconstarray_sig(cell); + const uint8_t *sig_array = + trn_cell_establish_intro_getconstarray_sig(cell); /* Make sure the signature length is of the right size. For EXTRA safety, * we check both the size of the array and the length which must be the * same. Safety first!*/ - if (hs_cell_establish_intro_getlen_sig(cell) != sizeof(sig_struct.sig) || - hs_cell_establish_intro_get_sig_len(cell) != sizeof(sig_struct.sig)) { + if (trn_cell_establish_intro_getlen_sig(cell) != sizeof(sig_struct.sig) || + trn_cell_establish_intro_get_sig_len(cell) != sizeof(sig_struct.sig)) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "ESTABLISH_INTRO sig len is invalid"); return -1; @@ -147,21 +148,21 @@ hs_intro_send_intro_established_cell,(or_circuit_t *circ)) int ret; uint8_t *encoded_cell = NULL; ssize_t encoded_len, result_len; - hs_cell_intro_established_t *cell; - cell_extension_t *ext; + trn_cell_intro_established_t *cell; + trn_cell_extension_t *ext; tor_assert(circ); /* Build the cell payload. */ - cell = hs_cell_intro_established_new(); - ext = cell_extension_new(); - cell_extension_set_num(ext, 0); - hs_cell_intro_established_set_extensions(cell, ext); + cell = trn_cell_intro_established_new(); + ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + trn_cell_intro_established_set_extensions(cell, ext); /* Encode the cell to binary format. */ - encoded_len = hs_cell_intro_established_encoded_len(cell); + encoded_len = trn_cell_intro_established_encoded_len(cell); tor_assert(encoded_len > 0); encoded_cell = tor_malloc_zero(encoded_len); - result_len = hs_cell_intro_established_encode(encoded_cell, encoded_len, + result_len = trn_cell_intro_established_encode(encoded_cell, encoded_len, cell); tor_assert(encoded_len == result_len); @@ -170,7 +171,7 @@ hs_intro_send_intro_established_cell,(or_circuit_t *circ)) (char *) encoded_cell, encoded_len, NULL); /* On failure, the above function will close the circuit. */ - hs_cell_intro_established_free(cell); + trn_cell_intro_established_free(cell); tor_free(encoded_cell); return ret; } @@ -180,7 +181,7 @@ hs_intro_send_intro_established_cell,(or_circuit_t *circ)) * establish an intro point. */ static int handle_verified_establish_intro_cell(or_circuit_t *circ, - const hs_cell_establish_intro_t *parsed_cell) + const trn_cell_establish_intro_t *parsed_cell) { /* Get the auth key of this intro point */ ed25519_public_key_t auth_key; @@ -195,7 +196,7 @@ handle_verified_establish_intro_cell(or_circuit_t *circ, } /* Associate intro point auth key with this circuit. */ - hs_circuitmap_register_intro_circ_v3(circ, &auth_key); + hs_circuitmap_register_intro_circ_v3_relay_side(circ, &auth_key); /* Repurpose this circuit into an intro circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT); @@ -210,7 +211,7 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request, size_t request_len) { int cell_ok, retval = -1; - hs_cell_establish_intro_t *parsed_cell = NULL; + trn_cell_establish_intro_t *parsed_cell = NULL; tor_assert(circ); tor_assert(request); @@ -224,7 +225,7 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request, } /* Parse the cell */ - ssize_t parsing_result = hs_cell_establish_intro_parse(&parsed_cell, + ssize_t parsing_result = trn_cell_establish_intro_parse(&parsed_cell, request, request_len); if (parsing_result < 0) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, @@ -260,7 +261,7 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request, } done: - hs_cell_establish_intro_free(parsed_cell); + trn_cell_establish_intro_free(parsed_cell); return retval; } @@ -340,28 +341,28 @@ send_introduce_ack_cell(or_circuit_t *circ, hs_intro_ack_status_t status) int ret = -1; uint8_t *encoded_cell = NULL; ssize_t encoded_len, result_len; - hs_cell_introduce_ack_t *cell; - cell_extension_t *ext; + trn_cell_introduce_ack_t *cell; + trn_cell_extension_t *ext; tor_assert(circ); /* Setup the INTRODUCE_ACK cell. We have no extensions so the N_EXTENSIONS * field is set to 0 by default with a new object. */ - cell = hs_cell_introduce_ack_new(); - ret = hs_cell_introduce_ack_set_status(cell, status); + cell = trn_cell_introduce_ack_new(); + ret = trn_cell_introduce_ack_set_status(cell, status); /* We have no cell extensions in an INTRODUCE_ACK cell. */ - ext = cell_extension_new(); - cell_extension_set_num(ext, 0); - hs_cell_introduce_ack_set_extensions(cell, ext); + ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce_ack_set_extensions(cell, ext); /* A wrong status is a very bad code flow error as this value is controlled * by the code in this file and not an external input. This means we use a * code that is not known by the trunnel ABI. */ tor_assert(ret == 0); /* Encode the payload. We should never fail to get the encoded length. */ - encoded_len = hs_cell_introduce_ack_encoded_len(cell); + encoded_len = trn_cell_introduce_ack_encoded_len(cell); tor_assert(encoded_len > 0); encoded_cell = tor_malloc_zero(encoded_len); - result_len = hs_cell_introduce_ack_encode(encoded_cell, encoded_len, cell); + result_len = trn_cell_introduce_ack_encode(encoded_cell, encoded_len, cell); tor_assert(encoded_len == result_len); ret = relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), @@ -369,7 +370,7 @@ send_introduce_ack_cell(or_circuit_t *circ, hs_intro_ack_status_t status) (char *) encoded_cell, encoded_len, NULL); /* On failure, the above function will close the circuit. */ - hs_cell_introduce_ack_free(cell); + trn_cell_introduce_ack_free(cell); tor_free(encoded_cell); return ret; } @@ -377,7 +378,7 @@ send_introduce_ack_cell(or_circuit_t *circ, hs_intro_ack_status_t status) /* Validate a parsed INTRODUCE1 <b>cell</b>. Return 0 if valid or else a * negative value for an invalid cell that should be NACKed. */ STATIC int -validate_introduce1_parsed_cell(const hs_cell_introduce1_t *cell) +validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell) { size_t legacy_key_id_len; const uint8_t *legacy_key_id; @@ -386,29 +387,29 @@ validate_introduce1_parsed_cell(const hs_cell_introduce1_t *cell) /* This code path SHOULD NEVER be reached if the cell is a legacy type so * safety net here. The legacy ID must be zeroes in this case. */ - legacy_key_id_len = hs_cell_introduce1_getlen_legacy_key_id(cell); - legacy_key_id = hs_cell_introduce1_getconstarray_legacy_key_id(cell); + legacy_key_id_len = trn_cell_introduce1_getlen_legacy_key_id(cell); + legacy_key_id = trn_cell_introduce1_getconstarray_legacy_key_id(cell); if (BUG(!tor_mem_is_zero((char *) legacy_key_id, legacy_key_id_len))) { goto invalid; } /* The auth key of an INTRODUCE1 should be of type ed25519 thus leading to a * known fixed length as well. */ - if (hs_cell_introduce1_get_auth_key_type(cell) != + if (trn_cell_introduce1_get_auth_key_type(cell) != HS_INTRO_AUTH_KEY_TYPE_ED25519) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Rejecting invalid INTRODUCE1 cell auth key type. " "Responding with NACK."); goto invalid; } - if (hs_cell_introduce1_get_auth_key_len(cell) != ED25519_PUBKEY_LEN || - hs_cell_introduce1_getlen_auth_key(cell) != ED25519_PUBKEY_LEN) { + if (trn_cell_introduce1_get_auth_key_len(cell) != ED25519_PUBKEY_LEN || + trn_cell_introduce1_getlen_auth_key(cell) != ED25519_PUBKEY_LEN) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Rejecting invalid INTRODUCE1 cell auth key length. " "Responding with NACK."); goto invalid; } - if (hs_cell_introduce1_getlen_encrypted(cell) == 0) { + if (trn_cell_introduce1_getlen_encrypted(cell) == 0) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Rejecting invalid INTRODUCE1 cell encrypted length. " "Responding with NACK."); @@ -431,7 +432,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, { int ret = -1; or_circuit_t *service_circ; - hs_cell_introduce1_t *parsed_cell; + trn_cell_introduce1_t *parsed_cell; hs_intro_ack_status_t status = HS_INTRO_ACK_STATUS_SUCCESS; tor_assert(client_circ); @@ -440,7 +441,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, /* Parse cell. Note that we can only parse the non encrypted section for * which we'll use the authentication key to find the service introduction * circuit and relay the cell on it. */ - ssize_t cell_size = hs_cell_introduce1_parse(&parsed_cell, request, + ssize_t cell_size = trn_cell_introduce1_parse(&parsed_cell, request, request_len); if (cell_size < 0) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, @@ -462,7 +463,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, { ed25519_public_key_t auth_key; get_auth_key_from_cell(&auth_key, RELAY_COMMAND_INTRODUCE1, parsed_cell); - service_circ = hs_circuitmap_get_intro_circ_v3(&auth_key); + service_circ = hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key); if (service_circ == NULL) { char b64_key[ED25519_BASE64_LEN + 1]; ed25519_public_to_base64(b64_key, &auth_key); @@ -506,7 +507,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, circuit_mark_for_close(TO_CIRCUIT(client_circ), END_CIRC_REASON_INTERNAL); } done: - hs_cell_introduce1_free(parsed_cell); + trn_cell_introduce1_free(parsed_cell); return ret; } diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h index e6024a858f..163ed810e7 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -41,7 +41,7 @@ int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ); #include "hs/cell_introduce1.h" STATIC int -verify_establish_intro_cell(const hs_cell_establish_intro_t *out, +verify_establish_intro_cell(const trn_cell_establish_intro_t *out, const uint8_t *circuit_key_material, size_t circuit_key_material_len); @@ -52,7 +52,7 @@ get_auth_key_from_cell(ed25519_public_key_t *auth_key_out, STATIC int introduce1_cell_is_legacy(const uint8_t *request); STATIC int handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, size_t request_len); -STATIC int validate_introduce1_parsed_cell(const hs_cell_introduce1_t *cell); +STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell); STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ); #endif /* HS_INTROPOINT_PRIVATE */ diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c new file mode 100644 index 0000000000..119899817e --- /dev/null +++ b/src/or/hs_ntor.c @@ -0,0 +1,626 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** \file hs_ntor.c + * \brief Implements the ntor variant used in Tor hidden services. + * + * \details + * This module handles the variant of the ntor handshake that is documented in + * section [NTOR-WITH-EXTRA-DATA] of rend-spec-ng.txt . + * + * The functions in this file provide an API that should be used when sending + * or receiving INTRODUCE1/RENDEZVOUS1 cells to generate the various key + * material required to create and handle those cells. + * + * In the case of INTRODUCE1 it provides encryption and MAC keys to + * encode/decode the encrypted blob (see hs_ntor_intro_cell_keys_t). The + * relevant pub functions are hs_ntor_{client,service}_get_introduce1_keys(). + * + * In the case of RENDEZVOUS1 it calculates the MAC required to authenticate + * the cell, and also provides the key seed that is used to derive the crypto + * material for rendezvous encryption (see hs_ntor_rend_cell_keys_t). The + * relevant pub functions are hs_ntor_{client,service}_get_rendezvous1_keys(). + * It also provides a function (hs_ntor_circuit_key_expansion()) that does the + * rendezvous key expansion to setup end-to-end rend circuit keys. + */ + +#include "or.h" +#include "hs_ntor.h" + +/* String constants used by the ntor HS protocol */ +#define PROTOID "tor-hs-ntor-curve25519-sha3-256-1" +#define PROTOID_LEN (sizeof(PROTOID) - 1) +#define SERVER_STR "Server" +#define SERVER_STR_LEN (sizeof(SERVER_STR) - 1) + +/* Protocol-specific tweaks to our crypto inputs */ +#define T_HSENC PROTOID ":hs_key_extract" +#define T_HSENC_LEN (sizeof(T_HSENC) - 1) +#define T_HSVERIFY PROTOID ":hs_verify" +#define T_HSMAC PROTOID ":hs_mac" +#define M_HSEXPAND PROTOID ":hs_key_expand" +#define M_HSEXPAND_LEN (sizeof(M_HSEXPAND) - 1) + +/************************* Helper functions: *******************************/ + +/** Helper macro: copy <b>len</b> bytes from <b>inp</b> to <b>ptr</b> and + *advance <b>ptr</b> by the number of bytes copied. Stolen from onion_ntor.c */ +#define APPEND(ptr, inp, len) \ + STMT_BEGIN { \ + memcpy(ptr, (inp), (len)); \ + ptr += len; \ + } STMT_END + +/* Length of EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID */ +#define REND_SECRET_HS_INPUT_LEN (CURVE25519_OUTPUT_LEN * 2 + \ + ED25519_PUBKEY_LEN + CURVE25519_PUBKEY_LEN * 3 + PROTOID_LEN) +/* Length of auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" */ +#define REND_AUTH_INPUT_LEN (DIGEST256_LEN + ED25519_PUBKEY_LEN + \ + CURVE25519_PUBKEY_LEN * 3 + PROTOID_LEN + SERVER_STR_LEN) + +/** Helper function: Compute the last part of the HS ntor handshake which + * derives key material necessary to create and handle RENDEZVOUS1 + * cells. Function used by both client and service. The actual calculations is + * as follows: + * + * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc) + * verify = MAC(rend_secret_hs_input, t_hsverify) + * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" + * auth_input_mac = MAC(auth_input, t_hsmac) + * + * where in the above, AUTH_KEY is <b>intro_auth_pubkey</b>, B is + * <b>intro_enc_pubkey</b>, Y is <b>service_ephemeral_rend_pubkey</b>, and X + * is <b>client_ephemeral_enc_pubkey</b>. The provided + * <b>rend_secret_hs_input</b> is of size REND_SECRET_HS_INPUT_LEN. + * + * The final results of NTOR_KEY_SEED and auth_input_mac are placed in + * <b>hs_ntor_rend_cell_keys_out</b>. Return 0 if everything went fine. */ +static int +get_rendezvous1_key_material(const uint8_t *rend_secret_hs_input, + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_public_key_t *service_ephemeral_rend_pubkey, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out) +{ + int bad = 0; + uint8_t ntor_key_seed[DIGEST256_LEN]; + uint8_t ntor_verify[DIGEST256_LEN]; + uint8_t rend_auth_input[REND_AUTH_INPUT_LEN]; + uint8_t rend_cell_auth[DIGEST256_LEN]; + uint8_t *ptr; + + /* Let's build NTOR_KEY_SEED */ + crypto_mac_sha3_256(ntor_key_seed, sizeof(ntor_key_seed), + rend_secret_hs_input, REND_SECRET_HS_INPUT_LEN, + (const uint8_t *)T_HSENC, strlen(T_HSENC)); + bad |= safe_mem_is_zero(ntor_key_seed, DIGEST256_LEN); + + /* Let's build ntor_verify */ + crypto_mac_sha3_256(ntor_verify, sizeof(ntor_verify), + rend_secret_hs_input, REND_SECRET_HS_INPUT_LEN, + (const uint8_t *)T_HSVERIFY, strlen(T_HSVERIFY)); + bad |= safe_mem_is_zero(ntor_verify, DIGEST256_LEN); + + /* Let's build auth_input: */ + ptr = rend_auth_input; + /* Append ntor_verify */ + APPEND(ptr, ntor_verify, sizeof(ntor_verify)); + /* Append AUTH_KEY */ + APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN); + /* Append B */ + APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append Y */ + APPEND(ptr, + service_ephemeral_rend_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append X */ + APPEND(ptr, + client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append PROTOID */ + APPEND(ptr, PROTOID, strlen(PROTOID)); + /* Append "Server" */ + APPEND(ptr, SERVER_STR, strlen(SERVER_STR)); + tor_assert(ptr == rend_auth_input + sizeof(rend_auth_input)); + + /* Let's build auth_input_mac that goes in RENDEZVOUS1 cell */ + crypto_mac_sha3_256(rend_cell_auth, sizeof(rend_cell_auth), + rend_auth_input, sizeof(rend_auth_input), + (const uint8_t *)T_HSMAC, strlen(T_HSMAC)); + bad |= safe_mem_is_zero(ntor_verify, DIGEST256_LEN); + + { /* Get the computed RENDEZVOUS1 material! */ + memcpy(&hs_ntor_rend_cell_keys_out->rend_cell_auth_mac, + rend_cell_auth, DIGEST256_LEN); + memcpy(&hs_ntor_rend_cell_keys_out->ntor_key_seed, + ntor_key_seed, DIGEST256_LEN); + } + + memwipe(rend_cell_auth, 0, sizeof(rend_cell_auth)); + memwipe(rend_auth_input, 0, sizeof(rend_auth_input)); + memwipe(ntor_key_seed, 0, sizeof(ntor_key_seed)); + + return bad; +} + +/** Length of secret_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID */ +#define INTRO_SECRET_HS_INPUT_LEN (CURVE25519_OUTPUT_LEN +ED25519_PUBKEY_LEN +\ + CURVE25519_PUBKEY_LEN + CURVE25519_PUBKEY_LEN + PROTOID_LEN) +/* Length of info = m_hsexpand | subcredential */ +#define INFO_BLOB_LEN (M_HSEXPAND_LEN + DIGEST256_LEN) +/* Length of KDF input = intro_secret_hs_input | t_hsenc | info */ +#define KDF_INPUT_LEN (INTRO_SECRET_HS_INPUT_LEN + T_HSENC_LEN + INFO_BLOB_LEN) + +/** Helper function: Compute the part of the HS ntor handshake that generates + * key material for creating and handling INTRODUCE1 cells. Function used + * by both client and service. Specifically, calculate the following: + * + * info = m_hsexpand | subcredential + * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) + * ENC_KEY = hs_keys[0:S_KEY_LEN] + * MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] + * + * where intro_secret_hs_input is <b>secret_input</b> (of size + * INTRO_SECRET_HS_INPUT_LEN), and <b>subcredential</b> is of size + * DIGEST256_LEN. + * + * If everything went well, fill <b>hs_ntor_intro_cell_keys_out</b> with the + * necessary key material, and return 0. */ +static void +get_introduce1_key_material(const uint8_t *secret_input, + const uint8_t *subcredential, + hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out) +{ + uint8_t keystream[CIPHER256_KEY_LEN + DIGEST256_LEN]; + uint8_t info_blob[INFO_BLOB_LEN]; + uint8_t kdf_input[KDF_INPUT_LEN]; + crypto_xof_t *xof; + uint8_t *ptr; + + /* Let's build info */ + ptr = info_blob; + APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); + APPEND(ptr, subcredential, DIGEST256_LEN); + tor_assert(ptr == info_blob + sizeof(info_blob)); + + /* Let's build the input to the KDF */ + ptr = kdf_input; + APPEND(ptr, secret_input, INTRO_SECRET_HS_INPUT_LEN); + APPEND(ptr, T_HSENC, strlen(T_HSENC)); + APPEND(ptr, info_blob, sizeof(info_blob)); + tor_assert(ptr == kdf_input + sizeof(kdf_input)); + + /* Now we need to run kdf_input over SHAKE-256 */ + xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); + crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)) ; + crypto_xof_free(xof); + + { /* Get the keys */ + memcpy(&hs_ntor_intro_cell_keys_out->enc_key, keystream,CIPHER256_KEY_LEN); + memcpy(&hs_ntor_intro_cell_keys_out->mac_key, + keystream+CIPHER256_KEY_LEN, DIGEST256_LEN); + } + + memwipe(keystream, 0, sizeof(keystream)); + memwipe(kdf_input, 0, sizeof(kdf_input)); +} + +/** Helper function: Calculate the 'intro_secret_hs_input' element used by the + * HS ntor handshake and place it in <b>secret_input_out</b>. This function is + * used by both client and service code. + * + * For the client-side it looks like this: + * + * intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID + * + * whereas for the service-side it looks like this: + * + * intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID + * + * In this function, <b>dh_result</b> carries the EXP() result (and has size + * CURVE25519_OUTPUT_LEN) <b>intro_auth_pubkey</b> is AUTH_KEY, + * <b>client_ephemeral_enc_pubkey</b> is X, and <b>intro_enc_pubkey</b> is B. + */ +static void +get_intro_secret_hs_input(const uint8_t *dh_result, + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + uint8_t *secret_input_out) +{ + uint8_t *ptr; + + /* Append EXP() */ + ptr = secret_input_out; + APPEND(ptr, dh_result, CURVE25519_OUTPUT_LEN); + /* Append AUTH_KEY */ + APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN); + /* Append X */ + APPEND(ptr, client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append B */ + APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append PROTOID */ + APPEND(ptr, PROTOID, strlen(PROTOID)); + tor_assert(ptr == secret_input_out + INTRO_SECRET_HS_INPUT_LEN); +} + +/** Calculate the 'rend_secret_hs_input' element used by the HS ntor handshake + * and place it in <b>rend_secret_hs_input_out</b>. This function is used by + * both client and service code. + * + * The computation on the client side is: + * rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID + * whereas on the service side it is: + * rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID + * + * where: + * <b>dh_result1</b> and <b>dh_result2</b> carry the two EXP() results (of size + * CURVE25519_OUTPUT_LEN) + * <b>intro_auth_pubkey</b> is AUTH_KEY, + * <b>intro_enc_pubkey</b> is B, + * <b>client_ephemeral_enc_pubkey</b> is X, and + * <b>service_ephemeral_rend_pubkey</b> is Y. + */ +static void +get_rend_secret_hs_input(const uint8_t *dh_result1, const uint8_t *dh_result2, + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + const curve25519_public_key_t *service_ephemeral_rend_pubkey, + uint8_t *rend_secret_hs_input_out) +{ + uint8_t *ptr; + + ptr = rend_secret_hs_input_out; + /* Append the first EXP() */ + APPEND(ptr, dh_result1, CURVE25519_OUTPUT_LEN); + /* Append the other EXP() */ + APPEND(ptr, dh_result2, CURVE25519_OUTPUT_LEN); + /* Append AUTH_KEY */ + APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN); + /* Append B */ + APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append X */ + APPEND(ptr, + client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append Y */ + APPEND(ptr, + service_ephemeral_rend_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append PROTOID */ + APPEND(ptr, PROTOID, strlen(PROTOID)); + tor_assert(ptr == rend_secret_hs_input_out + REND_SECRET_HS_INPUT_LEN); +} + +/************************* Public functions: *******************************/ + +/* Public function: Do the appropriate ntor calculations and derive the keys + * needed to encrypt and authenticate INTRODUCE1 cells. Return 0 and place the + * final key material in <b>hs_ntor_intro_cell_keys_out</b> if everything went + * well, otherwise return -1; + * + * The relevant calculations are as follows: + * + * intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID + * info = m_hsexpand | subcredential + * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) + * ENC_KEY = hs_keys[0:S_KEY_LEN] + * MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] + * + * where: + * <b>intro_auth_pubkey</b> is AUTH_KEY (found in HS descriptor), + * <b>intro_enc_pubkey</b> is B (also found in HS descriptor), + * <b>client_ephemeral_enc_keypair</b> is freshly generated keypair (x,X) + * <b>subcredential</b> is the hidden service subcredential (of size + * DIGEST256_LEN). */ +int +hs_ntor_client_get_introduce1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + const uint8_t *subcredential, + hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out) +{ + int bad = 0; + uint8_t secret_input[INTRO_SECRET_HS_INPUT_LEN]; + uint8_t dh_result[CURVE25519_OUTPUT_LEN]; + + tor_assert(intro_auth_pubkey); + tor_assert(intro_enc_pubkey); + tor_assert(client_ephemeral_enc_keypair); + tor_assert(subcredential); + tor_assert(hs_ntor_intro_cell_keys_out); + + /* Calculate EXP(B,x) */ + curve25519_handshake(dh_result, + &client_ephemeral_enc_keypair->seckey, + intro_enc_pubkey); + bad |= safe_mem_is_zero(dh_result, CURVE25519_OUTPUT_LEN); + + /* Get intro_secret_hs_input */ + get_intro_secret_hs_input(dh_result, intro_auth_pubkey, + &client_ephemeral_enc_keypair->pubkey, + intro_enc_pubkey, secret_input); + bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN); + + /* Get ENC_KEY and MAC_KEY! */ + get_introduce1_key_material(secret_input, subcredential, + hs_ntor_intro_cell_keys_out); + + /* Cleanup */ + memwipe(secret_input, 0, sizeof(secret_input)); + if (bad) { + memwipe(hs_ntor_intro_cell_keys_out, 0, sizeof(hs_ntor_intro_cell_keys_t)); + } + + return bad ? -1 : 0; +} + +/* Public function: Do the appropriate ntor calculations and derive the keys + * needed to verify RENDEZVOUS1 cells and encrypt further rendezvous + * traffic. Return 0 and place the final key material in + * <b>hs_ntor_rend_cell_keys_out</b> if everything went well, else return -1. + * + * The relevant calculations are as follows: + * + * rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID + * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc) + * verify = MAC(rend_secret_hs_input, t_hsverify) + * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" + * auth_input_mac = MAC(auth_input, t_hsmac) + * + * where: + * <b>intro_auth_pubkey</b> is AUTH_KEY (found in HS descriptor), + * <b>client_ephemeral_enc_keypair</b> is freshly generated keypair (x,X) + * <b>intro_enc_pubkey</b> is B (also found in HS descriptor), + * <b>service_ephemeral_rend_pubkey</b> is Y (SERVER_PK in RENDEZVOUS1 cell) */ +int +hs_ntor_client_get_rendezvous1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_public_key_t *service_ephemeral_rend_pubkey, + hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out) +{ + int bad = 0; + uint8_t rend_secret_hs_input[REND_SECRET_HS_INPUT_LEN]; + uint8_t dh_result1[CURVE25519_OUTPUT_LEN]; + uint8_t dh_result2[CURVE25519_OUTPUT_LEN]; + + tor_assert(intro_auth_pubkey); + tor_assert(client_ephemeral_enc_keypair); + tor_assert(intro_enc_pubkey); + tor_assert(service_ephemeral_rend_pubkey); + tor_assert(hs_ntor_rend_cell_keys_out); + + /* Compute EXP(Y, x) */ + curve25519_handshake(dh_result1, + &client_ephemeral_enc_keypair->seckey, + service_ephemeral_rend_pubkey); + bad |= safe_mem_is_zero(dh_result1, CURVE25519_OUTPUT_LEN); + + /* Compute EXP(B, x) */ + curve25519_handshake(dh_result2, + &client_ephemeral_enc_keypair->seckey, + intro_enc_pubkey); + bad |= safe_mem_is_zero(dh_result2, CURVE25519_OUTPUT_LEN); + + /* Get rend_secret_hs_input */ + get_rend_secret_hs_input(dh_result1, dh_result2, + intro_auth_pubkey, intro_enc_pubkey, + &client_ephemeral_enc_keypair->pubkey, + service_ephemeral_rend_pubkey, + rend_secret_hs_input); + + /* Get NTOR_KEY_SEED and the auth_input MAC */ + bad |= get_rendezvous1_key_material(rend_secret_hs_input, + intro_auth_pubkey, + intro_enc_pubkey, + service_ephemeral_rend_pubkey, + &client_ephemeral_enc_keypair->pubkey, + hs_ntor_rend_cell_keys_out); + + memwipe(rend_secret_hs_input, 0, sizeof(rend_secret_hs_input)); + if (bad) { + memwipe(hs_ntor_rend_cell_keys_out, 0, sizeof(hs_ntor_rend_cell_keys_t)); + } + + return bad ? -1 : 0; +} + +/* Public function: Do the appropriate ntor calculations and derive the keys + * needed to decrypt and verify INTRODUCE1 cells. Return 0 and place the final + * key material in <b>hs_ntor_intro_cell_keys_out</b> if everything went well, + * otherwise return -1; + * + * The relevant calculations are as follows: + * + * intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID + * info = m_hsexpand | subcredential + * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) + * HS_DEC_KEY = hs_keys[0:S_KEY_LEN] + * HS_MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] + * + * where: + * <b>intro_auth_pubkey</b> is AUTH_KEY (introduction point auth key), + * <b>intro_enc_keypair</b> is (b,B) (introduction point encryption keypair), + * <b>client_ephemeral_enc_pubkey</b> is X (CLIENT_PK in INTRODUCE2 cell), + * <b>subcredential</b> is the HS subcredential (of size DIGEST256_LEN) */ +int +hs_ntor_service_get_introduce1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *intro_enc_keypair, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + const uint8_t *subcredential, + hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out) +{ + int bad = 0; + uint8_t secret_input[INTRO_SECRET_HS_INPUT_LEN]; + uint8_t dh_result[CURVE25519_OUTPUT_LEN]; + + tor_assert(intro_auth_pubkey); + tor_assert(intro_enc_keypair); + tor_assert(client_ephemeral_enc_pubkey); + tor_assert(subcredential); + tor_assert(hs_ntor_intro_cell_keys_out); + + /* Compute EXP(X, b) */ + curve25519_handshake(dh_result, + &intro_enc_keypair->seckey, + client_ephemeral_enc_pubkey); + bad |= safe_mem_is_zero(dh_result, CURVE25519_OUTPUT_LEN); + + /* Get intro_secret_hs_input */ + get_intro_secret_hs_input(dh_result, intro_auth_pubkey, + client_ephemeral_enc_pubkey, + &intro_enc_keypair->pubkey, + secret_input); + bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN); + + /* Get ENC_KEY and MAC_KEY! */ + get_introduce1_key_material(secret_input, subcredential, + hs_ntor_intro_cell_keys_out); + + memwipe(secret_input, 0, sizeof(secret_input)); + if (bad) { + memwipe(hs_ntor_intro_cell_keys_out, 0, sizeof(hs_ntor_intro_cell_keys_t)); + } + + return bad ? -1 : 0; +} + +/* Public function: Do the appropriate ntor calculations and derive the keys + * needed to create and authenticate RENDEZVOUS1 cells. Return 0 and place the + * final key material in <b>hs_ntor_rend_cell_keys_out</b> if all went fine, + * return -1 if error happened. + * + * The relevant calculations are as follows: + * + * rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID + * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc) + * verify = MAC(rend_secret_hs_input, t_hsverify) + * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" + * auth_input_mac = MAC(auth_input, t_hsmac) + * + * where: + * <b>intro_auth_pubkey</b> is AUTH_KEY (intro point auth key), + * <b>intro_enc_keypair</b> is (b,B) (intro point enc keypair) + * <b>service_ephemeral_rend_keypair</b> is a fresh (y,Y) keypair + * <b>client_ephemeral_enc_pubkey</b> is X (CLIENT_PK in INTRODUCE2 cell) */ +int +hs_ntor_service_get_rendezvous1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *intro_enc_keypair, + const curve25519_keypair_t *service_ephemeral_rend_keypair, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out) +{ + int bad = 0; + uint8_t rend_secret_hs_input[REND_SECRET_HS_INPUT_LEN]; + uint8_t dh_result1[CURVE25519_OUTPUT_LEN]; + uint8_t dh_result2[CURVE25519_OUTPUT_LEN]; + + tor_assert(intro_auth_pubkey); + tor_assert(intro_enc_keypair); + tor_assert(service_ephemeral_rend_keypair); + tor_assert(client_ephemeral_enc_pubkey); + tor_assert(hs_ntor_rend_cell_keys_out); + + /* Compute EXP(X, y) */ + curve25519_handshake(dh_result1, + &service_ephemeral_rend_keypair->seckey, + client_ephemeral_enc_pubkey); + bad |= safe_mem_is_zero(dh_result1, CURVE25519_OUTPUT_LEN); + + /* Compute EXP(X, b) */ + curve25519_handshake(dh_result2, + &intro_enc_keypair->seckey, + client_ephemeral_enc_pubkey); + bad |= safe_mem_is_zero(dh_result2, CURVE25519_OUTPUT_LEN); + + /* Get rend_secret_hs_input */ + get_rend_secret_hs_input(dh_result1, dh_result2, + intro_auth_pubkey, + &intro_enc_keypair->pubkey, + client_ephemeral_enc_pubkey, + &service_ephemeral_rend_keypair->pubkey, + rend_secret_hs_input); + + /* Get NTOR_KEY_SEED and AUTH_INPUT_MAC! */ + bad |= get_rendezvous1_key_material(rend_secret_hs_input, + intro_auth_pubkey, + &intro_enc_keypair->pubkey, + &service_ephemeral_rend_keypair->pubkey, + client_ephemeral_enc_pubkey, + hs_ntor_rend_cell_keys_out); + + memwipe(rend_secret_hs_input, 0, sizeof(rend_secret_hs_input)); + if (bad) { + memwipe(hs_ntor_rend_cell_keys_out, 0, sizeof(hs_ntor_rend_cell_keys_t)); + } + + return bad ? -1 : 0; +} + +/** Given a received RENDEZVOUS2 MAC in <b>mac</b> (of length DIGEST256_LEN), + * and the RENDEZVOUS1 key material in <b>hs_ntor_rend_cell_keys</b>, return 1 + * if the MAC is good, otherwise return 0. */ +int +hs_ntor_client_rendezvous2_mac_is_good( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys, + const uint8_t *rcvd_mac) +{ + tor_assert(rcvd_mac); + tor_assert(hs_ntor_rend_cell_keys); + + return tor_memeq(hs_ntor_rend_cell_keys->rend_cell_auth_mac, + rcvd_mac, DIGEST256_LEN); +} + +/* Input length to KDF for key expansion */ +#define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN) +/* Output length of KDF for key expansion */ +#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2) + +/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the + * circuit key expansion as specified by section '4.2.1. Key expansion' and + * return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */ +hs_ntor_rend_circuit_keys_t * +hs_ntor_circuit_key_expansion( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys) +{ + uint8_t *ptr; + uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN]; + uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN]; + crypto_xof_t *xof; + hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL; + + /* Let's build the input to the KDF */ + ptr = kdf_input; + APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN); + APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); + tor_assert(ptr == kdf_input + sizeof(kdf_input)); + + /* Generate the keys */ + xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); + crypto_xof_squeeze_bytes(xof, keys, sizeof(keys)); + crypto_xof_free(xof); + + /* Generate keys structure and assign keys to it */ + rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t)); + ptr = keys; + memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN); + ptr += DIGEST256_LEN;; + memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN); + ptr += DIGEST256_LEN; + memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN); + ptr += DIGEST256_LEN; + memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN); + ptr += CIPHER256_KEY_LEN; + memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN); + ptr += CIPHER256_KEY_LEN; + tor_assert(ptr == keys + sizeof(keys)); + + return rend_circuit_keys; +} + diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h new file mode 100644 index 0000000000..cd75f46a4c --- /dev/null +++ b/src/or/hs_ntor.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_HS_NTOR_H +#define TOR_HS_NTOR_H + +#include "or.h" + +/* Key material needed to encode/decode INTRODUCE1 cells */ +typedef struct { + /* Key used for encryption of encrypted INTRODUCE1 blob */ + uint8_t enc_key[CIPHER256_KEY_LEN]; + /* MAC key used to protect encrypted INTRODUCE1 blob */ + uint8_t mac_key[DIGEST256_LEN]; +} hs_ntor_intro_cell_keys_t; + +/* Key material needed to encode/decode RENDEZVOUS1 cells */ +typedef struct { + /* This is the MAC of the HANDSHAKE_INFO field */ + uint8_t rend_cell_auth_mac[DIGEST256_LEN]; + /* This is the key seed used to derive further rendezvous crypto keys as + * detailed in section 4.2.1 of rend-spec-ng.txt. */ + uint8_t ntor_key_seed[DIGEST256_LEN]; +} hs_ntor_rend_cell_keys_t; + +/* Key material resulting from key expansion as detailed in section "4.2.1. Key + * expansion" of rend-spec-ng.txt. */ +typedef struct { + /* Per-circuit key material used in ESTABLISH_INTRO cell */ + uint8_t KH[DIGEST256_LEN]; + /* Authentication key for outgoing RELAY cells */ + uint8_t Df[DIGEST256_LEN]; + /* Authentication key for incoming RELAY cells */ + uint8_t Db[DIGEST256_LEN]; + /* Encryption key for outgoing RELAY cells */ + uint8_t Kf[CIPHER256_KEY_LEN]; + /* Decryption key for incoming RELAY cells */ + uint8_t Kb[CIPHER256_KEY_LEN]; +} hs_ntor_rend_circuit_keys_t; + +int hs_ntor_client_get_introduce1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + const uint8_t *subcredential, + hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out); + +int hs_ntor_client_get_rendezvous1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_public_key_t *service_ephemeral_rend_pubkey, + hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out); + +int hs_ntor_service_get_introduce1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *intro_enc_keypair, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + const uint8_t *subcredential, + hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out); + +int hs_ntor_service_get_rendezvous1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *intro_enc_keypair, + const curve25519_keypair_t *service_ephemeral_rend_keypair, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out); + +hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys); + +int hs_ntor_client_rendezvous2_mac_is_good( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys, + const uint8_t *rcvd_mac); + +#endif + diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 8687403b86..205ef11c92 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,6 +12,7 @@ #include "circuitlist.h" #include "circpathbias.h" +#include "hs_intropoint.h" #include "hs_service.h" #include "hs_common.h" @@ -27,7 +28,7 @@ * bytes written, or a negative integer if there was an error. */ ssize_t get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len, - const hs_cell_establish_intro_t *cell) + const trn_cell_establish_intro_t *cell) { ssize_t bytes_used = 0; @@ -35,31 +36,31 @@ get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len, return -1; } - bytes_used = hs_cell_establish_intro_encode(buf_out, buf_out_len, + bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len, cell); return bytes_used; } /* Set the cell extensions of <b>cell</b>. */ static void -set_cell_extensions(hs_cell_establish_intro_t *cell) +set_trn_cell_extensions(trn_cell_establish_intro_t *cell) { - cell_extension_t *cell_extensions = cell_extension_new(); + trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new(); /* For now, we don't use extensions at all. */ - cell_extensions->num = 0; /* It's already zeroed, but be explicit. */ - hs_cell_establish_intro_set_extensions(cell, cell_extensions); + trn_cell_extensions->num = 0; /* It's already zeroed, but be explicit. */ + trn_cell_establish_intro_set_extensions(cell, trn_cell_extensions); } /** Given the circuit handshake info in <b>circuit_key_material</b>, create and * return an ESTABLISH_INTRO cell. Return NULL if something went wrong. The * returned cell is allocated on the heap and it's the responsibility of the * caller to free it. */ -hs_cell_establish_intro_t * +trn_cell_establish_intro_t * generate_establish_intro_cell(const uint8_t *circuit_key_material, size_t circuit_key_material_len) { - hs_cell_establish_intro_t *cell = NULL; + trn_cell_establish_intro_t *cell = NULL; ssize_t encoded_len; log_warn(LD_GENERAL, @@ -72,31 +73,32 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, goto err; } - cell = hs_cell_establish_intro_new(); + cell = trn_cell_establish_intro_new(); /* Set AUTH_KEY_TYPE: 2 means ed25519 */ - hs_cell_establish_intro_set_auth_key_type(cell, AUTH_KEY_ED25519); + trn_cell_establish_intro_set_auth_key_type(cell, + HS_INTRO_AUTH_KEY_TYPE_ED25519); /* Set AUTH_KEY_LEN field */ /* Must also set byte-length of AUTH_KEY to match */ int auth_key_len = ED25519_PUBKEY_LEN; - hs_cell_establish_intro_set_auth_key_len(cell, auth_key_len); - hs_cell_establish_intro_setlen_auth_key(cell, auth_key_len); + trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); /* Set AUTH_KEY field */ - uint8_t *auth_key_ptr = hs_cell_establish_intro_getarray_auth_key(cell); + uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len); /* No cell extensions needed */ - set_cell_extensions(cell); + set_trn_cell_extensions(cell); /* Set signature size. We need to do this up here, because _encode() needs it and we need to call _encode() to calculate the MAC and signature. */ int sig_len = ED25519_SIG_LEN; - hs_cell_establish_intro_set_sig_len(cell, sig_len); - hs_cell_establish_intro_setlen_sig(cell, sig_len); + trn_cell_establish_intro_set_sig_len(cell, sig_len); + trn_cell_establish_intro_setlen_sig(cell, sig_len); /* XXX How to make this process easier and nicer? */ @@ -107,7 +109,7 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; uint8_t mac[TRUNNEL_SHA3_256_LEN]; - encoded_len = hs_cell_establish_intro_encode(cell_bytes_tmp, + encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, sizeof(cell_bytes_tmp), cell); if (encoded_len < 0) { @@ -126,7 +128,7 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, (ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN)); /* Write the MAC to the cell */ uint8_t *handshake_ptr = - hs_cell_establish_intro_getarray_handshake_mac(cell); + trn_cell_establish_intro_getarray_handshake_mac(cell); memcpy(handshake_ptr, mac, sizeof(mac)); } @@ -137,7 +139,7 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; ed25519_signature_t sig; - encoded_len = hs_cell_establish_intro_encode(cell_bytes_tmp, + encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, sizeof(cell_bytes_tmp), cell); if (encoded_len < 0) { @@ -158,7 +160,7 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, } /* And write the signature to the cell */ - uint8_t *sig_ptr = hs_cell_establish_intro_getarray_sig(cell); + uint8_t *sig_ptr = trn_cell_establish_intro_getarray_sig(cell); memcpy(sig_ptr, sig.sig, sig_len); } @@ -166,7 +168,7 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, return cell; err: - hs_cell_establish_intro_free(cell); + trn_cell_establish_intro_free(cell); return NULL; } diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 5d2d8dc4bb..3302592762 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,12 +16,12 @@ * hs_service.o ends up with no symbols in libor.a which makes clang throw a * warning at compile time. See #21825. */ -hs_cell_establish_intro_t * +trn_cell_establish_intro_t * generate_establish_intro_cell(const uint8_t *circuit_key_material, size_t circuit_key_material_len); ssize_t get_establish_intro_payload(uint8_t *buf, size_t buf_len, - const hs_cell_establish_intro_t *cell); + const trn_cell_establish_intro_t *cell); #endif /* TOR_HS_SERVICE_H */ diff --git a/src/or/include.am b/src/or/include.am index 4e54deca55..1ef5afa013 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -22,6 +22,7 @@ LIBTOR_A_SOURCES = \ src/or/bridges.c \ src/or/buffers.c \ src/or/channel.c \ + src/or/channelpadding.c \ src/or/channeltls.c \ src/or/circpathbias.c \ src/or/circuitbuild.c \ @@ -36,6 +37,9 @@ LIBTOR_A_SOURCES = \ src/or/connection.c \ src/or/connection_edge.c \ src/or/connection_or.c \ + src/or/conscache.c \ + src/or/consdiff.c \ + src/or/consdiffmgr.c \ src/or/control.c \ src/or/cpuworker.c \ src/or/dircollate.c \ @@ -48,6 +52,7 @@ LIBTOR_A_SOURCES = \ src/or/geoip.c \ src/or/hs_intropoint.c \ src/or/hs_circuitmap.c \ + src/or/hs_ntor.c \ src/or/hs_service.c \ src/or/entrynodes.c \ src/or/ext_orport.c \ @@ -116,8 +121,11 @@ src_or_tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libev src_or_tor_LDADD = src/or/libtor.a src/common/libor.a src/common/libor-ctime.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ + src/trace/libor-trace.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ + $(rust_ldadd) if COVERAGE_ENABLED src_or_tor_cov_SOURCES = src/or/tor_main.c @@ -129,7 +137,8 @@ src_or_tor_cov_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \ src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event-testing.a src/trunnel/libor-trunnel-testing.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ endif ORHEADERS = \ @@ -137,6 +146,7 @@ ORHEADERS = \ src/or/bridges.h \ src/or/buffers.h \ src/or/channel.h \ + src/or/channelpadding.h \ src/or/channeltls.h \ src/or/circpathbias.h \ src/or/circuitbuild.h \ @@ -151,6 +161,9 @@ ORHEADERS = \ src/or/connection.h \ src/or/connection_edge.h \ src/or/connection_or.h \ + src/or/conscache.h \ + src/or/consdiff.h \ + src/or/consdiffmgr.h \ src/or/control.h \ src/or/cpuworker.h \ src/or/dircollate.h \ @@ -171,6 +184,7 @@ ORHEADERS = \ src/or/hs_descriptor.h \ src/or/hs_intropoint.h \ src/or/hs_circuitmap.h \ + src/or/hs_ntor.h \ src/or/hs_service.h \ src/or/keypin.h \ src/or/main.h \ diff --git a/src/or/keypin.c b/src/or/keypin.c index 2d4c4e92d2..1698dc184f 100644 --- a/src/or/keypin.c +++ b/src/or/keypin.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/keypin.h b/src/or/keypin.h index 673f24d9e3..2564f5befb 100644 --- a/src/or/keypin.h +++ b/src/or/keypin.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_KEYPIN_H diff --git a/src/or/main.c b/src/or/main.c index 1d899349ef..0d91803d4e 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -54,15 +54,19 @@ #include "buffers.h" #include "channel.h" #include "channeltls.h" +#include "channelpadding.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" #include "command.h" +#include "compat_rust.h" +#include "compress.h" #include "config.h" #include "confparse.h" #include "connection.h" #include "connection_edge.h" #include "connection_or.h" +#include "consdiffmgr.h" #include "control.h" #include "cpuworker.h" #include "crypto_s2k.h" @@ -104,7 +108,6 @@ #include "ext_orport.h" #ifdef USE_DMALLOC #include <dmalloc.h> -#include <openssl/crypto.h> #endif #include "memarea.h" #include "sandbox.h" @@ -176,7 +179,7 @@ static int signewnym_is_pending = 0; static unsigned newnym_epoch = 0; /** Smartlist of all open connections. */ -static smartlist_t *connection_array = NULL; +STATIC smartlist_t *connection_array = NULL; /** List of connections that have been marked for close and need to be freed * and removed from connection_array. */ static smartlist_t *closeable_connection_lst = NULL; @@ -1095,8 +1098,9 @@ run_connection_housekeeping(int i, time_t now) } else if (!have_any_circuits && now - or_conn->idle_timeout >= chan->timestamp_last_had_circuits) { - log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) " - "[no circuits for %d; timeout %d; %scanonical].", + log_info(LD_OR,"Expiring non-used OR connection "U64_FORMAT" to fd %d " + "(%s:%d) [no circuits for %d; timeout %d; %scanonical].", + U64_PRINTF_ARG(chan->global_identifier), (int)conn->s, conn->address, conn->port, (int)(now - chan->timestamp_last_had_circuits), or_conn->idle_timeout, @@ -1119,6 +1123,8 @@ run_connection_housekeeping(int i, time_t now) memset(&cell,0,sizeof(cell_t)); cell.command = CELL_PADDING; connection_or_write_cell_to_buf(&cell, or_conn); + } else { + channelpadding_decide_to_pad_channel(chan); } } @@ -1161,6 +1167,7 @@ static int periodic_events_initialized = 0; #define CALLBACK(name) \ static int name ## _callback(time_t, const or_options_t *) CALLBACK(rotate_onion_key); +CALLBACK(check_onion_keys_expiry_time); CALLBACK(check_ed_keys); CALLBACK(launch_descriptor_fetches); CALLBACK(rotate_x509_certificate); @@ -1184,6 +1191,9 @@ CALLBACK(check_dns_honesty); CALLBACK(write_bridge_ns); CALLBACK(check_fw_helper_app); CALLBACK(heartbeat); +CALLBACK(clean_consdiffmgr); +CALLBACK(reset_padding_counts); +CALLBACK(check_canonical_channels); #undef CALLBACK @@ -1192,6 +1202,7 @@ CALLBACK(heartbeat); static periodic_event_item_t periodic_events[] = { CALLBACK(rotate_onion_key), + CALLBACK(check_onion_keys_expiry_time), CALLBACK(check_ed_keys), CALLBACK(launch_descriptor_fetches), CALLBACK(rotate_x509_certificate), @@ -1215,6 +1226,9 @@ static periodic_event_item_t periodic_events[] = { CALLBACK(write_bridge_ns), CALLBACK(check_fw_helper_app), CALLBACK(heartbeat), + CALLBACK(clean_consdiffmgr), + CALLBACK(reset_padding_counts), + CALLBACK(check_canonical_channels), END_OF_PERIODIC_EVENTS }; #undef CALLBACK @@ -1470,19 +1484,26 @@ run_scheduled_events(time_t now) /* 11b. check pending unconfigured managed proxies */ if (!net_is_disabled() && pt_proxies_configuration_pending()) pt_configure_remaining_proxies(); + + /* 12. launch diff computations. (This is free if there are none to + * launch.) */ + if (dir_server_mode(options)) { + consdiffmgr_rescan(); + } } -/* Periodic callback: Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion - * keys, shut down and restart all cpuworkers, and update our descriptor if - * necessary. +/* Periodic callback: rotate the onion keys after the period defined by the + * "onion-key-rotation-days" consensus parameter, shut down and restart all + * cpuworkers, and update our descriptor if necessary. */ static int rotate_onion_key_callback(time_t now, const or_options_t *options) { if (server_mode(options)) { - time_t rotation_time = get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME; + int onion_key_lifetime = get_onion_key_lifetime(); + time_t rotation_time = get_onion_key_set_at()+onion_key_lifetime; if (rotation_time > now) { - return safe_timer_diff(now, rotation_time); + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; } log_info(LD_GENERAL,"Rotating onion key."); @@ -1493,8 +1514,32 @@ rotate_onion_key_callback(time_t now, const or_options_t *options) } if (advertised_server_mode() && !options->DisableNetwork) router_upload_dir_desc_to_dirservers(0); - return MIN_ONION_KEY_LIFETIME; + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; + } + return PERIODIC_EVENT_NO_UPDATE; +} + +/* Period callback: Check if our old onion keys are still valid after the + * period of time defined by the consensus parameter + * "onion-key-grace-period-days", otherwise expire them by setting them to + * NULL. + */ +static int +check_onion_keys_expiry_time_callback(time_t now, const or_options_t *options) +{ + if (server_mode(options)) { + int onion_key_grace_period = get_onion_key_grace_period(); + time_t expiry_time = get_onion_key_set_at()+onion_key_grace_period; + if (expiry_time > now) { + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; + } + + log_info(LD_GENERAL, "Expiring old onion keys."); + expire_old_onion_keys(); + cpuworkers_rotate_keyinfo(); + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; } + return PERIODIC_EVENT_NO_UPDATE; } @@ -1511,7 +1556,7 @@ check_ed_keys_callback(time_t now, const or_options_t *options) generate_ed_link_cert(options, now, new_signing_key > 0)) { log_err(LD_OR, "Unable to update Ed25519 keys! Exiting."); tor_cleanup(); - exit(0); + exit(1); } } return 30; @@ -1726,6 +1771,28 @@ write_stats_file_callback(time_t now, const or_options_t *options) return safe_timer_diff(now, next_time_to_write_stats_files); } +#define CHANNEL_CHECK_INTERVAL (60*60) +static int +check_canonical_channels_callback(time_t now, const or_options_t *options) +{ + (void)now; + if (public_server_mode(options)) + channel_check_for_duplicates(); + + return CHANNEL_CHECK_INTERVAL; +} + +static int +reset_padding_counts_callback(time_t now, const or_options_t *options) +{ + if (options->PaddingStatistics) { + rep_hist_prep_published_padding_counts(now); + } + + rep_hist_reset_padding_counts(); + return REPHIST_CELL_PADDING_COUNTS_INTERVAL; +} + /** * Periodic callback: Write bridge statistics to disk if appropriate. */ @@ -2014,6 +2081,17 @@ heartbeat_callback(time_t now, const or_options_t *options) return options->HeartbeatPeriod; } +#define CDM_CLEAN_CALLBACK_INTERVAL 600 +static int +clean_consdiffmgr_callback(time_t now, const or_options_t *options) +{ + (void)now; + if (server_mode(options)) { + consdiffmgr_cleanup(); + } + return CDM_CLEAN_CALLBACK_INTERVAL; +} + /** Timer: used to invoke second_elapsed_callback() once per second. */ static periodic_timer_t *second_timer = NULL; /** Number of libevent errors in the last second: we die if we get too many. */ @@ -2343,6 +2421,8 @@ do_main_loop(void) } handle_signals(1); + monotime_init(); + timers_initialize(); /* load the private keys, if we're supposed to have them, and set up the * TLS context. */ @@ -2410,9 +2490,10 @@ do_main_loop(void) /* launch cpuworkers. Need to do this *after* we've read the onion key. */ cpu_init(); } + consdiffmgr_enable_background_compression(); /* Setup shared random protocol subsystem. */ - if (authdir_mode_publishes_statuses(get_options())) { + if (authdir_mode_v3(get_options())) { if (sr_init(1) < 0) { return -1; } @@ -2980,11 +3061,16 @@ tor_init(int argc, char *argv[]) const char *version = get_version(); log_notice(LD_GENERAL, "Tor %s running on %s with Libevent %s, " - "OpenSSL %s and Zlib %s.", version, + "OpenSSL %s, Zlib %s, Liblzma %s, and Libzstd %s.", version, get_uname(), tor_libevent_get_version_str(), crypto_openssl_get_version_str(), - tor_zlib_get_version_str()); + tor_compress_supports_method(ZLIB_METHOD) ? + tor_compress_version_str(ZLIB_METHOD) : "N/A", + tor_compress_supports_method(LZMA_METHOD) ? + tor_compress_version_str(LZMA_METHOD) : "N/A", + tor_compress_supports_method(ZSTD_METHOD) ? + tor_compress_version_str(ZSTD_METHOD) : "N/A"); log_notice(LD_GENERAL, "Tor can't help you if you use it wrong! " "Learn how to be safe at " @@ -2995,6 +3081,15 @@ tor_init(int argc, char *argv[]) "Expect more bugs than usual."); } + { + rust_str_t rust_str = rust_welcome_string(); + const char *s = rust_str_get(rust_str); + if (strlen(s) > 0) { + log_notice(LD_GENERAL, "%s", s); + } + rust_str_free(rust_str); + } + if (network_init()<0) { log_err(LD_BUG,"Error initializing network; exiting."); return -1; @@ -3009,6 +3104,13 @@ tor_init(int argc, char *argv[]) /* The options are now initialised */ const or_options_t *options = get_options(); + /* Initialize channelpadding parameters to defaults until we get + * a consensus */ + channelpadding_new_consensus_params(NULL); + + /* Initialize predicted ports list after loading options */ + predicted_ports_init(); + #ifndef _WIN32 if (geteuid()==0) log_warn(LD_GENERAL,"You are running Tor as root. You don't need to, " @@ -3066,7 +3168,7 @@ try_locking(const or_options_t *options, int err_if_locked) r = try_locking(options, 0); if (r<0) { log_err(LD_GENERAL, "No, it's still there. Exiting."); - exit(0); + exit(1); } return r; } @@ -3138,6 +3240,7 @@ tor_free_all(int postfork) sandbox_free_getaddrinfo_cache(); protover_free_all(); bridges_free_all(); + consdiffmgr_free_all(); if (!postfork) { config_free_all(); or_state_free_all(); @@ -3167,6 +3270,7 @@ tor_free_all(int postfork) if (!postfork) { escaped(NULL); esc_router_info(NULL); + clean_up_backtrace_handler(); logs_free_all(); /* free log strings. do this last so logs keep working. */ } } @@ -3204,6 +3308,9 @@ tor_cleanup(void) rep_hist_record_mtbf_data(now, 0); keypin_close_journal(); } + + timers_shutdown(); + #ifdef USE_DMALLOC dmalloc_log_stats(); #endif @@ -3559,6 +3666,8 @@ sandbox_init_filter(void) OPEN_DATADIR("stats"); STAT_DATADIR("stats"); STAT_DATADIR2("stats", "dirreq-stats"); + + consdiffmgr_register_with_sandbox(&cfg); } init_addrinfo(); @@ -3599,14 +3708,15 @@ tor_main(int argc, char *argv[]) update_approx_time(time(NULL)); tor_threads_init(); + tor_compress_init(); init_logging(0); monotime_init(); #ifdef USE_DMALLOC { /* Instruct OpenSSL to use our internal wrappers for malloc, realloc and free. */ - int r = CRYPTO_set_mem_ex_functions(tor_malloc_, tor_realloc_, tor_free_); - tor_assert(r); + int r = crypto_use_tor_alloc_functions(); + tor_assert(r == 0); } #endif #ifdef NT_SERVICE diff --git a/src/or/main.h b/src/or/main.h index 07b22598b1..57aa372750 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -92,6 +92,9 @@ STATIC void init_connection_lists(void); STATIC void close_closeable_connections(void); STATIC void initialize_periodic_events(void); STATIC void teardown_periodic_events(void); +#ifdef TOR_UNIT_TESTS +extern smartlist_t *connection_array; +#endif #endif #endif diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 140117f683..32242d0054 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2016, The Tor Project, Inc. */ +/* Copyright (c) 2009-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -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 @@ -804,18 +905,6 @@ microdesc_cache_lookup_by_digest256(microdesc_cache_t *cache, const char *d) return md; } -/** Return the mean size of decriptors added to <b>cache</b> since it was last - * cleared. Used to estimate the size of large downloads. */ -size_t -microdesc_average_size(microdesc_cache_t *cache) -{ - if (!cache) - cache = get_microdesc_cache(); - if (!cache->n_seen) - return 512; - return (size_t)(cache->total_len_seen / cache->n_seen); -} - /** Return a smartlist of all the sha256 digest of the microdescriptors that * are listed in <b>ns</b> but not present in <b>cache</b>. Returns pointers * to internals of <b>ns</b>; you should not free the members of the resulting diff --git a/src/or/microdesc.h b/src/or/microdesc.h index 40c83139e9..1be12156a4 100644 --- a/src/or/microdesc.h +++ b/src/or/microdesc.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -32,8 +32,6 @@ void microdesc_cache_clear(microdesc_cache_t *cache); microdesc_t *microdesc_cache_lookup_by_digest256(microdesc_cache_t *cache, const char *d); -size_t microdesc_average_size(microdesc_cache_t *cache); - smartlist_t *microdesc_list_missing_digest256(networkstatus_t *ns, microdesc_cache_t *cache, int downloadable_only, @@ -52,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); -#endif +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 1fd0772f3e..36e62020e3 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -46,6 +46,7 @@ #include "config.h" #include "connection.h" #include "connection_or.h" +#include "consdiffmgr.h" #include "control.h" #include "directory.h" #include "dirserv.h" @@ -63,6 +64,7 @@ #include "shared_random.h" #include "transports.h" #include "torcert.h" +#include "channelpadding.h" /** Map from lowercase nickname to identity digest of named server, if any. */ static strmap_t *named_server_map = NULL; @@ -72,11 +74,11 @@ static strmap_t *unnamed_server_map = NULL; /** Most recently received and validated v3 "ns"-flavored consensus network * status. */ -static networkstatus_t *current_ns_consensus = NULL; +STATIC networkstatus_t *current_ns_consensus = NULL; /** Most recently received and validated v3 "microdec"-flavored consensus * network status. */ -static networkstatus_t *current_md_consensus = NULL; +STATIC networkstatus_t *current_md_consensus = NULL; /** A v3 consensus networkstatus that we've received, but which we don't * have enough certificates to be happy about. */ @@ -178,53 +180,74 @@ networkstatus_reset_download_failures(void) download_status_reset(&consensus_bootstrap_dl_status[i]); } +/** + * Read and and return the cached consensus of type <b>flavorname</b>. If + * <b>unverified</b> is false, get the one we haven't verified. Return NULL if + * the file isn't there. */ +static char * +networkstatus_read_cached_consensus_impl(int flav, + const char *flavorname, + int unverified_consensus) +{ + char buf[128]; + const char *prefix; + if (unverified_consensus) { + prefix = "unverified"; + } else { + prefix = "cached"; + } + if (flav == FLAV_NS) { + tor_snprintf(buf, sizeof(buf), "%s-consensus", prefix); + } else { + tor_snprintf(buf, sizeof(buf), "%s-%s-consensus", prefix, flavorname); + } + + char *filename = get_datadir_fname(buf); + char *result = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + tor_free(filename); + return result; +} + +/** Return a new string containing the current cached consensus of flavor + * <b>flavorname</b>. */ +char * +networkstatus_read_cached_consensus(const char *flavorname) + { + int flav = networkstatus_parse_flavor_name(flavorname); + if (flav < 0) + return NULL; + return networkstatus_read_cached_consensus_impl(flav, flavorname, 0); +} + /** Read every cached v3 consensus networkstatus from the disk. */ int router_reload_consensus_networkstatus(void) { - char *filename; - char *s; const unsigned int flags = NSSET_FROM_CACHE | NSSET_DONT_DOWNLOAD_CERTS; int flav; /* FFFF Suppress warnings if cached consensus is bad? */ for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) { - char buf[128]; const char *flavor = networkstatus_get_flavor_name(flav); - if (flav == FLAV_NS) { - filename = get_datadir_fname("cached-consensus"); - } else { - tor_snprintf(buf, sizeof(buf), "cached-%s-consensus", flavor); - filename = get_datadir_fname(buf); - } - s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + char *s = networkstatus_read_cached_consensus_impl(flav, flavor, 0); if (s) { if (networkstatus_set_current_consensus(s, flavor, flags, NULL) < -1) { - log_warn(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"", - flavor, filename); + log_warn(LD_FS, "Couldn't load consensus %s networkstatus from cache", + flavor); } tor_free(s); } - tor_free(filename); - if (flav == FLAV_NS) { - filename = get_datadir_fname("unverified-consensus"); - } else { - tor_snprintf(buf, sizeof(buf), "unverified-%s-consensus", flavor); - filename = get_datadir_fname(buf); - } - - s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + s = networkstatus_read_cached_consensus_impl(flav, flavor, 1); if (s) { if (networkstatus_set_current_consensus(s, flavor, flags|NSSET_WAS_WAITING_FOR_CERTS, NULL)) { - log_info(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"", - flavor, filename); - } + log_info(LD_FS, "Couldn't load unverified consensus %s networkstatus " + "from cache", flavor); + } tor_free(s); } - tor_free(filename); } if (!networkstatus_get_latest_consensus()) { @@ -1384,16 +1407,24 @@ networkstatus_get_live_consensus,(time_t now)) * Return 1 if the consensus is reasonably live, or 0 if it is too old. */ int -networkstatus_consensus_reasonably_live(networkstatus_t *consensus, time_t now) +networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, + time_t now) { -#define REASONABLY_LIVE_TIME (24*60*60) if (BUG(!consensus)) return 0; - if (now <= consensus->valid_until + REASONABLY_LIVE_TIME) - return 1; + return networkstatus_valid_until_is_reasonably_live(consensus->valid_until, + now); +} - return 0; +/** As networkstatus_consensus_reasonably_live, but takes a valid_until + * time rather than an entire consensus. */ +int +networkstatus_valid_until_is_reasonably_live(time_t valid_until, + time_t now) +{ +#define REASONABLY_LIVE_TIME (24*60*60) + return (now <= valid_until + REASONABLY_LIVE_TIME); } /* XXXX remove this in favor of get_live_consensus. But actually, @@ -1966,6 +1997,7 @@ networkstatus_set_current_consensus(const char *consensus, circuit_build_times_new_consensus_params( get_circuit_build_times_mutable(), c); + channelpadding_new_consensus_params(c); } /* Reset the failure count only if this consensus is actually valid. */ @@ -1980,7 +2012,11 @@ networkstatus_set_current_consensus(const char *consensus, dirserv_set_cached_consensus_networkstatus(consensus, flavor, &c->digests, + c->digest_sha3_as_signed, c->valid_after); + if (dir_server_mode(get_options())) { + consdiffmgr_add_consensus(consensus, c); + } } if (!from_cache) { @@ -2005,6 +2041,9 @@ networkstatus_set_current_consensus(const char *consensus, "CLOCK_SKEW MIN_SKEW=%ld SOURCE=CONSENSUS", delta); } + /* We got a new consesus. Reset our md fetch fail cache */ + microdesc_reset_outdated_dirservers_list(); + router_dir_info_changed(); result = 0; @@ -2272,13 +2311,23 @@ networkstatus_dump_bridge_status_to_file(time_t now) char *thresholds = NULL; char *published_thresholds_and_status = NULL; char published[ISO_TIME_LEN+1]; + const routerinfo_t *me = router_get_my_routerinfo(); + char fingerprint[FINGERPRINT_LEN+1]; + char *fingerprint_line = NULL; + if (me && crypto_pk_get_fingerprint(me->identity_pkey, + fingerprint, 0) >= 0) { + tor_asprintf(&fingerprint_line, "fingerprint %s\n", fingerprint); + } else { + log_warn(LD_BUG, "Error computing fingerprint for bridge status."); + } format_iso_time(published, now); dirserv_compute_bridge_flag_thresholds(); thresholds = dirserv_get_flag_thresholds_line(); tor_asprintf(&published_thresholds_and_status, - "published %s\nflag-thresholds %s\n%s", - published, thresholds, status); + "published %s\nflag-thresholds %s\n%s%s", + published, thresholds, fingerprint_line ? fingerprint_line : "", + status); tor_asprintf(&fname, "%s"PATH_SEPARATOR"networkstatus-bridges", options->DataDirectory); write_str_to_file(fname,published_thresholds_and_status,0); @@ -2286,6 +2335,7 @@ networkstatus_dump_bridge_status_to_file(time_t now) tor_free(published_thresholds_and_status); tor_free(fname); tor_free(status); + tor_free(fingerprint_line); } /* DOCDOC get_net_param_from_list */ @@ -2441,10 +2491,8 @@ networkstatus_parse_flavor_name(const char *flavname) * running, or otherwise not a descriptor that we would make any * use of even if we had it. Else return 1. */ int -client_would_use_router(const routerstatus_t *rs, time_t now, - const or_options_t *options) +client_would_use_router(const routerstatus_t *rs, time_t now) { - (void) options; /* unused */ if (!rs->is_flagged_running) { /* If we had this router descriptor, we wouldn't even bother using it. * (Fetching and storing depends on by we_want_to_fetch_flavor().) */ diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 66cd84c88e..e774c4d266 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,6 +16,7 @@ void networkstatus_reset_warnings(void); void networkstatus_reset_download_failures(void); +char *networkstatus_read_cached_consensus(const char *flavorname); int router_reload_consensus_networkstatus(void); void routerstatus_free(routerstatus_t *rs); void networkstatus_vote_free(networkstatus_t *ns); @@ -75,14 +76,15 @@ int should_delay_dir_fetches(const or_options_t *options,const char **msg_out); void update_networkstatus_downloads(time_t now); void update_certificate_downloads(time_t now); int consensus_is_waiting_for_certs(void); -int client_would_use_router(const routerstatus_t *rs, time_t now, - const or_options_t *options); +int client_would_use_router(const routerstatus_t *rs, time_t now); MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void)); MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, (consensus_flavor_t f)); MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)); -int networkstatus_consensus_reasonably_live(networkstatus_t *consensus, +int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, time_t now); +int networkstatus_valid_until_is_reasonably_live(time_t valid_until, + time_t now); networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now, int flavor); MOCK_DECL(int, networkstatus_consensus_is_bootstrapping,(time_t now)); @@ -138,7 +140,9 @@ void vote_routerstatus_free(vote_routerstatus_t *rs); #ifdef TOR_UNIT_TESTS STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c, const char *flavor); -#endif // TOR_UNIT_TESTS +extern networkstatus_t *current_ns_consensus; +extern networkstatus_t *current_md_consensus; +#endif #endif #endif diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 938b791102..d09989d93f 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -74,7 +74,6 @@ static void count_usable_descriptors(int *num_present, int *num_usable, smartlist_t *descs_out, const networkstatus_t *consensus, - const or_options_t *options, time_t now, routerset_t *in_set, usable_descriptor_t exit_only); @@ -1011,16 +1010,6 @@ node_get_platform(const node_t *node) return NULL; } -/** Return <b>node</b>'s time of publication, or 0 if we don't have one. */ -time_t -node_get_published_on(const node_t *node) -{ - if (node->ri) - return node->ri->cache_info.published_on; - else - return 0; -} - /** Return true iff <b>node</b> is one representing this router. */ int node_is_me(const node_t *node) @@ -1695,7 +1684,7 @@ static void count_usable_descriptors(int *num_present, int *num_usable, smartlist_t *descs_out, const networkstatus_t *consensus, - const or_options_t *options, time_t now, + time_t now, routerset_t *in_set, usable_descriptor_t exit_only) { @@ -1712,7 +1701,7 @@ count_usable_descriptors(int *num_present, int *num_usable, continue; if (in_set && ! routerset_contains_routerstatus(in_set, rs, -1)) continue; - if (client_would_use_router(rs, now, options)) { + if (client_would_use_router(rs, now)) { const char * const digest = rs->descriptor_digest; int present; ++*num_usable; /* the consensus says we want it. */ @@ -1766,10 +1755,10 @@ compute_frac_paths_available(const networkstatus_t *consensus, const int authdir = authdir_mode_v3(options); count_usable_descriptors(num_present_out, num_usable_out, - mid, consensus, options, now, NULL, + mid, consensus, now, NULL, USABLE_DESCRIPTOR_ALL); if (options->EntryNodes) { - count_usable_descriptors(&np, &nu, guards, consensus, options, now, + count_usable_descriptors(&np, &nu, guards, consensus, now, options->EntryNodes, USABLE_DESCRIPTOR_ALL); } else { SMARTLIST_FOREACH(mid, const node_t *, node, { @@ -1790,7 +1779,7 @@ compute_frac_paths_available(const networkstatus_t *consensus, * an unavoidable feature of forcing authorities to declare * certain nodes as exits. */ - count_usable_descriptors(&np, &nu, exits, consensus, options, now, + count_usable_descriptors(&np, &nu, exits, consensus, now, NULL, USABLE_DESCRIPTOR_EXIT_ONLY); log_debug(LD_NET, "%s: %d present, %d usable", @@ -1839,7 +1828,7 @@ compute_frac_paths_available(const networkstatus_t *consensus, smartlist_t *myexits_unflagged = smartlist_new(); /* All nodes with exit flag in ExitNodes option */ - count_usable_descriptors(&np, &nu, myexits, consensus, options, now, + count_usable_descriptors(&np, &nu, myexits, consensus, now, options->ExitNodes, USABLE_DESCRIPTOR_EXIT_ONLY); log_debug(LD_NET, "%s: %d present, %d usable", @@ -1850,7 +1839,7 @@ compute_frac_paths_available(const networkstatus_t *consensus, /* Now compute the nodes in the ExitNodes option where which we don't know * what their exit policy is, or we know it permits something. */ count_usable_descriptors(&np, &nu, myexits_unflagged, - consensus, options, now, + consensus, now, options->ExitNodes, USABLE_DESCRIPTOR_ALL); log_debug(LD_NET, "%s: %d present, %d usable", diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 4e5301df6b..95ae778a5b 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -53,7 +53,6 @@ const char *node_get_platform(const node_t *node); uint32_t node_get_prim_addr_ipv4h(const node_t *node); void node_get_address_string(const node_t *node, char *cp, size_t len); long node_get_declared_uptime(const node_t *node); -time_t node_get_published_on(const node_t *node); const smartlist_t *node_get_declared_family(const node_t *node); const ed25519_public_key_t *node_get_ed25519_id(const node_t *node); int node_ed25519_id_matches(const node_t *node, diff --git a/src/or/ntmain.c b/src/or/ntmain.c index 0e6f296d24..d0d5276c48 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/ntmain.h b/src/or/ntmain.h index 31bf38c62c..4b771b1828 100644 --- a/src/or/ntmain.h +++ b/src/or/ntmain.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/onion.c b/src/or/onion.c index b3898d4085..a98b97cb1d 100644 --- a/src/or/onion.c +++ b/src/or/onion.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/onion.h b/src/or/onion.h index 19e4a7c381..37a7b08cb6 100644 --- a/src/or/onion.h +++ b/src/or/onion.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c index 8dcbfe22d8..146943a273 100644 --- a/src/or/onion_fast.c +++ b/src/or/onion_fast.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/onion_fast.h b/src/or/onion_fast.h index b9626002c3..b31f8e9492 100644 --- a/src/or/onion_fast.h +++ b/src/or/onion_fast.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/onion_ntor.c b/src/or/onion_ntor.c index ded97ee73d..902260b54b 100644 --- a/src/or/onion_ntor.c +++ b/src/or/onion_ntor.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/onion_ntor.h b/src/or/onion_ntor.h index f637b437fd..158c499de4 100644 --- a/src/or/onion_ntor.h +++ b/src/or/onion_ntor.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_ONION_NTOR_H diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c index 2769300945..294fc0df6d 100644 --- a/src/or/onion_tap.c +++ b/src/or/onion_tap.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -159,7 +159,7 @@ onion_skin_TAP_server_handshake( * big. That should be impossible. */ log_info(LD_GENERAL, "crypto_dh_get_public failed."); goto err; - /* LCOV_EXCP_STOP */ + /* LCOV_EXCL_STOP */ } key_material_len = DIGEST_LEN+key_out_len; diff --git a/src/or/onion_tap.h b/src/or/onion_tap.h index a2880f6e98..bd625231f4 100644 --- a/src/or/onion_tap.h +++ b/src/or/onion_tap.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/or.h b/src/or/or.h index 2b56a486d8..95281e701f 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -71,10 +71,11 @@ #include "tortls.h" #include "torlog.h" #include "container.h" -#include "torgzip.h" +#include "compress.h" #include "address.h" #include "compat_libevent.h" #include "ht.h" +#include "confline.h" #include "replaycache.h" #include "crypto_curve25519.h" #include "crypto_ed25519.h" @@ -147,8 +148,27 @@ /** Maximum size of a single extrainfo document, as above. */ #define MAX_EXTRAINFO_UPLOAD_SIZE 50000 -/** How often do we rotate onion keys? */ -#define MIN_ONION_KEY_LIFETIME (7*24*60*60) +/** Minimum lifetime for an onion key in days. */ +#define MIN_ONION_KEY_LIFETIME_DAYS (1) + +/** Maximum lifetime for an onion key in days. */ +#define MAX_ONION_KEY_LIFETIME_DAYS (90) + +/** Default lifetime for an onion key in days. */ +#define DEFAULT_ONION_KEY_LIFETIME_DAYS (28) + +/** Minimum grace period for acceptance of an onion key in days. + * The maximum value is defined in proposal #274 as being the current network + * consensus parameter for "onion-key-rotation-days". */ +#define MIN_ONION_KEY_GRACE_PERIOD_DAYS (1) + +/** Default grace period for acceptance of an onion key in days. */ +#define DEFAULT_ONION_KEY_GRACE_PERIOD_DAYS (7) + +/** How often we should check the network consensus if it is time to rotate or + * expire onion keys. */ +#define ONION_KEY_CONSENSUS_CHECK_INTERVAL (60*60) + /** How often do we rotate TLS contexts? */ #define MAX_SSL_KEY_LIFETIME_INTERNAL (2*60*60) @@ -403,12 +423,13 @@ typedef enum { #define DIR_PURPOSE_FETCH_MICRODESC 19 #define DIR_PURPOSE_MAX_ 19 -/** True iff <b>p</b> is a purpose corresponding to uploading data to a - * directory server. */ +/** True iff <b>p</b> is a purpose corresponding to uploading + * data to a directory server. */ #define DIR_PURPOSE_IS_UPLOAD(p) \ ((p)==DIR_PURPOSE_UPLOAD_DIR || \ (p)==DIR_PURPOSE_UPLOAD_VOTE || \ - (p)==DIR_PURPOSE_UPLOAD_SIGNATURES) + (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ + (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2) #define EXIT_PURPOSE_MIN_ 1 /** This exit stream wants to do an ordinary connect. */ @@ -875,6 +896,7 @@ typedef enum { #define CELL_RELAY_EARLY 9 #define CELL_CREATE2 10 #define CELL_CREATED2 11 +#define CELL_PADDING_NEGOTIATE 12 #define CELL_VPADDING 128 #define CELL_CERTS 129 @@ -1564,10 +1586,6 @@ typedef struct or_connection_t { * NETINFO cell listed the address we're connected to as recognized. */ unsigned int is_canonical:1; - /** True iff we have decided that the other end of this connection - * is a client. Connections with this flag set should never be used - * to satisfy an EXTEND request. */ - unsigned int is_connection_with_client:1; /** True iff this is an outgoing connection. */ unsigned int is_outgoing:1; unsigned int proxy_type:2; /**< One of PROXY_NONE...PROXY_SOCKS5 */ @@ -1753,14 +1771,6 @@ typedef struct entry_connection_t { unsigned int is_socks_socket:1; } entry_connection_t; -typedef enum { - DIR_SPOOL_NONE=0, DIR_SPOOL_SERVER_BY_DIGEST, DIR_SPOOL_SERVER_BY_FP, - DIR_SPOOL_EXTRA_BY_DIGEST, DIR_SPOOL_EXTRA_BY_FP, - DIR_SPOOL_CACHED_DIR, DIR_SPOOL_NETWORKSTATUS, - DIR_SPOOL_MICRODESC, /* NOTE: if we add another entry, add another bit. */ -} dir_spool_source_t; -#define dir_spool_source_bitfield_t ENUM_BF(dir_spool_source_t) - /** Subtype of connection_t for an "directory connection" -- that is, an HTTP * connection to retrieve or serve directory material. */ typedef struct dir_connection_t { @@ -1775,23 +1785,15 @@ typedef struct dir_connection_t { char *requested_resource; unsigned int dirconn_direct:1; /**< Is this dirconn direct, or via Tor? */ - /* Used only for server sides of some dir connections, to implement - * "spooling" of directory material to the outbuf. Otherwise, we'd have - * to append everything to the outbuf in one enormous chunk. */ - /** What exactly are we spooling right now? */ - dir_spool_source_bitfield_t dir_spool_src : 3; - /** If we're fetching descriptors, what router purpose shall we assign * to them? */ uint8_t router_purpose; - /** List of fingerprints for networkstatuses or descriptors to be spooled. */ - smartlist_t *fingerprint_stack; - /** A cached_dir_t object that we're currently spooling out */ - struct cached_dir_t *cached_dir; - /** The current offset into cached_dir. */ - off_t cached_dir_offset; - /** The zlib object doing on-the-fly compression for spooled data. */ - tor_zlib_state_t *zlib_state; + + /** List of spooled_resource_t for objects that we're spooling. We use + * it from back to front. */ + smartlist_t *spool; + /** The compression object doing on-the-fly compression for spooled data. */ + tor_compress_state_t *compress_state; /** What rendezvous service are we querying for? */ rend_data_t *rend_data; @@ -1807,6 +1809,14 @@ typedef struct dir_connection_t { * that's going away and being used on channels instead. The dirserver still * needs this for the incoming side, so it's moved here. */ uint64_t dirreq_id; + +#ifdef MEASUREMENTS_21206 + /** Number of RELAY_DATA cells received. */ + uint32_t data_cells_received; + + /** Number of RELAY_DATA cells sent. */ + uint32_t data_cells_sent; +#endif } dir_connection_t; /** Subtype of connection_t for an connection to a controller. */ @@ -1945,11 +1955,13 @@ typedef struct addr_policy_t { * compressed form. */ typedef struct cached_dir_t { char *dir; /**< Contents of this object, NUL-terminated. */ - char *dir_z; /**< Compressed contents of this object. */ + char *dir_compressed; /**< Compressed contents of this object. */ size_t dir_len; /**< Length of <b>dir</b> (not counting its NUL). */ - size_t dir_z_len; /**< Length of <b>dir_z</b>. */ + size_t dir_compressed_len; /**< Length of <b>dir_compressed</b>. */ time_t published; /**< When was this object published. */ common_digests_t digests; /**< Digests of this object (networkstatus only) */ + /** Sha3 digest (also ns only) */ + uint8_t digest_sha3_as_signed[DIGEST256_LEN]; int refcnt; /**< Reference count for this cached_dir_t. */ } cached_dir_t; @@ -2287,6 +2299,16 @@ typedef struct routerstatus_t { * ed25519 identity keys on a link handshake. */ unsigned int supports_ed25519_link_handshake:1; + /** True iff this router has a protocol list that allows it to be an + * introduction point supporting ed25519 authentication key which is part of + * the v3 protocol detailed in proposal 224. This requires HSIntro=4. */ + unsigned int supports_ed25519_hs_intro : 1; + + /** True iff this router has a protocol list that allows it to be an hidden + * service directory supporting version 3 as seen in proposal 224. This + * requires HSDir=2. */ + unsigned int supports_v3_hsdir : 1; + unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with @@ -2640,6 +2662,9 @@ typedef struct networkstatus_t { /** Digests of this document, as signed. */ common_digests_t digests; + /** A SHA3-256 digest of the document, not including signatures: used for + * consensus diffs */ + uint8_t digest_sha3_as_signed[DIGEST256_LEN]; /** List of router statuses, sorted by identity digest. For a vote, * the elements are vote_routerstatus_t; for a consensus, the elements @@ -3073,6 +3098,13 @@ typedef struct circuit_t { * circuit's queues; used only if CELL_STATS events are enabled and * cleared after being sent to control port. */ smartlist_t *testing_cell_stats; + + /** If set, points to an HS token that this circuit might be carrying. + * Used by the HS circuitmap. */ + hs_token_t *hs_token; + /** Hashtable node: used to look up the circuit by its HS token using the HS + circuitmap. */ + HT_ENTRY(circuit_t) hs_circuitmap_node; } circuit_t; /** Largest number of relay_early cells that we can send on a given @@ -3321,6 +3353,13 @@ typedef struct origin_circuit_t { * adjust_exit_policy_from_exitpolicy_failure. */ smartlist_t *prepend_policy; + + /** How long do we wait before closing this circuit if it remains + * completely idle after it was built, in seconds? This value + * is randomized on a per-circuit basis from CircuitsAvailableTimoeut + * to 2*CircuitsAvailableTimoeut. */ + int circuit_idle_timeout; + } origin_circuit_t; struct onion_queue_t; @@ -3382,13 +3421,6 @@ typedef struct or_circuit_t { * is not marked for close. */ struct or_circuit_t *rend_splice; - /** If set, points to an HS token that this circuit might be carrying. - * Used by the HS circuitmap. */ - hs_token_t *hs_token; - /** Hashtable node: used to look up the circuit by its HS token using the HS - circuitmap. */ - HT_ENTRY(or_circuit_t) hs_circuitmap_node; - /** Stores KH for the handshake. */ char rend_circ_nonce[DIGEST_LEN];/* KH in tor-spec.txt */ @@ -3469,15 +3501,6 @@ static inline const origin_circuit_t *CONST_TO_ORIGIN_CIRCUIT( return DOWNCAST(origin_circuit_t, x); } -/** Bitfield type: things that we're willing to use invalid routers for. */ -typedef enum invalid_router_usage_t { - ALLOW_INVALID_ENTRY =1, - ALLOW_INVALID_EXIT =2, - ALLOW_INVALID_MIDDLE =4, - ALLOW_INVALID_RENDEZVOUS =8, - ALLOW_INVALID_INTRODUCTION=16, -} invalid_router_usage_t; - /* limits for TCP send and recv buffer size used for constrained sockets */ #define MIN_CONSTRAINED_TCP_BUFFER 2048 #define MAX_CONSTRAINED_TCP_BUFFER 262144 /* 256k */ @@ -3539,27 +3562,6 @@ typedef struct port_cfg_t { char unix_addr[FLEXIBLE_ARRAY_MEMBER]; } port_cfg_t; -/** Ordinary configuration line. */ -#define CONFIG_LINE_NORMAL 0 -/** Appends to previous configuration for the same option, even if we - * would ordinary replace it. */ -#define CONFIG_LINE_APPEND 1 -/* Removes all previous configuration for an option. */ -#define CONFIG_LINE_CLEAR 2 - -/** A linked list of lines in a config file. */ -typedef struct config_line_t { - char *key; - char *value; - struct config_line_t *next; - /** What special treatment (if any) does this line require? */ - unsigned int command:2; - /** If true, subsequent assignments to this linelist should replace - * it, not extend it. Set only on the first item in a linelist in an - * or_options_t. */ - unsigned int fragile:1; -} config_line_t; - typedef struct routerset_t routerset_t; /** A magic value for the (Socks|OR|...)Port options below, telling Tor @@ -3624,10 +3626,6 @@ typedef struct { int DisableAllSwap; /**< Boolean: Attempt to call mlockall() on our * process for all current and future memory. */ - /** List of "entry", "middle", "exit", "introduction", "rendezvous". */ - smartlist_t *AllowInvalidNodes; - /** Bitmask; derived from AllowInvalidNodes. */ - invalid_router_usage_t AllowInvalid_; config_line_t *ExitPolicy; /**< Lists of exit policy components. */ int ExitPolicyRejectPrivate; /**< Should we not exit to reserved private * addresses, and our own published addresses? @@ -3638,21 +3636,6 @@ typedef struct { * configured ports. */ config_line_t *SocksPolicy; /**< Lists of socks policy components */ config_line_t *DirPolicy; /**< Lists of dir policy components */ - /** Addresses to bind for listening for SOCKS connections. */ - config_line_t *SocksListenAddress; - /** Addresses to bind for listening for transparent pf/netfilter - * connections. */ - config_line_t *TransListenAddress; - /** Addresses to bind for listening for transparent natd connections */ - config_line_t *NATDListenAddress; - /** Addresses to bind for listening for SOCKS connections. */ - config_line_t *DNSListenAddress; - /** Addresses to bind for listening for OR connections. */ - config_line_t *ORListenAddress; - /** Addresses to bind for listening for directory connections. */ - config_line_t *DirListenAddress; - /** Addresses to bind for listening for control connections. */ - config_line_t *ControlListenAddress; /** Local address to bind outbound sockets */ config_line_t *OutboundBindAddress; /** Local address to bind outbound relay sockets */ @@ -3674,7 +3657,6 @@ typedef struct { /** Whether routers accept EXTEND cells to routers with private IPs. */ int ExtendAllowPrivateAddresses; char *User; /**< Name of user to run Tor as. */ - char *Group; /**< Name of group to run Tor as. */ config_line_t *ORPort_lines; /**< Ports to listen on for OR connections. */ /** Ports to listen on for extended OR connections. */ config_line_t *ExtORPort_lines; @@ -3777,6 +3759,15 @@ typedef struct { int AvoidDiskWrites; /**< Boolean: should we never cache things to disk? * Not used yet. */ int ClientOnly; /**< Boolean: should we never evolve into a server role? */ + + int ReducedConnectionPadding; /**< Boolean: Should we try to keep connections + open shorter and pad them less against + connection-level traffic analysis? */ + /** Autobool: if auto, then connection padding will be negotiated by client + * and server. If 0, it will be fully disabled. If 1, the client will still + * pad to the server regardless of server support. */ + int ConnectionPadding; + /** To what authority types do we publish our descriptor? Choices are * "v1", "v2", "v3", "bridge", or "". */ smartlist_t *PublishServerDescriptor; @@ -3802,15 +3793,6 @@ typedef struct { /** A routerset that should be used when picking RPs for HS circuits. */ routerset_t *Tor2webRendezvousPoints; - /** Close hidden service client circuits immediately when they reach - * the normal circuit-build timeout, even if they have already sent - * an INTRODUCE1 cell on its way to the service. */ - int CloseHSClientCircuitsImmediatelyOnTimeout; - - /** Close hidden-service-side rendezvous circuits immediately when - * they reach the normal circuit-build timeout. */ - int CloseHSServiceRendCircuitsImmediatelyOnTimeout; - /** Onion Services in HiddenServiceSingleHopMode make one-hop (direct) * circuits between the onion service server, and the introduction and * rendezvous points. (Onion service descriptors are still posted using @@ -3891,8 +3873,8 @@ typedef struct { int CircuitBuildTimeout; /**< Cull non-open circuits that were born at * least this many seconds ago. Used until * adaptive algorithm learns a new value. */ - int CircuitIdleTimeout; /**< Cull open clean circuits that were born - * at least this many seconds ago. */ + int CircuitsAvailableTimeout; /**< Try to have an open circuit for at + least this long after last activity */ int CircuitStreamTimeout; /**< If non-zero, detach streams from circuits * and try a new circuit if the stream has been * waiting for this many seconds. If zero, use @@ -3902,10 +3884,6 @@ typedef struct { * a new one? */ int MaxCircuitDirtiness; /**< Never use circs that were first used more than this interval ago. */ - int PredictedPortsRelevanceTime; /** How long after we've requested a - * connection for a given port, do we want - * to continue to pick exits that support - * that port? */ uint64_t BandwidthRate; /**< How much bandwidth, on average, are we willing * to use in a second? */ uint64_t BandwidthBurst; /**< How much bandwidth, at maximum, are we willing @@ -3919,8 +3897,6 @@ typedef struct { uint64_t PerConnBWRate; /**< Long-term bw on a single TLS conn, if set. */ uint64_t PerConnBWBurst; /**< Allowed burst on a single TLS conn, if set. */ int NumCPUs; /**< How many CPUs should we try to use? */ -//int RunTesting; /**< If true, create testing circuits to measure how well the -// * other ORs are running. */ config_line_t *RendConfigLines; /**< List of configuration lines * for rendezvous services. */ config_line_t *HidServAuth; /**< List of configuration lines for client-side @@ -3971,7 +3947,8 @@ typedef struct { /** If set, use these bridge authorities and not the default one. */ config_line_t *AlternateBridgeAuthority; - char *MyFamily; /**< Declared family for this OR. */ + config_line_t *MyFamily_lines; /**< Declared family for this OR. */ + config_line_t *MyFamily; /**< Declared family for this OR, normalized */ config_line_t *NodeFamilies; /**< List of config lines for * node families */ smartlist_t *NodeFamilySets; /**< List of parsed NodeFamilies values. */ @@ -4090,8 +4067,6 @@ typedef struct { int NumDirectoryGuards; /**< How many dir guards do we try to establish? * If 0, use value from NumEntryGuards. */ int RephistTrackTime; /**< How many seconds do we keep rephist info? */ - int FastFirstHopPK; /**< If Tor believes it is safe, should we save a third - * of our PK time by sending CREATE_FAST cells? */ /** Should we always fetch our dir info on the mirror schedule (which * means directly from the authorities) no matter our other config? */ int FetchDirInfoEarly; @@ -4147,16 +4122,6 @@ typedef struct { * if we are a cache). For authorities, this is always true. */ int DownloadExtraInfo; - /** If true, and we are acting as a relay, allow exit circuits even when - * we are the first hop of a circuit. */ - int AllowSingleHopExits; - /** If true, don't allow relays with AllowSingleHopExits=1 to be used in - * circuits that we build. */ - int ExcludeSingleHopRelays; - /** If true, and the controller tells us to use a one-hop circuit, and the - * exit allows it, we use it. */ - int AllowSingleHopCircuits; - /** If true, we convert "www.google.com.foo.exit" addresses on the * socks/trans/natd ports into "www.google.com" addresses that * exit from the node "foo". Disabled by default since attacking @@ -4164,10 +4129,6 @@ typedef struct { * selection. */ int AllowDotExit; - /** If true, we will warn if a user gives us only an IP address - * instead of a hostname. */ - int WarnUnsafeSocks; - /** If true, we're configured to collect statistics on clients * requesting network statuses from us as directory. */ int DirReqStatistics_option; @@ -4184,6 +4145,9 @@ typedef struct { /** If true, the user wants us to collect cell statistics. */ int CellStatistics; + /** If true, the user wants us to collect padding statistics. */ + int PaddingStatistics; + /** If true, the user wants us to collect statistics as entry node. */ int EntryStatistics; @@ -4391,8 +4355,7 @@ typedef struct { int TestingDirAuthVoteGuardIsStrict; /** Relays in a testing network which should be voted HSDir - * regardless of uptime and DirPort. - * Respects VoteOnHidServDirectoriesV2. */ + * regardless of uptime and DirPort. */ routerset_t *TestingDirAuthVoteHSDir; int TestingDirAuthVoteHSDirIsStrict; @@ -4524,8 +4487,6 @@ typedef struct { int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */ - char *TLSECGroup; /**< One of "P256", "P224", or nil for auto */ - /** Fraction: */ double PathsNeededToBuildCircuits; @@ -4609,6 +4570,14 @@ typedef struct { * do we enforce Ed25519 identity match? */ /* NOTE: remove this option someday. */ int AuthDirTestEd25519LinkKeys; + + /** Bool (default: 0): Tells if a %include was used on torrc */ + int IncludeUsed; + + /** The seconds after expiration which we as a relay should keep old + * consensuses around so that we can generate diffs from them. If 0, + * use the default. */ + int MaxConsensusAgeForDiffs; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ @@ -4834,7 +4803,7 @@ typedef uint32_t build_time_t; double circuit_build_times_quantile_cutoff(void); /** How often in seconds should we build a test circuit */ -#define CBT_DEFAULT_TEST_FREQUENCY 60 +#define CBT_DEFAULT_TEST_FREQUENCY 10 #define CBT_MIN_TEST_FREQUENCY 1 #define CBT_MAX_TEST_FREQUENCY INT32_MAX @@ -5323,7 +5292,8 @@ typedef struct dir_server_t { * address information from published? */ routerstatus_t fake_status; /**< Used when we need to pass this trusted - * dir_server_t to directory_initiate_command_* + * dir_server_t to + * directory_request_set_routerstatus. * as a routerstatus_t. Not updated by the * router-status management code! **/ @@ -5376,7 +5346,6 @@ typedef enum { CRN_NEED_UPTIME = 1<<0, CRN_NEED_CAPACITY = 1<<1, CRN_NEED_GUARD = 1<<2, - CRN_ALLOW_INVALID = 1<<3, /* XXXX not used, apparently. */ CRN_WEIGHT_AS_EXIT = 1<<5, CRN_NEED_DESC = 1<<6, @@ -5391,8 +5360,6 @@ typedef enum { typedef enum was_router_added_t { /* Router was added successfully. */ ROUTER_ADDED_SUCCESSFULLY = 1, - /* Router descriptor was added with warnings to submitter. */ - ROUTER_ADDED_NOTIFY_GENERATOR = 0, /* Extrainfo document was rejected because no corresponding router * descriptor was found OR router descriptor was rejected because * it was incompatible with its extrainfo document. */ diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c index ec2cec69f7..7959867875 100644 --- a/src/or/parsecommon.c +++ b/src/or/parsecommon.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h index 15e9f7ae85..b9f1613457 100644 --- a/src/or/parsecommon.h +++ b/src/or/parsecommon.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -157,12 +157,18 @@ typedef enum { R3_SUPERENCRYPTED, R3_SIGNATURE, R3_CREATE2_FORMATS, - R3_AUTHENTICATION_REQUIRED, + R3_INTRO_AUTH_REQUIRED, R3_SINGLE_ONION_SERVICE, R3_INTRODUCTION_POINT, R3_INTRO_AUTH_KEY, R3_INTRO_ENC_KEY, - R3_INTRO_ENC_KEY_CERTIFICATION, + R3_INTRO_ENC_KEY_CERT, + R3_INTRO_LEGACY_KEY, + R3_INTRO_LEGACY_KEY_CERT, + R3_DESC_AUTH_TYPE, + R3_DESC_AUTH_KEY, + R3_DESC_AUTH_CLIENT, + R3_ENCRYPTED, R_IPO_IDENTIFIER, R_IPO_IP_ADDRESS, diff --git a/src/or/periodic.c b/src/or/periodic.c index d02d4a7bbb..6896b41c86 100644 --- a/src/or/periodic.c +++ b/src/or/periodic.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/periodic.h b/src/or/periodic.h index 021bb4ef5c..88d00cc7e9 100644 --- a/src/or/periodic.h +++ b/src/or/periodic.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_PERIODIC_H diff --git a/src/or/policies.c b/src/or/policies.c index 2aa6373f3e..3d49a6110c 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/policies.h b/src/or/policies.h index f73f850c21..ce08d497e9 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/protover.c b/src/or/protover.c index c76f028699..0946092692 100644 --- a/src/or/protover.c +++ b/src/or/protover.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -288,7 +288,7 @@ protover_get_supported_protocols(void) return "Cons=1-2 " "Desc=1-2 " - "DirCache=1 " + "DirCache=1-2 " "HSDir=1-2 " "HSIntro=3-4 " "HSRend=1-2 " diff --git a/src/or/protover.h b/src/or/protover.h index 5c658931ea..22667bed79 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/reasons.c b/src/or/reasons.c index a1566e2299..e6c325f1b3 100644 --- a/src/or/reasons.c +++ b/src/or/reasons.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/reasons.h b/src/or/reasons.h index 2e12c93728..1cadf4e89e 100644 --- a/src/or/reasons.h +++ b/src/or/reasons.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/relay.c b/src/or/relay.c index e504643f35..a86ae9bc43 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -48,12 +48,14 @@ #define RELAY_PRIVATE #include "or.h" #include "addressmap.h" +#include "backtrace.h" #include "buffers.h" #include "channel.h" #include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" +#include "compress.h" #include "config.h" #include "connection.h" #include "connection_edge.h" @@ -74,6 +76,7 @@ #include "routerlist.h" #include "routerparse.h" #include "scheduler.h" +#include "rephist.h" static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, @@ -196,6 +199,82 @@ relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in, return 0; } +/** + * Update channel usage state based on the type of relay cell and + * circuit properties. + * + * This is needed to determine if a client channel is being + * used for application traffic, and if a relay channel is being + * used for multihop circuits and application traffic. The decision + * to pad in channelpadding.c depends upon this info (as well as + * consensus parameters) to decide what channels to pad. + */ +static void +circuit_update_channel_usage(circuit_t *circ, cell_t *cell) +{ + if (CIRCUIT_IS_ORIGIN(circ)) { + /* + * The client state was first set much earlier in + * circuit_send_next_onion_skin(), so we can start padding as early as + * possible. + * + * However, if padding turns out to be expensive, we may want to not do + * it until actual application traffic starts flowing (which is controlled + * via consensus param nf_pad_before_usage). + * + * So: If we're an origin circuit and we've created a full length circuit, + * then any CELL_RELAY cell means application data. Increase the usage + * state of the channel to indicate this. + * + * We want to wait for CELL_RELAY specifically here, so we know that + * the channel was definitely being used for data and not for extends. + * By default, we pad as soon as a channel has been used for *any* + * circuits, so this state is irrelevant to the padding decision in + * the default case. However, if padding turns out to be expensive, + * we would like the ability to avoid padding until we're absolutely + * sure that a channel is used for enough application data to be worth + * padding. + * + * (So it does not matter that CELL_RELAY_EARLY can actually contain + * application data. This is only a load reducing option and that edge + * case does not matter if we're desperately trying to reduce overhead + * anyway. See also consensus parameter nf_pad_before_usage). + */ + if (BUG(!circ->n_chan)) + return; + + if (circ->n_chan->channel_usage == CHANNEL_USED_FOR_FULL_CIRCS && + cell->command == CELL_RELAY) { + circ->n_chan->channel_usage = CHANNEL_USED_FOR_USER_TRAFFIC; + } + } else { + /* If we're a relay circuit, the question is more complicated. Basically: + * we only want to pad connections that carry multihop (anonymous) + * circuits. + * + * We assume we're more than one hop if either the previous hop + * is not a client, or if the previous hop is a client and there's + * a next hop. Then, circuit traffic starts at RELAY_EARLY, and + * user application traffic starts when we see RELAY cells. + */ + or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); + + if (BUG(!or_circ->p_chan)) + return; + + if (!channel_is_client(or_circ->p_chan) || + (channel_is_client(or_circ->p_chan) && circ->n_chan)) { + if (cell->command == CELL_RELAY_EARLY) { + if (or_circ->p_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) { + or_circ->p_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; + } + } else if (cell->command == CELL_RELAY) { + or_circ->p_chan->channel_usage = CHANNEL_USED_FOR_USER_TRAFFIC; + } + } + } +} + /** Receive a relay cell: * - Crypt it (encrypt if headed toward the origin or if we <b>are</b> the * origin; decrypt if we're headed toward the exit). @@ -225,10 +304,13 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, return 0; if (relay_crypt(circ, cell, cell_direction, &layer_hint, &recognized) < 0) { - log_warn(LD_BUG,"relay crypt failed. Dropping connection."); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "relay crypt failed. Dropping connection."); return -END_CIRC_REASON_INTERNAL; } + circuit_update_channel_usage(circ, cell); + if (recognized) { edge_connection_t *conn = NULL; @@ -257,8 +339,13 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, log_debug(LD_OR,"Sending to origin."); if ((reason = connection_edge_process_relay_cell(cell, circ, conn, layer_hint)) < 0) { - log_warn(LD_OR, - "connection_edge_process_relay_cell (at origin) failed."); + /* If a client is trying to connect to unknown hidden service port, + * END_CIRC_AT_ORIGIN is sent back so we can then close the circuit. + * Do not log warn as this is an expected behavior for a service. */ + if (reason != END_CIRC_AT_ORIGIN) { + log_warn(LD_OR, + "connection_edge_process_relay_cell (at origin) failed."); + } return reason; } } @@ -430,11 +517,13 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, if (!chan) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d has n_chan==NULL." " Dropping.", filename, lineno); + log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } if (!CIRCUIT_IS_ORIGIN(circ)) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d on non-origin " "circ. Dropping.", filename, lineno); + log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } @@ -637,6 +726,9 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ, log_debug(LD_OR,"delivering %d cell %s.", relay_command, cell_direction == CELL_DIRECTION_OUT ? "forward" : "backward"); + if (relay_command == RELAY_COMMAND_DROP) + rep_hist_padding_count_write(PADDING_TYPE_DROP); + /* If we are sending an END cell and this circuit is used for a tunneled * directory request, advance its state. */ if (relay_command == RELAY_COMMAND_END && circ->dirreq_id) @@ -743,6 +835,16 @@ connection_edge_send_command(edge_connection_t *fromconn, return -1; } +#ifdef MEASUREMENTS_21206 + /* Keep track of the number of RELAY_DATA cells sent for directory + * connections. */ + connection_t *linked_conn = TO_CONN(fromconn)->linked_conn; + + if (linked_conn && linked_conn->type == CONN_TYPE_DIR) { + ++(TO_DIR_CONN(linked_conn)->data_cells_sent); + } +#endif + return relay_send_command_from_edge(fromconn->stream_id, circ, relay_command, payload, payload_len, cpath_layer); @@ -1525,6 +1627,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, switch (rh.command) { case RELAY_COMMAND_DROP: + rep_hist_padding_count_read(PADDING_TYPE_DROP); // log_info(domain,"Got a relay-level padding cell. Dropping."); return 0; case RELAY_COMMAND_BEGIN: @@ -1598,6 +1701,16 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE), rh.length, TO_CONN(conn)); +#ifdef MEASUREMENTS_21206 + /* Count number of RELAY_DATA cells received on a linked directory + * connection. */ + connection_t *linked_conn = TO_CONN(conn)->linked_conn; + + if (linked_conn && linked_conn->type == CONN_TYPE_DIR) { + ++(TO_DIR_CONN(linked_conn)->data_cells_received); + } +#endif + if (!optimistic_data) { /* Only send a SENDME if we're not getting optimistic data; otherwise * a SENDME could arrive before the CONNECTED. @@ -2507,7 +2620,7 @@ cell_queues_check_size(void) { size_t alloc = cell_queues_get_total_allocation(); alloc += buf_get_total_allocation(); - alloc += tor_zlib_get_total_allocation(); + alloc += tor_compress_get_total_allocation(); const size_t rend_cache_total = rend_cache_get_total_allocation(); alloc += rend_cache_total; if (alloc >= get_options()->MaxMemInQueues_low_threshold) { diff --git a/src/or/relay.h b/src/or/relay.h index f01cbf1298..9dc0b5d3a2 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendcache.c b/src/or/rendcache.c index 12c23ea87c..11b60b36a1 100644 --- a/src/or/rendcache.c +++ b/src/or/rendcache.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendcache.h b/src/or/rendcache.h index 746f142fcc..1bd3be2243 100644 --- a/src/or/rendcache.h +++ b/src/or/rendcache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 06744ad795..9e2daf0380 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -265,6 +265,11 @@ rend_client_send_introduction(origin_circuit_t *introcirc, klen = crypto_pk_asn1_encode(extend_info->onion_key, tmp+v3_shift+7+DIGEST_LEN+2, sizeof(tmp)-(v3_shift+7+DIGEST_LEN+2)); + if (klen < 0) { + log_warn(LD_BUG,"Internal error: can't encode public key."); + status = -2; + goto perm_err; + } set_uint16(tmp+v3_shift+7+DIGEST_LEN, htons(klen)); memcpy(tmp+v3_shift+7+DIGEST_LEN+2+klen, rendcirc->rend_data->rend_cookie, REND_COOKIE_LEN); @@ -724,6 +729,9 @@ directory_get_from_hs_dir(const char *desc_id, hs_dir = pick_hsdir(desc_id, 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_hs_descriptor_content(rend_data_get_address(rend_query), + desc_id_base32, NULL, NULL); return 0; } } @@ -744,6 +752,9 @@ 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_hs_descriptor_content(rend_data_get_address(rend_query), + desc_id_base32, hsdir_fp, NULL); return 0; } /* Remove == signs. */ @@ -756,13 +767,15 @@ directory_get_from_hs_dir(const char *desc_id, /* Send fetch request. (Pass query and possibly descriptor cookie so that * they can be written to the directory connection and be referred to when * the response arrives. */ - directory_initiate_command_routerstatus_rend(hs_dir, - DIR_PURPOSE_FETCH_RENDDESC_V2, - ROUTER_PURPOSE_GENERAL, - how_to_fetch, - desc_id_base32, - NULL, 0, 0, - rend_query, NULL); + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_RENDDESC_V2); + directory_request_set_routerstatus(req, hs_dir); + directory_request_set_indirection(req, how_to_fetch); + directory_request_set_resource(req, desc_id_base32); + directory_request_set_rend_query(req, rend_query); + directory_initiate_request(req); + directory_request_free(req); + log_info(LD_REND, "Sending fetch request for v2 descriptor for " "service '%s' with descriptor ID '%s', auth type %d, " "and descriptor cookie '%s' to hidden service " diff --git a/src/or/rendclient.h b/src/or/rendclient.h index 164305a773..ff0f4084fd 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index bc53762fb6..d18356e67a 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -477,7 +477,10 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, tor_assert(descriptor_cookie); } /* Obtain service_id from public key. */ - crypto_pk_get_digest(service_key, service_id); + if (crypto_pk_get_digest(service_key, service_id) < 0) { + log_warn(LD_BUG, "Couldn't compute service key digest."); + return -1; + } /* Calculate current time-period. */ time_period = get_time_period(now, period, service_id); /* Determine how many seconds the descriptor will be valid. */ diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 942ace5761..94c2480d86 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendmid.c b/src/or/rendmid.c index 57c8cfac92..23c3deddaa 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -96,7 +96,8 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, /* Close any other intro circuits with the same pk. */ c = NULL; - while ((c = hs_circuitmap_get_intro_circ_v2((const uint8_t *)pk_digest))) { + while ((c = hs_circuitmap_get_intro_circ_v2_relay_side( + (const uint8_t *)pk_digest))) { log_info(LD_REND, "Replacing old circuit for service %s", safe_str(serviceid)); circuit_mark_for_close(TO_CIRCUIT(c), END_CIRC_REASON_FINISHED); @@ -111,7 +112,7 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, /* Now, set up this circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT); - hs_circuitmap_register_intro_circ_v2(circ, (uint8_t *)pk_digest); + hs_circuitmap_register_intro_circ_v2_relay_side(circ, (uint8_t *)pk_digest); log_info(LD_REND, "Established introduction point on circuit %u for service %s", @@ -165,7 +166,8 @@ rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request, /* The first 20 bytes are all we look at: they have a hash of the service's * PK. */ - intro_circ = hs_circuitmap_get_intro_circ_v2((const uint8_t*)request); + intro_circ = hs_circuitmap_get_intro_circ_v2_relay_side( + (const uint8_t*)request); if (!intro_circ) { log_info(LD_REND, "No intro circ found for INTRODUCE1 cell (%s) from circuit %u; " @@ -242,7 +244,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, goto err; } - if (hs_circuitmap_get_rend_circ(request)) { + if (hs_circuitmap_get_rend_circ_relay_side(request)) { log_warn(LD_PROTOCOL, "Duplicate rendezvous cookie in ESTABLISH_RENDEZVOUS."); goto err; @@ -258,7 +260,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, } circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_POINT_WAITING); - hs_circuitmap_register_rend_circ(circ, request); + hs_circuitmap_register_rend_circ_relay_side(circ, request); base16_encode(hexid,9,(char*)request,4); @@ -307,7 +309,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, "Got request for rendezvous from circuit %u to cookie %s.", (unsigned)circ->p_circ_id, hexid); - rend_circ = hs_circuitmap_get_rend_circ(request); + rend_circ = hs_circuitmap_get_rend_circ_relay_side(request); if (!rend_circ) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Rejecting RENDEZVOUS1 cell with unrecognized rendezvous cookie %s.", @@ -342,7 +344,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_ESTABLISHED); circuit_change_purpose(TO_CIRCUIT(rend_circ), CIRCUIT_PURPOSE_REND_ESTABLISHED); - hs_circuitmap_remove_circuit(circ); + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); rend_circ->rend_splice = circ; circ->rend_splice = rend_circ; diff --git a/src/or/rendmid.h b/src/or/rendmid.h index 347d745853..daf9e2885e 100644 --- a/src/or/rendmid.h +++ b/src/or/rendmid.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendservice.c b/src/or/rendservice.c index ec39e3b80e..b1e8a2f0cd 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -76,13 +76,11 @@ static ssize_t rend_service_parse_intro_for_v3( static int rend_service_check_private_dir(const or_options_t *options, const rend_service_t *s, int create); -static int rend_service_check_private_dir_impl(const or_options_t *options, - const rend_service_t *s, - int create); static const smartlist_t* rend_get_service_list( const smartlist_t* substitute_service_list); static smartlist_t* rend_get_service_list_mutable( smartlist_t* substitute_service_list); +static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted); /** Represents the mapping from a virtual port of a rendezvous service to * a real port on some IP. @@ -100,23 +98,6 @@ struct rend_service_port_config_s { char unix_addr[FLEXIBLE_ARRAY_MEMBER]; }; -/** Try to maintain this many intro points per service by default. */ -#define NUM_INTRO_POINTS_DEFAULT 3 -/** Maximum number of intro points per service. */ -#define NUM_INTRO_POINTS_MAX 10 -/** Number of extra intro points we launch if our set of intro nodes is - * empty. See proposal 155, section 4. */ -#define NUM_INTRO_POINTS_EXTRA 2 - -/** If we can't build our intro circuits, don't retry for this long. */ -#define INTRO_CIRC_RETRY_PERIOD (60*5) -/** How many times will a hidden service operator attempt to connect to - * a requested rendezvous point before giving up? */ -#define MAX_REND_FAILURES 1 -/** How many seconds should we spend trying to connect to a requested - * rendezvous point before giving up? */ -#define MAX_REND_TIMEOUT 30 - /* Hidden service directory file names: * new file names should be added to rend_service_add_filenames_to_list() * for sandboxing purposes. */ @@ -125,9 +106,12 @@ static const char *hostname_fname = "hostname"; static const char *client_keys_fname = "client_keys"; static const char *sos_poison_fname = "onion_service_non_anonymous"; -/** A list of rend_service_t's for services run on this OP. - */ +/** A list of rend_service_t's for services run on this OP. */ static smartlist_t *rend_service_list = NULL; +/** A list of rend_service_t's for services run on this OP which is used as a + * staging area before they are put in the main list in order to prune dying + * service on config reload. */ +static smartlist_t *rend_service_staging_list = NULL; /* Like rend_get_service_list_mutable, but returns a read-only list. */ static const smartlist_t* @@ -261,35 +245,23 @@ rend_service_free_all(void) rend_service_list = NULL; } -/** Validate <b>service</b> and add it to <b>service_list</b>, or to - * the global rend_service_list if <b>service_list</b> is NULL. - * Return 0 on success. On failure, free <b>service</b> and return -1. - * Takes ownership of <b>service</b>. - */ +/* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there + * is no duplicate entry for the given service object. Return 0 if valid else + * -1 if not.*/ static int -rend_add_service(smartlist_t *service_list, rend_service_t *service) +rend_validate_service(const smartlist_t *service_list, + const rend_service_t *service) { - int i; - rend_service_port_config_t *p; + int dupe = 0; + tor_assert(service_list); tor_assert(service); - smartlist_t *s_list = rend_get_service_list_mutable(service_list); - /* We must have a service list, even if it's a temporary one, so we can - * check for duplicate services */ - if (BUG(!s_list)) { - return -1; - } - - service->intro_nodes = smartlist_new(); - service->expiring_nodes = smartlist_new(); - if (service->max_streams_per_circuit < 0) { log_warn(LD_CONFIG, "Hidden service (%s) configured with negative max " "streams per circuit.", rend_service_escaped_dir(service)); - rend_service_free(service); - return -1; + goto invalid; } if (service->max_streams_close_circuit < 0 || @@ -297,87 +269,107 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) log_warn(LD_CONFIG, "Hidden service (%s) configured with invalid " "max streams handling.", rend_service_escaped_dir(service)); - rend_service_free(service); - return -1; + goto invalid; } if (service->auth_type != REND_NO_AUTH && - (!service->clients || - smartlist_len(service->clients) == 0)) { - log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but no " - "clients.", + (!service->clients || smartlist_len(service->clients) == 0)) { + log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but " + "no clients.", rend_service_escaped_dir(service)); - rend_service_free(service); - return -1; + goto invalid; } if (!service->ports || !smartlist_len(service->ports)) { log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.", rend_service_escaped_dir(service)); - rend_service_free(service); - return -1; - } else { - int dupe = 0; - /* XXX This duplicate check has two problems: - * - * a) It's O(n^2), but the same comment from the bottom of - * rend_config_services() should apply. - * - * b) We only compare directory paths as strings, so we can't - * detect two distinct paths that specify the same directory - * (which can arise from symlinks, case-insensitivity, bind - * mounts, etc.). - * - * It also can't detect that two separate Tor instances are trying - * to use the same HiddenServiceDir; for that, we would need a - * lock file. But this is enough to detect a simple mistake that - * at least one person has actually made. - */ - tor_assert(s_list); - if (!rend_service_is_ephemeral(service)) { - /* Skip dupe for ephemeral services. */ - SMARTLIST_FOREACH(s_list, rend_service_t*, ptr, - dupe = dupe || - !strcmp(ptr->directory, service->directory)); - if (dupe) { - log_warn(LD_REND, "Another hidden service is already configured for " - "directory %s.", - rend_service_escaped_dir(service)); - rend_service_free(service); - return -1; - } + goto invalid; + } + + /* XXX This duplicate check has two problems: + * + * a) It's O(n^2), but the same comment from the bottom of + * rend_config_services() should apply. + * + * b) We only compare directory paths as strings, so we can't + * detect two distinct paths that specify the same directory + * (which can arise from symlinks, case-insensitivity, bind + * mounts, etc.). + * + * It also can't detect that two separate Tor instances are trying + * to use the same HiddenServiceDir; for that, we would need a + * lock file. But this is enough to detect a simple mistake that + * at least one person has actually made. + */ + if (!rend_service_is_ephemeral(service)) { + /* Skip dupe for ephemeral services. */ + SMARTLIST_FOREACH(service_list, rend_service_t *, ptr, + dupe = dupe || + !strcmp(ptr->directory, service->directory)); + if (dupe) { + log_warn(LD_REND, "Another hidden service is already configured for " + "directory %s.", + rend_service_escaped_dir(service)); + goto invalid; } - log_debug(LD_REND,"Configuring service with directory %s", - rend_service_escaped_dir(service)); - for (i = 0; i < smartlist_len(service->ports); ++i) { - p = smartlist_get(service->ports, i); - if (!(p->is_unix_addr)) { - log_debug(LD_REND, - "Service maps port %d to %s", - p->virtual_port, - fmt_addrport(&p->real_addr, p->real_port)); - } else { + } + + /* Valid. */ + return 0; + invalid: + return -1; +} + +/** Add it to <b>service_list</b>, or to the global rend_service_list if + * <b>service_list</b> is NULL. Return 0 on success. On failure, free + * <b>service</b> and return -1. Takes ownership of <b>service</b>. */ +static int +rend_add_service(smartlist_t *service_list, rend_service_t *service) +{ + int i; + rend_service_port_config_t *p; + + tor_assert(service); + + smartlist_t *s_list = rend_get_service_list_mutable(service_list); + /* We must have a service list, even if it's a temporary one, so we can + * check for duplicate services */ + if (BUG(!s_list)) { + return -1; + } + + service->intro_nodes = smartlist_new(); + service->expiring_nodes = smartlist_new(); + + log_debug(LD_REND,"Configuring service with directory %s", + rend_service_escaped_dir(service)); + for (i = 0; i < smartlist_len(service->ports); ++i) { + p = smartlist_get(service->ports, i); + if (!(p->is_unix_addr)) { + log_debug(LD_REND, + "Service maps port %d to %s", + p->virtual_port, + fmt_addrport(&p->real_addr, p->real_port)); + } else { #ifdef HAVE_SYS_UN_H - log_debug(LD_REND, - "Service maps port %d to socket at \"%s\"", - p->virtual_port, p->unix_addr); + log_debug(LD_REND, + "Service maps port %d to socket at \"%s\"", + p->virtual_port, p->unix_addr); #else - log_warn(LD_BUG, - "Service maps port %d to an AF_UNIX socket, but we " - "have no AF_UNIX support on this platform. This is " - "probably a bug.", - p->virtual_port); - rend_service_free(service); - return -1; + log_warn(LD_BUG, + "Service maps port %d to an AF_UNIX socket, but we " + "have no AF_UNIX support on this platform. This is " + "probably a bug.", + p->virtual_port); + rend_service_free(service); + return -1; #endif /* defined(HAVE_SYS_UN_H) */ - } } - /* The service passed all the checks */ - tor_assert(s_list); - smartlist_add(s_list, service); - return 0; } - /* NOTREACHED */ + /* The service passed all the checks */ + tor_assert(s_list); + smartlist_add(s_list, service); + return 0; } /** Return a new rend_service_port_config_t with its path set to @@ -539,18 +531,34 @@ rend_service_check_dir_and_add(smartlist_t *service_list, return rend_add_service(s_list, service); } -/* If this is a reload and there were hidden services configured before, - * keep the introduction points that are still needed and close the - * other ones. */ +/* Helper: Actual implementation of the pruning on reload which we've + * decoupled in order to make the unit test workeable without ugly hacks. + * Furthermore, this function does NOT free any memory but will nullify the + * temporary list pointer whatever happens. */ STATIC void -prune_services_on_reload(smartlist_t *old_service_list, - smartlist_t *new_service_list) +rend_service_prune_list_impl_(void) { origin_circuit_t *ocirc = NULL; - smartlist_t *surviving_services = NULL; + smartlist_t *surviving_services, *old_service_list, *new_service_list; - tor_assert(old_service_list); - tor_assert(new_service_list); + /* When pruning our current service list, we must have a staging list that + * contains what we want to check else it's a code flow error. */ + tor_assert(rend_service_staging_list); + + /* We are about to prune the current list of its dead service so set the + * semantic for that list to be the "old" one. */ + old_service_list = rend_service_list; + /* The staging list is now the "new" list so set this semantic. */ + new_service_list = rend_service_staging_list; + /* After this, whatever happens, we'll use our new list. */ + rend_service_list = new_service_list; + /* Finally, nullify the staging list pointer as we don't need it anymore + * and it needs to be NULL before the next reload. */ + rend_service_staging_list = NULL; + /* Nothing to prune if we have no service list so stop right away. */ + if (!old_service_list) { + return; + } /* This contains all _existing_ services that survives the relaod that is * that haven't been removed from the configuration. The difference between @@ -628,6 +636,27 @@ prune_services_on_reload(smartlist_t *old_service_list, smartlist_free(surviving_services); } +/* Try to prune our main service list using the temporary one that we just + * loaded and parsed successfully. The pruning process decides which onion + * services to keep and which to discard after a reload. */ +void +rend_service_prune_list(void) +{ + smartlist_t *old_service_list = rend_service_list; + /* Don't try to prune anything if we have no staging list. */ + if (!rend_service_staging_list) { + return; + } + rend_service_prune_list_impl_(); + if (old_service_list) { + /* Every remaining service in the old list have been removed from the + * configuration so clean them up safely. */ + SMARTLIST_FOREACH(old_service_list, rend_service_t *, s, + rend_service_free(s)); + smartlist_free(old_service_list); + } +} + /** Set up rend_service_list, based on the values of HiddenServiceDir and * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on * failure. (If <b>validate_only</b> is set, parse, warn and return as @@ -639,24 +668,30 @@ rend_config_services(const or_options_t *options, int validate_only) config_line_t *line; rend_service_t *service = NULL; rend_service_port_config_t *portcfg; - smartlist_t *old_service_list = NULL; - smartlist_t *temp_service_list = NULL; int ok = 0; int rv = -1; - /* Use a temporary service list, so that we can check the new services' - * consistency with each other */ - temp_service_list = smartlist_new(); + /* Use the staging service list so that we can check then do the pruning + * process using the main list at the end. */ + if (rend_service_staging_list == NULL) { + rend_service_staging_list = smartlist_new(); + } for (line = options->RendConfigLines; line; line = line->next) { if (!strcasecmp(line->key, "HiddenServiceDir")) { - /* register the service we just finished parsing - * this code registers every service except the last one parsed, - * which is registered below the loop */ - if (rend_service_check_dir_and_add(temp_service_list, options, service, - validate_only) < 0) { - service = NULL; - goto free_and_return; + if (service) { + /* Validate and register the service we just finished parsing this + * code registers every service except the last one parsed, which is + * validated and registered below the loop. */ + if (rend_validate_service(rend_service_staging_list, service) < 0) { + goto free_and_return; + } + if (rend_service_check_dir_and_add(rend_service_staging_list, options, + service, validate_only) < 0) { + /* The above frees the service on error so nullify the pointer. */ + service = NULL; + goto free_and_return; + } } service = tor_malloc_zero(sizeof(rend_service_t)); service->directory = tor_strdup(line->value); @@ -851,14 +886,23 @@ rend_config_services(const or_options_t *options, int validate_only) } } } + /* Validate the last service that we just parsed. */ + if (service && + rend_validate_service(rend_service_staging_list, service) < 0) { + goto free_and_return; + } /* register the final service after we have finished parsing all services * this code only registers the last service, other services are registered * within the loop. It is ok for this service to be NULL, it is ignored. */ - if (rend_service_check_dir_and_add(temp_service_list, options, service, - validate_only) < 0) { + if (rend_service_check_dir_and_add(rend_service_staging_list, options, + service, validate_only) < 0) { + /* Service object is freed on error so nullify pointer. */ service = NULL; goto free_and_return; } + /* The service is in the staging list so nullify pointer to avoid double + * free of this object in case of error because we lost ownership of it at + * this point. */ service = NULL; /* Free the newly added services if validating */ @@ -867,31 +911,19 @@ rend_config_services(const or_options_t *options, int validate_only) goto free_and_return; } - /* Otherwise, use the newly added services as the new service list - * Since we have now replaced the global service list, from this point on we - * must succeed, or die trying. */ - old_service_list = rend_service_list; - rend_service_list = temp_service_list; - temp_service_list = NULL; - - /* If this is a reload and there were hidden services configured before, - * keep the introduction points that are still needed and close the - * other ones. */ - if (old_service_list && !validate_only) { - prune_services_on_reload(old_service_list, rend_service_list); - /* Every remaining service in the old list have been removed from the - * configuration so clean them up safely. */ - SMARTLIST_FOREACH(old_service_list, rend_service_t *, s, - rend_service_free(s)); - smartlist_free(old_service_list); - } + /* This could be a reload of configuration so try to prune the main list + * using the staging one. And we know we are not in validate mode here. + * After this, the main and staging list will point to the right place and + * be in a quiescent usable state. */ + rend_service_prune_list(); return 0; free_and_return: rend_service_free(service); - SMARTLIST_FOREACH(temp_service_list, rend_service_t *, ptr, + SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t *, ptr, rend_service_free(ptr)); - smartlist_free(temp_service_list); + smartlist_free(rend_service_staging_list); + rend_service_staging_list = NULL; return rv; } @@ -1025,6 +1057,45 @@ rend_service_del_ephemeral(const char *service_id) return 0; } +/* There can be 1 second's delay due to second_elapsed_callback, and perhaps + * another few seconds due to blocking calls. */ +#define INTRO_CIRC_RETRY_PERIOD_SLOP 10 + +/** Log information about the intro point creation rate and current intro + * points for service, upgrading the log level from min_severity to warn if + * we have stopped launching new intro point circuits. */ +static void +rend_log_intro_limit(const rend_service_t *service, int min_severity) +{ + int exceeded_limit = (service->n_intro_circuits_launched >= + rend_max_intro_circs_per_period( + service->n_intro_points_wanted)); + int severity = min_severity; + /* We stopped creating circuits */ + if (exceeded_limit) { + severity = LOG_WARN; + } + time_t intro_period_elapsed = time(NULL) - service->intro_period_started; + tor_assert_nonfatal(intro_period_elapsed >= 0); + { + char *msg; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, approx_time()))) { + log_fn(severity, LD_REND, + "Hidden service %s %s %d intro points in the last %d seconds. " + "Intro circuit launches are limited to %d per %d seconds.%s", + service->service_id, + exceeded_limit ? "exceeded launch limit with" : "launched", + service->n_intro_circuits_launched, + (int)intro_period_elapsed, + rend_max_intro_circs_per_period(service->n_intro_points_wanted), + INTRO_CIRC_RETRY_PERIOD, msg); + rend_service_dump_stats(severity); + tor_free(msg); + } + } +} + /** Replace the old value of <b>service</b>-\>desc with one that reflects * the other fields in service. */ @@ -1032,7 +1103,6 @@ static void rend_service_update_descriptor(rend_service_t *service) { rend_service_descriptor_t *d; - origin_circuit_t *circ; int i; rend_service_descriptor_free(service->desc); @@ -1053,9 +1123,10 @@ rend_service_update_descriptor(rend_service_t *service) /* This intro point won't be listed in the descriptor... */ intro_svc->listed_in_last_desc = 0; - circ = find_intro_circuit(intro_svc, service->pk_digest); - if (!circ || circ->base_.purpose != CIRCUIT_PURPOSE_S_INTRO) { - /* This intro point's circuit isn't finished yet. Don't list it. */ + /* circuit_established is set in rend_service_intro_established(), and + * checked every second in rend_consider_services_intro_points(), so it's + * safe to use it here */ + if (!intro_svc->circuit_established) { continue; } @@ -1077,6 +1148,26 @@ rend_service_update_descriptor(rend_service_t *service) intro_svc->time_published = time(NULL); } } + + /* Check that we have the right number of intro points */ + unsigned int have_intro = (unsigned int)smartlist_len(d->intro_nodes); + if (have_intro != service->n_intro_points_wanted) { + int severity; + /* Getting less than we wanted or more than we're allowed is serious */ + if (have_intro < service->n_intro_points_wanted || + have_intro > NUM_INTRO_POINTS_MAX) { + severity = LOG_WARN; + } else { + /* Getting more than we wanted is weird, but less of a problem */ + severity = LOG_NOTICE; + } + log_fn(severity, LD_REND, "Hidden service %s wanted %d intro points, but " + "descriptor was updated with %d instead.", + service->service_id, + service->n_intro_points_wanted, have_intro); + /* Now log an informative message about how we might have got here. */ + rend_log_intro_limit(service, severity); + } } /* Allocate and return a string containing the path to file_name in @@ -1234,7 +1325,8 @@ poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service, } /* Make sure the directory was created before calling this function. */ - if (BUG(rend_service_check_private_dir_impl(options, service, 0) < 0)) + if (BUG(hs_check_service_private_dir(options->User, service->directory, + service->dir_group_readable, 0) < 0)) return -1; poison_fname = rend_service_sos_poison_path(service); @@ -1384,32 +1476,6 @@ rend_service_derive_key_digests(struct rend_service_t *s) return 0; } -/* Implements the directory check from rend_service_check_private_dir, - * without doing the single onion poison checks. */ -static int -rend_service_check_private_dir_impl(const or_options_t *options, - const rend_service_t *s, - int create) -{ - cpd_check_t check_opts = CPD_NONE; - if (create) { - check_opts |= CPD_CREATE; - } else { - check_opts |= CPD_CHECK_MODE_ONLY; - check_opts |= CPD_CHECK; - } - if (s->dir_group_readable) { - check_opts |= CPD_GROUP_READ; - } - /* Check/create directory */ - if (check_private_dir(s->directory, check_opts, options->User) < 0) { - log_warn(LD_REND, "Checking service directory %s failed.", s->directory); - return -1; - } - - return 0; -} - /** Make sure that the directory for <b>s</b> is private, using the config in * <b>options</b>. * If <b>create</b> is true: @@ -1430,7 +1496,8 @@ rend_service_check_private_dir(const or_options_t *options, } /* Check/create directory */ - if (rend_service_check_private_dir_impl(options, s, create) < 0) { + if (hs_check_service_private_dir(options->User, s->directory, + s->dir_group_readable, create) < 0) { return -1; } @@ -2754,7 +2821,14 @@ rend_service_decrypt_intro( /* Check that this cell actually matches this service key */ /* first DIGEST_LEN bytes of request is intro or service pk digest */ - crypto_pk_get_digest(key, (char *)key_digest); + if (crypto_pk_get_digest(key, (char *)key_digest) < 0) { + if (err_msg_out) + *err_msg_out = tor_strdup("Couldn't compute RSA digest."); + log_warn(LD_BUG, "Couldn't compute key digest."); + status = -7; + goto err; + } + if (tor_memneq(key_digest, intro->pk, DIGEST_LEN)) { if (err_msg_out) { base32_encode(service_id, REND_SERVICE_ID_LEN_BASE32 + 1, @@ -3670,13 +3744,16 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, * request. Lookup is made in rend_service_desc_has_uploaded(). */ rend_data = rend_data_client_create(service_id, desc->desc_id, NULL, REND_NO_AUTH); - directory_initiate_command_routerstatus_rend(hs_dir, - DIR_PURPOSE_UPLOAD_RENDDESC_V2, - ROUTER_PURPOSE_GENERAL, - DIRIND_ANONYMOUS, NULL, - desc->desc_str, - strlen(desc->desc_str), - 0, rend_data, NULL); + directory_request_t *req = + directory_request_new(DIR_PURPOSE_UPLOAD_RENDDESC_V2); + directory_request_set_routerstatus(req, hs_dir); + directory_request_set_indirection(req, DIRIND_ANONYMOUS); + directory_request_set_payload(req, + desc->desc_str, strlen(desc->desc_str)); + directory_request_set_rend_query(req, rend_data); + directory_initiate_request(req); + directory_request_free(req); + rend_data_free(rend_data); base32_encode(desc_id_base32, sizeof(desc_id_base32), desc->desc_id, DIGEST_LEN); @@ -4029,7 +4106,12 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted) /* Allow all but one of the initial connections to fail and be * retried. (If all fail, we *want* to wait, because something is broken.) */ tor_assert(n_intro_points_wanted <= NUM_INTRO_POINTS_MAX); - return (int)(2*n_intro_points_wanted + NUM_INTRO_POINTS_EXTRA); + + /* For the normal use case, 3 intro points plus 2 extra for performance and + * allow that twice because once every 24h or so, we can do it twice for two + * descriptors that is the current one and the next one. So (3 + 2) * 2 == + * 12 allowed attempts for one period. */ + return ((n_intro_points_wanted + NUM_INTRO_POINTS_EXTRA) * 2); } /** For every service, check how many intro points it currently has, and: @@ -4082,8 +4164,12 @@ rend_consider_services_intro_points(void) /* This retry period is important here so we don't stress circuit * creation. */ + if (now > service->intro_period_started + INTRO_CIRC_RETRY_PERIOD) { - /* One period has elapsed; we can try building circuits again. */ + /* One period has elapsed: + * - if we stopped, we can try building circuits again, + * - if we haven't, we reset the circuit creation counts. */ + rend_log_intro_limit(service, LOG_INFO); service->intro_period_started = now; service->n_intro_circuits_launched = 0; } else if (service->n_intro_circuits_launched >= @@ -4091,6 +4177,7 @@ rend_consider_services_intro_points(void) service->n_intro_points_wanted)) { /* We have failed too many times in this period; wait for the next * one before we try to initiate any more connections. */ + rend_log_intro_limit(service, LOG_WARN); continue; } @@ -4143,8 +4230,6 @@ rend_consider_services_intro_points(void) const node_t *node; rend_intro_point_t *intro; router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC; - if (get_options()->AllowInvalid_ & ALLOW_INVALID_INTRODUCTION) - flags |= CRN_ALLOW_INVALID; router_crn_flags_t direct_flags = flags; direct_flags |= CRN_PREF_ADDR; direct_flags |= CRN_DIRECT_CONN; @@ -4536,3 +4621,19 @@ rend_service_non_anonymous_mode_enabled(const or_options_t *options) return options->HiddenServiceNonAnonymousMode ? 1 : 0; } +#ifdef TOR_UNIT_TESTS + +STATIC void +set_rend_service_list(smartlist_t *new_list) +{ + rend_service_list = new_list; +} + +STATIC void +set_rend_rend_service_staging_list(smartlist_t *new_list) +{ + rend_service_staging_list = new_list; +} + +#endif /* TOR_UNIT_TESTS */ + diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 85daaae4e2..1583a6010b 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -133,13 +133,19 @@ STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out, size_t cell_body_out_len, crypto_pk_t *intro_key, char *rend_circ_nonce); -STATIC void prune_services_on_reload(smartlist_t *old_service_list, - smartlist_t *new_service_list); +#ifdef TOR_UNIT_TESTS -#endif +STATIC void set_rend_service_list(smartlist_t *new_list); +STATIC void set_rend_rend_service_staging_list(smartlist_t *new_list); +STATIC void rend_service_prune_list_impl_(void); + +#endif /* TOR_UNIT_TESTS */ + +#endif /* RENDSERVICE_PRIVATE */ int num_rend_services(void); int rend_config_services(const or_options_t *options, int validate_only); +void rend_service_prune_list(void); int rend_service_load_all_keys(const smartlist_t *service_list); void rend_services_add_filenames_to_lists(smartlist_t *open_lst, smartlist_t *stat_lst); diff --git a/src/or/rephist.c b/src/or/rephist.c index f0bac57898..ffc1867955 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -84,9 +84,13 @@ #include "router.h" #include "routerlist.h" #include "ht.h" +#include "channelpadding.h" + +#include "channelpadding.h" +#include "connection_or.h" static void bw_arrays_init(void); -static void predicted_ports_init(void); +static void predicted_ports_alloc(void); /** Total number of bytes currently allocated in fields used by rephist.c. */ uint64_t rephist_total_alloc=0; @@ -165,6 +169,44 @@ typedef struct or_history_t { digestmap_t *link_history_map; } or_history_t; +/** + * This structure holds accounting needed to calculate the padding overhead. + */ +typedef struct padding_counts_t { + /** Total number of cells we have received, including padding */ + uint64_t read_cell_count; + /** Total number of cells we have sent, including padding */ + uint64_t write_cell_count; + /** Total number of CELL_PADDING cells we have received */ + uint64_t read_pad_cell_count; + /** Total number of CELL_PADDING cells we have sent */ + uint64_t write_pad_cell_count; + /** Total number of read cells on padding-enabled conns */ + uint64_t enabled_read_cell_count; + /** Total number of sent cells on padding-enabled conns */ + uint64_t enabled_write_cell_count; + /** Total number of read CELL_PADDING cells on padding-enabled cons */ + uint64_t enabled_read_pad_cell_count; + /** Total number of sent CELL_PADDING cells on padding-enabled cons */ + uint64_t enabled_write_pad_cell_count; + /** Total number of RELAY_DROP cells we have received */ + uint64_t read_drop_cell_count; + /** Total number of RELAY_DROP cells we have sent */ + uint64_t write_drop_cell_count; + /** The maximum number of padding timers we've seen in 24 hours */ + uint64_t maximum_chanpad_timers; + /** When did we first copy padding_current into padding_published? */ + char first_published_at[ISO_TIME_LEN+1]; +} padding_counts_t; + +/** Holds the current values of our padding statistics. + * It is not published until it is transferred to padding_published. */ +static padding_counts_t padding_current; + +/** Remains fixed for a 24 hour period, and then is replaced + * by a redacted copy of padding_current */ +static padding_counts_t padding_published; + /** When did we last multiply all routers' weighted_run_length and * total_run_weights by STABILITY_ALPHA? */ static time_t stability_last_downrated = 0; @@ -264,7 +306,7 @@ rep_hist_init(void) { history_map = digestmap_new(); bw_arrays_init(); - predicted_ports_init(); + predicted_ports_alloc(); } /** Helper: note that we are no longer connected to the router with history @@ -905,9 +947,9 @@ rep_hist_record_mtbf_data(time_t now, int missing_means_down) base16_encode(dbuf, sizeof(dbuf), digest, DIGEST_LEN); if (missing_means_down && hist->start_of_run && - !router_get_by_id_digest(digest)) { + !connection_or_digest_is_known_relay(digest)) { /* We think this relay is running, but it's not listed in our - * routerlist. Somehow it fell out without telling us it went + * consensus. Somehow it fell out without telling us it went * down. Complain and also correct it. */ log_info(LD_HIST, "Relay '%s' is listed as up in rephist, but it's not in " @@ -1758,6 +1800,40 @@ typedef struct predicted_port_t { /** A list of port numbers that have been used recently. */ static smartlist_t *predicted_ports_list=NULL; +/** How long do we keep predicting circuits? */ +static int prediction_timeout=0; +/** When was the last time we added a prediction entry (HS or port) */ +static time_t last_prediction_add_time=0; + +/** + * How much time left until we stop predicting circuits? + */ +int +predicted_ports_prediction_time_remaining(time_t now) +{ + time_t idle_delta = now - last_prediction_add_time; + + /* Protect against overflow of return value. This can happen if the clock + * jumps backwards in time. Update the last prediction time (aka last + * active time) to prevent it. This update is preferable to using monotonic + * time because it prevents clock jumps into the past from simply causing + * very long idle timeouts while the monotonic time stands still. */ + if (last_prediction_add_time > now) { + last_prediction_add_time = now; + idle_delta = 0; + } + + /* Protect against underflow of the return value. This can happen for very + * large periods of inactivity/system sleep. */ + if (idle_delta > prediction_timeout) + return 0; + + if (BUG((prediction_timeout - idle_delta) > INT_MAX)) { + return INT_MAX; + } + + return (int)(prediction_timeout - idle_delta); +} /** We just got an application request for a connection with * port <b>port</b>. Remember it for the future, so we can keep @@ -1767,21 +1843,40 @@ static void add_predicted_port(time_t now, uint16_t port) { predicted_port_t *pp = tor_malloc(sizeof(predicted_port_t)); + + // If the list is empty, re-randomize predicted ports lifetime + if (!any_predicted_circuits(now)) { + prediction_timeout = channelpadding_get_circuits_available_timeout(); + } + + last_prediction_add_time = now; + + log_info(LD_CIRC, + "New port prediction added. Will continue predictive circ building " + "for %d more seconds.", + predicted_ports_prediction_time_remaining(now)); + pp->port = port; pp->time = now; rephist_total_alloc += sizeof(*pp); smartlist_add(predicted_ports_list, pp); } -/** Initialize whatever memory and structs are needed for predicting +/** + * Allocate whatever memory and structs are needed for predicting * which ports will be used. Also seed it with port 80, so we'll build * circuits on start-up. */ static void -predicted_ports_init(void) +predicted_ports_alloc(void) { predicted_ports_list = smartlist_new(); - add_predicted_port(time(NULL), 80); /* add one to kickstart us */ +} + +void +predicted_ports_init(void) +{ + add_predicted_port(time(NULL), 443); // Add a port to get us started } /** Free whatever memory is needed for predicting which ports will @@ -1812,6 +1907,12 @@ rep_hist_note_used_port(time_t now, uint16_t port) SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) { if (pp->port == port) { pp->time = now; + + last_prediction_add_time = now; + log_info(LD_CIRC, + "New port prediction added. Will continue predictive circ " + "building for %d more seconds.", + predicted_ports_prediction_time_remaining(now)); return; } } SMARTLIST_FOREACH_END(pp); @@ -1828,7 +1929,8 @@ rep_hist_get_predicted_ports(time_t now) int predicted_circs_relevance_time; smartlist_t *out = smartlist_new(); tor_assert(predicted_ports_list); - predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime; + + predicted_circs_relevance_time = prediction_timeout; /* clean out obsolete entries */ SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) { @@ -1888,6 +1990,18 @@ static time_t predicted_internal_capacity_time = 0; void rep_hist_note_used_internal(time_t now, int need_uptime, int need_capacity) { + // If the list is empty, re-randomize predicted ports lifetime + if (!any_predicted_circuits(now)) { + prediction_timeout = channelpadding_get_circuits_available_timeout(); + } + + last_prediction_add_time = now; + + log_info(LD_CIRC, + "New port prediction added. Will continue predictive circ building " + "for %d more seconds.", + predicted_ports_prediction_time_remaining(now)); + predicted_internal_time = now; if (need_uptime) predicted_internal_uptime_time = now; @@ -1901,7 +2015,8 @@ rep_hist_get_predicted_internal(time_t now, int *need_uptime, int *need_capacity) { int predicted_circs_relevance_time; - predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime; + + predicted_circs_relevance_time = prediction_timeout; if (!predicted_internal_time) { /* initialize it */ predicted_internal_time = now; @@ -1923,7 +2038,7 @@ int any_predicted_circuits(time_t now) { int predicted_circs_relevance_time; - predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime; + predicted_circs_relevance_time = prediction_timeout; return smartlist_len(predicted_ports_list) || predicted_internal_time + predicted_circs_relevance_time >= now; @@ -3210,8 +3325,7 @@ rep_hist_hs_stats_write(time_t now) return start_of_hs_stats_interval + WRITE_STATS_INTERVAL; } -#define MAX_LINK_PROTO_TO_LOG 4 -static uint64_t link_proto_count[MAX_LINK_PROTO_TO_LOG+1][2]; +static uint64_t link_proto_count[MAX_LINK_PROTO+1][2]; /** Note that we negotiated link protocol version <b>link_proto</b>, on * a connection that started here iff <b>started_here</b> is true. @@ -3220,7 +3334,7 @@ void rep_hist_note_negotiated_link_proto(unsigned link_proto, int started_here) { started_here = !!started_here; /* force to 0 or 1 */ - if (link_proto > MAX_LINK_PROTO_TO_LOG) { + if (link_proto > MAX_LINK_PROTO) { log_warn(LD_BUG, "Can't log link protocol %u", link_proto); return; } @@ -3228,6 +3342,165 @@ rep_hist_note_negotiated_link_proto(unsigned link_proto, int started_here) link_proto_count[link_proto][started_here]++; } +/** + * Update the maximum count of total pending channel padding timers + * in this period. + */ +void +rep_hist_padding_count_timers(uint64_t num_timers) +{ + if (num_timers > padding_current.maximum_chanpad_timers) { + padding_current.maximum_chanpad_timers = num_timers; + } +} + +/** + * Count a cell that we sent for padding overhead statistics. + * + * RELAY_COMMAND_DROP and CELL_PADDING are accounted separately. Both should be + * counted for PADDING_TYPE_TOTAL. + */ +void +rep_hist_padding_count_write(padding_type_t type) +{ + switch (type) { + case PADDING_TYPE_DROP: + padding_current.write_drop_cell_count++; + break; + case PADDING_TYPE_CELL: + padding_current.write_pad_cell_count++; + break; + case PADDING_TYPE_TOTAL: + padding_current.write_cell_count++; + break; + case PADDING_TYPE_ENABLED_TOTAL: + padding_current.enabled_write_cell_count++; + break; + case PADDING_TYPE_ENABLED_CELL: + padding_current.enabled_write_pad_cell_count++; + break; + } +} + +/** + * Count a cell that we've received for padding overhead statistics. + * + * RELAY_COMMAND_DROP and CELL_PADDING are accounted separately. Both should be + * counted for PADDING_TYPE_TOTAL. + */ +void +rep_hist_padding_count_read(padding_type_t type) +{ + switch (type) { + case PADDING_TYPE_DROP: + padding_current.read_drop_cell_count++; + break; + case PADDING_TYPE_CELL: + padding_current.read_pad_cell_count++; + break; + case PADDING_TYPE_TOTAL: + padding_current.read_cell_count++; + break; + case PADDING_TYPE_ENABLED_TOTAL: + padding_current.enabled_read_cell_count++; + break; + case PADDING_TYPE_ENABLED_CELL: + padding_current.enabled_read_pad_cell_count++; + break; + } +} + +/** + * Reset our current padding statistics. Called once every 24 hours. + */ +void +rep_hist_reset_padding_counts(void) +{ + memset(&padding_current, 0, sizeof(padding_current)); +} + +/** + * Copy our current cell counts into a structure for listing in our + * extra-info descriptor. Also perform appropriate rounding and redaction. + * + * This function is called once every 24 hours. + */ +#define MIN_CELL_COUNTS_TO_PUBLISH 1 +#define ROUND_CELL_COUNTS_TO 10000 +void +rep_hist_prep_published_padding_counts(time_t now) +{ + memcpy(&padding_published, &padding_current, sizeof(padding_published)); + + if (padding_published.read_cell_count < MIN_CELL_COUNTS_TO_PUBLISH || + padding_published.write_cell_count < MIN_CELL_COUNTS_TO_PUBLISH) { + memset(&padding_published, 0, sizeof(padding_published)); + return; + } + + format_iso_time(padding_published.first_published_at, now); +#define ROUND_AND_SET_COUNT(x) (x) = round_uint64_to_next_multiple_of((x), \ + ROUND_CELL_COUNTS_TO) + ROUND_AND_SET_COUNT(padding_published.read_pad_cell_count); + ROUND_AND_SET_COUNT(padding_published.write_pad_cell_count); + ROUND_AND_SET_COUNT(padding_published.read_drop_cell_count); + ROUND_AND_SET_COUNT(padding_published.write_drop_cell_count); + ROUND_AND_SET_COUNT(padding_published.write_cell_count); + ROUND_AND_SET_COUNT(padding_published.read_cell_count); + ROUND_AND_SET_COUNT(padding_published.enabled_read_cell_count); + ROUND_AND_SET_COUNT(padding_published.enabled_read_pad_cell_count); + ROUND_AND_SET_COUNT(padding_published.enabled_write_cell_count); + ROUND_AND_SET_COUNT(padding_published.enabled_write_pad_cell_count); +#undef ROUND_AND_SET_COUNT +} + +/** + * Returns an allocated string for extra-info documents for publishing + * padding statistics from the last 24 hour interval. + */ +char * +rep_hist_get_padding_count_lines(void) +{ + char *result = NULL; + + if (!padding_published.read_cell_count || + !padding_published.write_cell_count) { + return NULL; + } + + tor_asprintf(&result, "padding-counts %s (%d s)" + " bin-size="U64_FORMAT + " write-drop="U64_FORMAT + " write-pad="U64_FORMAT + " write-total="U64_FORMAT + " read-drop="U64_FORMAT + " read-pad="U64_FORMAT + " read-total="U64_FORMAT + " enabled-read-pad="U64_FORMAT + " enabled-read-total="U64_FORMAT + " enabled-write-pad="U64_FORMAT + " enabled-write-total="U64_FORMAT + " max-chanpad-timers="U64_FORMAT + "\n", + padding_published.first_published_at, + REPHIST_CELL_PADDING_COUNTS_INTERVAL, + U64_PRINTF_ARG(ROUND_CELL_COUNTS_TO), + U64_PRINTF_ARG(padding_published.write_drop_cell_count), + U64_PRINTF_ARG(padding_published.write_pad_cell_count), + U64_PRINTF_ARG(padding_published.write_cell_count), + U64_PRINTF_ARG(padding_published.read_drop_cell_count), + U64_PRINTF_ARG(padding_published.read_pad_cell_count), + U64_PRINTF_ARG(padding_published.read_cell_count), + U64_PRINTF_ARG(padding_published.enabled_read_pad_cell_count), + U64_PRINTF_ARG(padding_published.enabled_read_cell_count), + U64_PRINTF_ARG(padding_published.enabled_write_pad_cell_count), + U64_PRINTF_ARG(padding_published.enabled_write_cell_count), + U64_PRINTF_ARG(padding_published.maximum_chanpad_timers) + ); + + return result; +} + /** Log a heartbeat message explaining how many connections of each link * protocol version we have used. */ diff --git a/src/or/rephist.h b/src/or/rephist.h index ff4810a56d..2b1c2e7ec7 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -48,6 +48,7 @@ double rep_hist_get_weighted_fractional_uptime(const char *id, time_t when); long rep_hist_get_weighted_time_known(const char *id, time_t when); int rep_hist_have_measured_enough_stability(void); +void predicted_ports_init(void); void rep_hist_note_used_port(time_t now, uint16_t port); smartlist_t *rep_hist_get_predicted_ports(time_t now); void rep_hist_remove_predicted_ports(const smartlist_t *rmv_ports); @@ -59,6 +60,7 @@ int rep_hist_get_predicted_internal(time_t now, int *need_uptime, int any_predicted_circuits(time_t now); int rep_hist_circbuilding_dormant(time_t now); +int predicted_ports_prediction_time_remaining(time_t now); void note_crypto_pk_op(pk_op_t operation); void dump_pk_ops(int severity); @@ -119,5 +121,30 @@ extern int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1]; extern int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1]; #endif +/** + * Represents the type of a cell for padding accounting + */ +typedef enum padding_type_t { + /** A RELAY_DROP cell */ + PADDING_TYPE_DROP, + /** A CELL_PADDING cell */ + PADDING_TYPE_CELL, + /** Total counts of padding and non-padding together */ + PADDING_TYPE_TOTAL, + /** Total cell counts for all padding-enabled channels */ + PADDING_TYPE_ENABLED_TOTAL, + /** CELL_PADDING counts for all padding-enabled channels */ + PADDING_TYPE_ENABLED_CELL +} padding_type_t; + +/** The amount of time over which the padding cell counts were counted */ +#define REPHIST_CELL_PADDING_COUNTS_INTERVAL (24*60*60) +void rep_hist_padding_count_read(padding_type_t type); +void rep_hist_padding_count_write(padding_type_t type); +char *rep_hist_get_padding_count_lines(void); +void rep_hist_reset_padding_counts(void); +void rep_hist_prep_published_padding_counts(time_t now); +void rep_hist_padding_count_timers(uint64_t num_timers); + #endif diff --git a/src/or/replaycache.c b/src/or/replaycache.c index 8290fa6964..3d42deb90a 100644 --- a/src/or/replaycache.c +++ b/src/or/replaycache.c @@ -1,4 +1,4 @@ - /* Copyright (c) 2012-2016, The Tor Project, Inc. */ + /* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/replaycache.h b/src/or/replaycache.h index 64a6caf5f5..0d637939a4 100644 --- a/src/or/replaycache.h +++ b/src/or/replaycache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/router.c b/src/or/router.c index 53f213210e..55b58e5d11 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTER_PRIVATE @@ -148,6 +148,51 @@ dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last) tor_mutex_release(key_lock); } +/** Expire our old set of onion keys. This is done by setting + * last_curve25519_onion_key and lastonionkey to all zero's and NULL + * respectively. + * + * This function does not perform any grace period checks for the old onion + * keys. + */ +void +expire_old_onion_keys(void) +{ + char *fname = NULL; + + tor_mutex_acquire(key_lock); + + /* Free lastonionkey and set it to NULL. */ + if (lastonionkey) { + crypto_pk_free(lastonionkey); + lastonionkey = NULL; + } + + /* We zero out the keypair. See the tor_mem_is_zero() check made in + * construct_ntor_key_map() below. */ + memset(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key)); + + tor_mutex_release(key_lock); + + fname = get_datadir_fname2("keys", "secret_onion_key.old"); + if (file_status(fname) == FN_FILE) { + if (tor_unlink(fname) != 0) { + log_warn(LD_FS, "Couldn't unlink old onion key file %s: %s", + fname, strerror(errno)); + } + } + tor_free(fname); + + fname = get_datadir_fname2("keys", "secret_onion_key_ntor.old"); + if (file_status(fname) == FN_FILE) { + if (tor_unlink(fname) != 0) { + log_warn(LD_FS, "Couldn't unlink old ntor onion key file %s: %s", + fname, strerror(errno)); + } + } + tor_free(fname); +} + /** Return the current secret onion key for the ntor handshake. Must only * be called from the main thread. */ static const curve25519_keypair_t * @@ -212,7 +257,11 @@ set_server_identity_key(crypto_pk_t *k) { crypto_pk_free(server_identitykey); server_identitykey = k; - crypto_pk_get_digest(server_identitykey, server_identitykey_digest); + if (crypto_pk_get_digest(server_identitykey, + server_identitykey_digest) < 0) { + log_err(LD_BUG, "Couldn't compute our own identity key digest."); + tor_assert(0); + } } /** Make sure that we have set up our identity keys to match or not match as @@ -683,6 +732,47 @@ v3_authority_check_key_expiry(void) last_warned = now; } +/** Get the lifetime of an onion key in days. This value is defined by the + * network consesus parameter "onion-key-rotation-days". Always returns a value + * between <b>MIN_ONION_KEY_LIFETIME_DAYS</b> and + * <b>MAX_ONION_KEY_LIFETIME_DAYS</b>. + */ +static int +get_onion_key_rotation_days_(void) +{ + return networkstatus_get_param(NULL, + "onion-key-rotation-days", + DEFAULT_ONION_KEY_LIFETIME_DAYS, + MIN_ONION_KEY_LIFETIME_DAYS, + MAX_ONION_KEY_LIFETIME_DAYS); +} + +/** Get the current lifetime of an onion key in seconds. This value is defined + * by the network consesus parameter "onion-key-rotation-days", but the value + * is converted to seconds. + */ +int +get_onion_key_lifetime(void) +{ + return get_onion_key_rotation_days_()*24*60*60; +} + +/** Get the grace period of an onion key in seconds. This value is defined by + * the network consesus parameter "onion-key-grace-period-days", but the value + * is converted to seconds. + */ +int +get_onion_key_grace_period(void) +{ + int grace_period; + grace_period = networkstatus_get_param(NULL, + "onion-key-grace-period-days", + DEFAULT_ONION_KEY_GRACE_PERIOD_DAYS, + MIN_ONION_KEY_GRACE_PERIOD_DAYS, + get_onion_key_rotation_days_()); + return grace_period*24*60*60; +} + /** Set up Tor's TLS contexts, based on our configuration and keys. Return 0 * on success, and -1 on failure. */ int @@ -693,12 +783,6 @@ router_initialize_tls_context(void) int lifetime = options->SSLKeyLifetime; if (public_server_mode(options)) flags |= TOR_TLS_CTX_IS_PUBLIC_SERVER; - if (options->TLSECGroup) { - if (!strcasecmp(options->TLSECGroup, "P256")) - flags |= TOR_TLS_CTX_USE_ECDHE_P256; - else if (!strcasecmp(options->TLSECGroup, "P224")) - flags |= TOR_TLS_CTX_USE_ECDHE_P224; - } if (!lifetime) { /* we should guess a good ssl cert lifetime */ /* choose between 5 and 365 days, and round to the day */ @@ -876,8 +960,12 @@ init_keys(void) } cert = get_my_v3_authority_cert(); if (cert) { - crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key, - v3_digest); + if (crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key, + v3_digest) < 0) { + log_err(LD_BUG, "Couldn't compute my v3 authority identity key " + "digest."); + return -1; + } v3_digest_set = 1; } } @@ -929,7 +1017,7 @@ init_keys(void) /* We have no LastRotatedOnionKey set; either we just created the key * or it's a holdover from 0.1.2.4-alpha-dev or earlier. In either case, * start the clock ticking now so that we will eventually rotate it even - * if we don't stay up for a full MIN_ONION_KEY_LIFETIME. */ + * if we don't stay up for the full lifetime of an onion key. */ state->LastRotatedOnionKey = onionkey_set_at = now; or_state_mark_dirty(state, options->AvoidDiskWrites ? time(NULL)+3600 : 0); @@ -1385,14 +1473,23 @@ consider_testing_reachability(int test_or, int test_dir) !connection_get_by_type_addr_port_purpose( CONN_TYPE_DIR, &addr, me->dir_port, DIR_PURPOSE_FETCH_SERVERDESC)) { + tor_addr_port_t my_orport, my_dirport; + memcpy(&my_orport.addr, &addr, sizeof(addr)); + memcpy(&my_dirport.addr, &addr, sizeof(addr)); + my_orport.port = me->or_port; + my_dirport.port = me->dir_port; /* ask myself, via tor, for my server descriptor. */ - directory_initiate_command(&addr, me->or_port, - &addr, me->dir_port, - me->cache_info.identity_digest, - DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_GENERAL, - DIRIND_ANON_DIRPORT, "authority.z", - NULL, 0, 0, NULL); + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_SERVERDESC); + directory_request_set_or_addr_port(req, &my_orport); + directory_request_set_dir_addr_port(req, &my_dirport); + directory_request_set_directory_id_digest(req, + me->cache_info.identity_digest); + // ask via an anon circuit, connecting to our dirport. + directory_request_set_indirection(req, DIRIND_ANON_DIRPORT); + directory_request_set_resource(req, "authority.z"); + directory_initiate_request(req); + directory_request_free(req); } } @@ -1569,8 +1666,7 @@ MOCK_IMPL(int, server_mode,(const or_options_t *options)) { if (options->ClientOnly) return 0; - /* XXXX I believe we can kill off ORListenAddress here.*/ - return (options->ORPort_set || options->ORListenAddress); + return (options->ORPort_set); } /** Return true iff we are trying to be a non-bridge server. @@ -2194,17 +2290,15 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) } if (options->MyFamily && ! options->BridgeRelay) { - smartlist_t *family; if (!warned_nonexistent_family) warned_nonexistent_family = smartlist_new(); - family = smartlist_new(); ri->declared_family = smartlist_new(); - smartlist_split_string(family, options->MyFamily, ",", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0); - SMARTLIST_FOREACH_BEGIN(family, char *, name) { + config_line_t *family; + for (family = options->MyFamily; family; family = family->next) { + char *name = family->value; const node_t *member; if (!strcasecmp(name, options->Nickname)) - goto skip; /* Don't list ourself, that's redundant */ + continue; /* Don't list ourself, that's redundant */ else member = node_get_by_nickname(name, 1); if (!member) { @@ -2223,8 +2317,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) smartlist_add_strdup(warned_nonexistent_family, name); } if (is_legal) { - smartlist_add(ri->declared_family, name); - name = NULL; + smartlist_add_strdup(ri->declared_family, name); } } else if (router_digest_is_me(member->identity)) { /* Don't list ourself in our own family; that's redundant */ @@ -2238,15 +2331,11 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) if (smartlist_contains_string(warned_nonexistent_family, name)) smartlist_string_remove(warned_nonexistent_family, name); } - skip: - tor_free(name); - } SMARTLIST_FOREACH_END(name); + } /* remove duplicates from the list */ smartlist_sort_strings(ri->declared_family); smartlist_uniq_strings(ri->declared_family); - - smartlist_free(family); } /* Now generate the extrainfo. */ @@ -2762,7 +2851,7 @@ router_dump_router_to_string(routerinfo_t *router, make_ntor_onion_key_crosscert(ntor_keypair, &router->cache_info.signing_key_cert->signing_key, router->cache_info.published_on, - MIN_ONION_KEY_LIFETIME, &sign); + get_onion_key_lifetime(), &sign); if (!cert) { log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!"); goto err; @@ -2848,7 +2937,7 @@ router_dump_router_to_string(routerinfo_t *router, "onion-key\n%s" "signing-key\n%s" "%s%s" - "%s%s%s%s", + "%s%s%s", router->nickname, address, router->or_port, @@ -2871,8 +2960,7 @@ router_dump_router_to_string(routerinfo_t *router, ntor_cc_line ? ntor_cc_line : "", family_line, we_are_hibernating() ? "hibernating 1\n" : "", - "hidden-service-dir\n", - options->AllowSingleHopExits ? "allow-single-hop-exits\n" : ""); + "hidden-service-dir\n"); if (options->ContactInfo && strlen(options->ContactInfo)) { const char *ci = options->ContactInfo; @@ -3210,6 +3298,12 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, } } + if (options->PaddingStatistics) { + contents = rep_hist_get_padding_count_lines(); + if (contents) + smartlist_add(chunks, contents); + } + /* Add information about the pluggable transports we support. */ if (options->ServerTransportPlugin) { char *pluggable_transports = pt_get_extra_info_descriptor_string(); diff --git a/src/or/router.h b/src/or/router.h index c30a0301b7..9c5def5218 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -27,10 +27,13 @@ crypto_pk_t *get_my_v3_authority_signing_key(void); authority_cert_t *get_my_v3_legacy_cert(void); crypto_pk_t *get_my_v3_legacy_signing_key(void); void dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last); +void expire_old_onion_keys(void); void rotate_onion_key(void); crypto_pk_t *init_key_from_file(const char *fname, int generate, int severity, int log_greeting); void v3_authority_check_key_expiry(void); +int get_onion_key_lifetime(void); +int get_onion_key_grace_period(void); di_digest256_map_t *construct_ntor_key_map(void); void ntor_key_map_free(di_digest256_map_t *map); diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index aa7aee4b02..fd4c6ce0dd 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -690,6 +690,10 @@ load_ed_keys(const or_options_t *options, time_t now) tor_cert_t *auth_cert = NULL; int signing_key_changed = 0; + // It is later than 1972, since otherwise there would be no C compilers. + // (Try to diagnose #22466.) + tor_assert_nonfatal(now >= 2 * 365 * 86400); + #define FAIL(msg) do { \ log_warn(LD_OR, (msg)); \ goto err; \ @@ -1232,7 +1236,9 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN]; *len_out = 0; - crypto_pk_get_digest(rsa_id_key, (char*)signed_data); + if (crypto_pk_get_digest(rsa_id_key, (char*)signed_data) < 0) { + return NULL; + } memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN); int r = crypto_pk_private_sign(onion_key, diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h index 845abb4c70..c10cf32a71 100644 --- a/src/or/routerkeys.h +++ b/src/or/routerkeys.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_ROUTERKEYS_H diff --git a/src/or/routerlist.c b/src/or/routerlist.c index f2ab6051c7..4f4d417cce 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -426,8 +426,8 @@ list_sk_digests_for_authority_id, (const char *digest)) * download_status_t or NULL if none exists. */ MOCK_IMPL(download_status_t *, - download_status_for_authority_id_and_sk, - (const char *id_digest, const char *sk_digest)) +download_status_for_authority_id_and_sk,(const char *id_digest, + const char *sk_digest)) { download_status_t *dl = NULL; cert_list_t *cl = NULL; @@ -947,6 +947,7 @@ authority_certs_fetch_resource_impl(const char *resource, const dir_indirection_t indirection = get_via_tor ? DIRIND_ANONYMOUS : DIRIND_ONEHOP; + directory_request_t *req = NULL; /* If we've just downloaded a consensus from a bridge, re-use that * bridge */ if (options->UseBridges && node && node->ri && !get_via_tor) { @@ -955,23 +956,25 @@ authority_certs_fetch_resource_impl(const char *resource, /* we are willing to use a non-preferred address if we need to */ fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, &or_ap); - directory_initiate_command(&or_ap.addr, or_ap.port, - NULL, 0, /*no dirport*/ - dir_hint, - DIR_PURPOSE_FETCH_CERTIFICATE, - 0, - indirection, - resource, NULL, 0, 0, NULL); - return; - } - if (rs) { - /* If we've just downloaded a consensus from a directory, re-use that + req = directory_request_new(DIR_PURPOSE_FETCH_CERTIFICATE); + directory_request_set_or_addr_port(req, &or_ap); + if (dir_hint) + directory_request_set_directory_id_digest(req, dir_hint); + } else if (rs) { + /* And if we've just downloaded a consensus from a directory, re-use that * directory */ - directory_initiate_command_routerstatus(rs, - DIR_PURPOSE_FETCH_CERTIFICATE, - 0, indirection, resource, NULL, - 0, 0, NULL); + req = directory_request_new(DIR_PURPOSE_FETCH_CERTIFICATE); + directory_request_set_routerstatus(req, rs); + } + + if (req) { + /* We've set up a request object -- fill in the other request fields, and + * send the request. */ + directory_request_set_indirection(req, indirection); + directory_request_set_resource(req, resource); + directory_initiate_request(req); + directory_request_free(req); return; } @@ -2317,17 +2320,16 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) * we can pick a node for a circuit. */ void -router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, - int need_uptime, int need_capacity, - int need_guard, int need_desc, - int pref_addr, int direct_conn) +router_add_running_nodes_to_smartlist(smartlist_t *sl, int need_uptime, + int need_capacity, int need_guard, + int need_desc, int pref_addr, + int direct_conn) { const int check_reach = !router_skip_or_reachability(get_options(), pref_addr); /* XXXX MOVE */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { - if (!node->is_running || - (!node->is_valid && !allow_invalid)) + if (!node->is_running || !node->is_valid) continue; if (need_desc && !(node->ri || (node->rs && node->md))) continue; @@ -2773,8 +2775,6 @@ node_sl_choose_by_bandwidth(const smartlist_t *sl, * a minimum uptime, return one of those. * If <b>CRN_NEED_CAPACITY</b> is set in flags, weight your choice by the * advertised capacity of each router. - * If <b>CRN_ALLOW_INVALID</b> is not set in flags, consider only Valid - * routers. * If <b>CRN_NEED_GUARD</b> is set in flags, consider only Guard routers. * If <b>CRN_WEIGHT_AS_EXIT</b> is set in flags, we weight bandwidths as if * picking an exit node, otherwise we weight bandwidths for picking a relay @@ -2795,7 +2795,6 @@ router_choose_random_node(smartlist_t *excludedsmartlist, const int need_uptime = (flags & CRN_NEED_UPTIME) != 0; const int need_capacity = (flags & CRN_NEED_CAPACITY) != 0; const int need_guard = (flags & CRN_NEED_GUARD) != 0; - const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0; const int weight_for_exit = (flags & CRN_WEIGHT_AS_EXIT) != 0; const int need_desc = (flags & CRN_NEED_DESC) != 0; const int pref_addr = (flags & CRN_PREF_ADDR) != 0; @@ -2811,14 +2810,12 @@ router_choose_random_node(smartlist_t *excludedsmartlist, rule = weight_for_exit ? WEIGHT_FOR_EXIT : (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID); - /* Exclude relays that allow single hop exit circuits, if the user - * wants to (such relays might be risky) */ - if (get_options()->ExcludeSingleHopRelays) { - SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, - if (node_allows_single_hop_exits(node)) { - smartlist_add(excludednodes, node); - }); - } + /* Exclude relays that allow single hop exit circuits. This is an obsolete + * option since 0.2.9.2-alpha and done by default in 0.3.1.0-alpha. */ + SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + if (node_allows_single_hop_exits(node)) { + smartlist_add(excludednodes, node); + }); /* 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 @@ -2826,8 +2823,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, if ((r = router_get_my_routerinfo())) routerlist_add_node_and_family(excludednodes, r); - router_add_running_nodes_to_smartlist(sl, allow_invalid, - need_uptime, need_capacity, + router_add_running_nodes_to_smartlist(sl, need_uptime, need_capacity, need_guard, need_desc, pref_addr, direct_conn); log_debug(LD_CIRC, @@ -3048,8 +3044,8 @@ router_get_by_extrainfo_digest,(const char *digest)) /** Return the signed descriptor for the extrainfo_t in our routerlist whose * extra-info-digest is <b>digest</b>. Return NULL if no such extra-info * document is known. */ -signed_descriptor_t * -extrainfo_get_by_descriptor_digest(const char *digest) +MOCK_IMPL(signed_descriptor_t *, +extrainfo_get_by_descriptor_digest,(const char *digest)) { extrainfo_t *ei; tor_assert(digest); @@ -4881,9 +4877,10 @@ list_pending_fpsk_downloads(fp_pair_map_t *result) * range.) If <b>source</b> is given, download from <b>source</b>; * otherwise, download from an appropriate random directory server. */ -MOCK_IMPL(STATIC void, initiate_descriptor_downloads, - (const routerstatus_t *source, int purpose, smartlist_t *digests, - int lo, int hi, int pds_flags)) +MOCK_IMPL(STATIC void, +initiate_descriptor_downloads,(const routerstatus_t *source, + int purpose, smartlist_t *digests, + int lo, int hi, int pds_flags)) { char *resource, *cp; int digest_len, enc_digest_len; @@ -4935,10 +4932,11 @@ MOCK_IMPL(STATIC void, initiate_descriptor_downloads, if (source) { /* We know which authority or directory mirror we want. */ - directory_initiate_command_routerstatus(source, purpose, - ROUTER_PURPOSE_GENERAL, - DIRIND_ONEHOP, - resource, NULL, 0, 0, NULL); + directory_request_t *req = directory_request_new(purpose); + directory_request_set_routerstatus(req, source); + directory_request_set_resource(req, resource); + directory_initiate_request(req); + directory_request_free(req); } else { directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource, pds_flags, DL_WANT_ANY_DIRSERVER); @@ -5153,7 +5151,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, continue; /* We would throw it out immediately. */ } if (!we_want_to_fetch_flavor(options, consensus->flavor) && - !client_would_use_router(rs, now, options)) { + !client_would_use_router(rs, now)) { ++n_wouldnt_use; continue; /* We would never use it ourself. */ } diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 8b68d69f28..e0ed4e623a 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -62,10 +62,10 @@ int router_skip_or_reachability(const or_options_t *options, int try_ip_pref); int router_get_my_share_of_directory_requests(double *v3_share_out); void router_reset_status_download_failures(void); int routers_have_same_or_addrs(const routerinfo_t *r1, const routerinfo_t *r2); -void router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, - int need_uptime, int need_capacity, - int need_guard, int need_desc, - int pref_addr, int direct_conn); +void router_add_running_nodes_to_smartlist(smartlist_t *sl, int need_uptime, + int need_capacity, int need_guard, + int need_desc, int pref_addr, + int direct_conn); const routerinfo_t *routerlist_find_my_routerinfo(void); uint32_t router_get_advertised_bandwidth(const routerinfo_t *router); @@ -92,7 +92,8 @@ routerinfo_t *router_get_mutable_by_digest(const char *digest); signed_descriptor_t *router_get_by_descriptor_digest(const char *digest); MOCK_DECL(signed_descriptor_t *,router_get_by_extrainfo_digest, (const char *digest)); -signed_descriptor_t *extrainfo_get_by_descriptor_digest(const char *digest); +MOCK_DECL(signed_descriptor_t *,extrainfo_get_by_descriptor_digest, + (const char *digest)); const char *signed_descriptor_get_body(const signed_descriptor_t *desc); const char *signed_descriptor_get_annotations(const signed_descriptor_t *desc); routerlist_t *router_get_routerlist(void); @@ -123,7 +124,7 @@ static int WRA_NEVER_DOWNLOADABLE(was_router_added_t s); */ static inline int WRA_WAS_ADDED(was_router_added_t s) { - return s == ROUTER_ADDED_SUCCESSFULLY || s == ROUTER_ADDED_NOTIFY_GENERATOR; + return s == ROUTER_ADDED_SUCCESSFULLY; } /** Return true iff the outcome code in <b>s</b> indicates that the descriptor * was not added because it was either: diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 0336c035b4..22521a3069 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -359,6 +359,7 @@ static addr_policy_t *router_parse_addr_policy_private(directory_token_t *tok); static int router_get_hash_impl_helper(const char *s, size_t s_len, const char *start_str, const char *end_str, char end_c, + int log_severity, const char **start_out, const char **end_out); static int router_get_hash_impl(const char *s, size_t s_len, char *digest, const char *start_str, const char *end_str, @@ -988,6 +989,41 @@ router_get_router_hash(const char *s, size_t s_len, char *digest) DIGEST_SHA1); } +/** Try to find the start and end of the signed portion of a networkstatus + * document in <b>s</b>. On success, set <b>start_out</b> to the first + * character of the document, and <b>end_out</b> to a position one after the + * final character of the signed document, and return 0. On failure, return + * -1. */ +int +router_get_networkstatus_v3_signed_boundaries(const char *s, + const char **start_out, + const char **end_out) +{ + return router_get_hash_impl_helper(s, strlen(s), + "network-status-version", + "\ndirectory-signature", + ' ', LOG_INFO, + start_out, end_out); +} + +/** Set <b>digest_out</b> to the SHA3-256 digest of the signed portion of the + * networkstatus vote in <b>s</b> -- or of the entirety of <b>s</b> if no + * signed portion can be identified. Return 0 on success, -1 on failure. */ +int +router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, + const char *s) +{ + const char *start, *end; + if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) { + start = s; + end = s + strlen(s); + } + tor_assert(start); + tor_assert(end); + return crypto_digest256((char*)digest_out, start, end-start, + DIGEST_SHA3_256); +} + /** Set <b>digests</b> to all the digests of the consensus document in * <b>s</b> */ int @@ -1787,7 +1823,8 @@ router_parse_entry_from_string(const char *s, const char *end, if (router_get_hash_impl_helper(s, end-s, "router ", "\nrouter-sig-ed25519", - ' ', &signed_start, &signed_end) < 0) { + ' ', LOG_WARN, + &signed_start, &signed_end) < 0) { log_warn(LD_DIR, "Can't find ed25519-signed portion of descriptor"); goto err; } @@ -2030,6 +2067,9 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, * parse that's covered by the hash. */ int can_dl_again = 0; + if (BUG(s == NULL)) + return NULL; + if (!end) { end = s + strlen(s); } @@ -2137,7 +2177,8 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, if (router_get_hash_impl_helper(s, end-s, "extra-info ", "\nrouter-sig-ed25519", - ' ', &signed_start, &signed_end) < 0) { + ' ', LOG_WARN, + &signed_start, &signed_end) < 0) { log_warn(LD_DIR, "Can't find ed25519-signed portion of extrainfo"); goto err; } @@ -2544,7 +2585,7 @@ routerstatus_parse_entry_from_string(memarea_t *area, goto err; } } else if (flav == FLAV_MICRODESC) { - offset = -1; /* There is no identity digest */ + offset = -1; /* There is no descriptor digest in an md consensus r line */ } if (vote_rs) { @@ -2664,6 +2705,10 @@ routerstatus_parse_entry_from_string(memarea_t *area, protocol_list_supports_protocol(tok->args[0], PRT_RELAY, 2); rs->supports_ed25519_link_handshake = protocol_list_supports_protocol(tok->args[0], PRT_LINKAUTH, 3); + rs->supports_ed25519_hs_intro = + protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4); + rs->supports_v3_hsdir = + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -3339,6 +3384,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, networkstatus_voter_info_t *voter = NULL; networkstatus_t *ns = NULL; common_digests_t ns_digests; + uint8_t sha3_as_signed[DIGEST256_LEN]; const char *cert, *end_of_header, *end_of_footer, *s_dup = s; directory_token_t *tok; struct in_addr in; @@ -3352,7 +3398,8 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, if (eos_out) *eos_out = NULL; - if (router_get_networkstatus_v3_hashes(s, &ns_digests)) { + if (router_get_networkstatus_v3_hashes(s, &ns_digests) || + router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, s)<0) { log_warn(LD_DIR, "Unable to compute digest of network-status"); goto err; } @@ -3369,6 +3416,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, ns = tor_malloc_zero(sizeof(networkstatus_t)); memcpy(&ns->digests, &ns_digests, sizeof(ns_digests)); + memcpy(&ns->digest_sha3_as_signed, sha3_as_signed, sizeof(sha3_as_signed)); tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION); tor_assert(tok); @@ -4464,16 +4512,18 @@ static int router_get_hash_impl_helper(const char *s, size_t s_len, const char *start_str, const char *end_str, char end_c, + int log_severity, const char **start_out, const char **end_out) { const char *start, *end; start = tor_memstr(s, s_len, start_str); if (!start) { - log_warn(LD_DIR,"couldn't find start of hashed material \"%s\"",start_str); + log_fn(log_severity,LD_DIR, + "couldn't find start of hashed material \"%s\"",start_str); return -1; } if (start != s && *(start-1) != '\n') { - log_warn(LD_DIR, + log_fn(log_severity,LD_DIR, "first occurrence of \"%s\" is not at the start of a line", start_str); return -1; @@ -4481,12 +4531,14 @@ router_get_hash_impl_helper(const char *s, size_t s_len, end = tor_memstr(start+strlen(start_str), s_len - (start-s) - strlen(start_str), end_str); if (!end) { - log_warn(LD_DIR,"couldn't find end of hashed material \"%s\"",end_str); + log_fn(log_severity,LD_DIR, + "couldn't find end of hashed material \"%s\"",end_str); return -1; } end = memchr(end+strlen(end_str), end_c, s_len - (end-s) - strlen(end_str)); if (!end) { - log_warn(LD_DIR,"couldn't find EOL"); + log_fn(log_severity,LD_DIR, + "couldn't find EOL"); return -1; } ++end; @@ -4510,7 +4562,7 @@ router_get_hash_impl(const char *s, size_t s_len, char *digest, digest_algorithm_t alg) { const char *start=NULL, *end=NULL; - if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c, + if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,LOG_WARN, &start,&end)<0) return -1; @@ -4547,7 +4599,7 @@ router_get_hashes_impl(const char *s, size_t s_len, common_digests_t *digests, const char *end_str, char end_c) { const char *start=NULL, *end=NULL; - if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c, + if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,LOG_WARN, &start,&end)<0) return -1; @@ -5241,7 +5293,10 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, "v2 rendezvous service descriptor") < 0) goto err; /* Verify that descriptor ID belongs to public key and secret ID part. */ - crypto_pk_get_digest(result->pk, public_key_hash); + if (crypto_pk_get_digest(result->pk, public_key_hash) < 0) { + log_warn(LD_REND, "Unable to compute rend descriptor public key digest"); + goto err; + } rend_get_descriptor_id_bytes(test_desc_id, public_key_hash, secret_id_part); if (tor_memneq(desc_id_out, test_desc_id, DIGEST_LEN)) { diff --git a/src/or/routerparse.h b/src/or/routerparse.h index 648f29b0d3..088f773c5e 100644 --- a/src/or/routerparse.h +++ b/src/or/routerparse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,6 +16,11 @@ int router_get_router_hash(const char *s, size_t s_len, char *digest); int router_get_dir_hash(const char *s, char *digest); int router_get_networkstatus_v3_hashes(const char *s, common_digests_t *digests); +int router_get_networkstatus_v3_signed_boundaries(const char *s, + const char **start_out, + const char **end_out); +int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, + const char *s); int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest); #define DIROBJ_MAX_SIG_LEN 256 char *router_get_dirobj_signature(const char *digest, diff --git a/src/or/routerset.c b/src/or/routerset.c index d0df0a74e6..4906c6a51d 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/routerset.h b/src/or/routerset.h index 2e3b4b0fe0..a63677b471 100644 --- a/src/or/routerset.h +++ b/src/or/routerset.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 033e6d119c..fac545fba7 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 3dcfd2faca..e29c13de7e 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/shared_random.c b/src/or/shared_random.c index f798a51a9f..25ca0611cd 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -230,9 +230,7 @@ commit_decode(const char *encoded, sr_commit_t *commit) { int decoded_len = 0; size_t offset = 0; - /* XXX: Needs two extra bytes for the base64 decode calculation matches - * the binary length once decoded. #17868. */ - char b64_decoded[SR_COMMIT_LEN + 2]; + char b64_decoded[SR_COMMIT_LEN]; tor_assert(encoded); tor_assert(commit); @@ -284,9 +282,7 @@ STATIC int reveal_decode(const char *encoded, sr_commit_t *commit) { int decoded_len = 0; - /* XXX: Needs two extra bytes for the base64 decode calculation matches - * the binary length once decoded. #17868. */ - char b64_decoded[SR_REVEAL_LEN + 2]; + char b64_decoded[SR_REVEAL_LEN]; tor_assert(encoded); tor_assert(commit); diff --git a/src/or/shared_random.h b/src/or/shared_random.h index dbb8effeaa..1f027c70e0 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_SHARED_RANDOM_H @@ -36,17 +36,14 @@ /* Length of base64 encoded commit NOT including the NUL terminated byte. * Formula is taken from base64_encode_size. This adds up to 56 bytes. */ -#define SR_COMMIT_BASE64_LEN \ - (((SR_COMMIT_LEN - 1) / 3) * 4 + 4) +#define SR_COMMIT_BASE64_LEN (BASE64_LEN(SR_COMMIT_LEN)) /* Length of base64 encoded reveal NOT including the NUL terminated byte. * Formula is taken from base64_encode_size. This adds up to 56 bytes. */ -#define SR_REVEAL_BASE64_LEN \ - (((SR_REVEAL_LEN - 1) / 3) * 4 + 4) +#define SR_REVEAL_BASE64_LEN (BASE64_LEN(SR_REVEAL_LEN)) /* Length of base64 encoded shared random value. It's 32 bytes long so 44 * bytes from the base64_encode_size formula. That includes the '=' * character at the end. */ -#define SR_SRV_VALUE_BASE64_LEN \ - (((DIGEST256_LEN - 1) / 3) * 4 + 4) +#define SR_SRV_VALUE_BASE64_LEN (BASE64_LEN(DIGEST256_LEN)) /* Assert if commit valid flag is not set. */ #define ASSERT_COMMIT_VALID(c) tor_assert((c)->valid) diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 87db9031ee..89d2e8d7f6 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 43a7f1d284..3526ad47d3 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_SHARED_RANDOM_STATE_H diff --git a/src/or/statefile.c b/src/or/statefile.c index a95ba8533c..d0606b3012 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/statefile.h b/src/or/statefile.h index b13743481d..10c09324bc 100644 --- a/src/or/statefile.h +++ b/src/or/statefile.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_STATEFILE_H diff --git a/src/or/status.c b/src/or/status.c index fce6a10157..f7be41e412 100644 --- a/src/or/status.c +++ b/src/or/status.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/status.h b/src/or/status.h index b97e835037..c1a0033ce0 100644 --- a/src/or/status.h +++ b/src/or/status.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_STATUS_H diff --git a/src/or/tor_main.c b/src/or/tor_main.c index d67eda2ac9..a3a8838602 100644 --- a/src/or/tor_main.c +++ b/src/or/tor_main.c @@ -1,6 +1,6 @@ /* Copyright 2001-2004 Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ extern const char tor_git_revision[]; diff --git a/src/or/torcert.c b/src/or/torcert.c index c58f3da2d3..658e620ca5 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -302,6 +302,10 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, time_t expires, uint8_t **cert) { + // It is later than 1985, since otherwise there would be no C89 + // compilers. (Try to diagnose #22466.) + tor_assert_nonfatal(expires >= 15 * 365 * 86400); + uint8_t *res; rsa_ed_crosscert_t *cc = rsa_ed_crosscert_new(); diff --git a/src/or/torcert.h b/src/or/torcert.h index 090f6b5811..51f7665f1e 100644 --- a/src/or/torcert.h +++ b/src/or/torcert.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TORCERT_H_INCLUDED diff --git a/src/or/transports.c b/src/or/transports.c index 535393b1a1..31849a8d15 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016, The Tor Project, Inc. */ +/* Copyright (c) 2011-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/transports.h b/src/or/transports.h index 7de90dcbec..44a9626e50 100644 --- a/src/or/transports.h +++ b/src/or/transports.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/rust/.cargo/config.in b/src/rust/.cargo/config.in new file mode 100644 index 0000000000..414b253a57 --- /dev/null +++ b/src/rust/.cargo/config.in @@ -0,0 +1,8 @@ +[source] + +@RUST_DL@ [source.crates-io] +@RUST_DL@ registry = 'https://github.com/rust-lang/crates.io-index' +@RUST_DL@ replace-with = 'vendored-sources' + +@RUST_DL@ [source.vendored-sources] +@RUST_DL@ directory = '@RUST_DEPENDENCIES@' diff --git a/src/rust/.rustfmt.toml b/src/rust/.rustfmt.toml new file mode 100644 index 0000000000..f25bd51883 --- /dev/null +++ b/src/rust/.rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 80 +comment_width = 80 diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock new file mode 100644 index 0000000000..4ac9606ce8 --- /dev/null +++ b/src/rust/Cargo.lock @@ -0,0 +1,14 @@ +[root] +name = "tor_util" +version = "0.0.1" +dependencies = [ + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml new file mode 100644 index 0000000000..fc4377e8b4 --- /dev/null +++ b/src/rust/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +members = ["tor_util"] + +[profile.release] +debug = true +panic = "abort" + diff --git a/src/rust/include.am b/src/rust/include.am new file mode 100644 index 0000000000..20afc6c4db --- /dev/null +++ b/src/rust/include.am @@ -0,0 +1,6 @@ +include src/rust/tor_util/include.am + +EXTRA_DIST +=\ + src/rust/Cargo.toml \ + src/rust/Cargo.lock \ + src/rust/.cargo/config.in diff --git a/src/rust/tor_util/Cargo.toml b/src/rust/tor_util/Cargo.toml new file mode 100644 index 0000000000..f175fbdfb0 --- /dev/null +++ b/src/rust/tor_util/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["The Tor Project"] +name = "tor_util" +version = "0.0.1" + +[lib] +name = "tor_util" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + +[dependencies] +libc = "*" + diff --git a/src/rust/tor_util/ffi.rs b/src/rust/tor_util/ffi.rs new file mode 100644 index 0000000000..af4bfc41af --- /dev/null +++ b/src/rust/tor_util/ffi.rs @@ -0,0 +1,56 @@ +//! FFI functions, only to be called from C. +//! +//! Equivalent C versions of these live in `src/common/compat_rust.c` + +use std::mem::forget; +use std::ffi::CString; + +use libc; +use rust_string::RustString; + +/// Free the passed `RustString` (`rust_str_t` in C), to be used in place of +/// `tor_free`(). +/// +/// # Examples +/// ```c +/// rust_str_t r_s = rust_welcome_string(); +/// rust_str_free(r_s); +/// ``` +#[no_mangle] +#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] +pub unsafe extern "C" fn rust_str_free(_str: RustString) { + // Empty body: Just drop _str and we're done (Drop takes care of it). +} + +/// Lends an immutable, NUL-terminated C String. +/// +/// # Examples +/// ```c +/// rust_str_t r_s = rust_welcome_string(); +/// const char *s = rust_str_get(r_s); +/// printf("%s", s); +/// rust_str_free(r_s); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn rust_str_get(str: RustString) -> *const libc::c_char { + let res = str.as_ptr(); + forget(str); + res +} + +/// Returns a short string to announce Rust support during startup. +/// +/// # Examples +/// ```c +/// rust_str_t r_s = rust_welcome_string(); +/// const char *s = rust_str_get(r_s); +/// printf("%s", s); +/// rust_str_free(r_s); +/// ``` +#[no_mangle] +pub extern "C" fn rust_welcome_string() -> RustString { + let s = CString::new("Tor is running with Rust integration. Please report \ + any bugs you encouter.") + .unwrap(); + RustString::from(s) +} diff --git a/src/rust/tor_util/include.am b/src/rust/tor_util/include.am new file mode 100644 index 0000000000..f0cd63920c --- /dev/null +++ b/src/rust/tor_util/include.am @@ -0,0 +1,13 @@ +EXTRA_DIST +=\ + src/rust/tor_util/Cargo.toml \ + src/rust/tor_util/lib.rs \ + src/rust/tor_util/ffi.rs \ + src/rust/tor_util/rust_string.rs + +src/rust/target/release/libtor_util.a: FORCE + ( cd "$(abs_top_srcdir)/src/rust/tor_util" ; \ + CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \ + CARGO_HOME="$(abs_top_builddir)/src/rust" \ + $(CARGO) build --release --quiet $(CARGO_ONLINE) ) + +FORCE: diff --git a/src/rust/tor_util/lib.rs b/src/rust/tor_util/lib.rs new file mode 100644 index 0000000000..79d583d1ae --- /dev/null +++ b/src/rust/tor_util/lib.rs @@ -0,0 +1,13 @@ +//! C <-> Rust compatibility helpers and types. +//! +//! Generically useful, small scale helpers should go here. This goes for both +//! the C side (in the form of the ffi module) as well as the Rust side +//! (individual modules per functionality). The corresponding C stuff lives in +//! `src/common/compat_rust.{c,h}`. + +extern crate libc; + +mod rust_string; +pub mod ffi; + +pub use rust_string::*; diff --git a/src/rust/tor_util/rust_string.rs b/src/rust/tor_util/rust_string.rs new file mode 100644 index 0000000000..46ec3fd7a8 --- /dev/null +++ b/src/rust/tor_util/rust_string.rs @@ -0,0 +1,101 @@ +use std::ffi::CString; +use std::mem::forget; +use libc; + +/// Compatibility wrapper for strings allocated in Rust and passed to C. +/// +/// Rust doesn't ensure the safety of freeing memory across an FFI boundary, so +/// we need to take special care to ensure we're not accidentally calling +/// `tor_free`() on any string allocated in Rust. To more easily differentiate +/// between strings that possibly (if Rust support is enabled) were allocated +/// in Rust, C has the `rust_str_t` helper type. The equivalent on the Rust +/// side is `RustString`. +/// +/// Note: This type must not be used for strings allocated in C. +#[repr(C)] +#[derive(Debug)] +pub struct RustString(*mut libc::c_char); + +impl RustString { + /// Returns a pointer to the underlying NUL-terminated byte array. + /// + /// Note that this function is not typically useful for Rust callers, + /// except in a direct FFI context. + /// + /// # Examples + /// ``` + /// # use tor_util::RustString; + /// use std::ffi::CString; + /// + /// let r = RustString::from(CString::new("asdf").unwrap()); + /// let c_str = r.as_ptr(); + /// assert_eq!(b'a', unsafe { *c_str as u8}); + /// ``` + pub fn as_ptr(&self) -> *const libc::c_char { + self.0 as *const libc::c_char + } +} + +impl From<CString> for RustString { + /// Constructs a new `RustString` + /// + /// # Examples + /// ``` + /// # use tor_util::RustString; + /// use std::ffi::CString; + /// + /// let r = RustString::from(CString::new("asdf").unwrap()); + /// ``` + fn from(str: CString) -> RustString { + RustString(str.into_raw()) + } +} + +impl Into<CString> for RustString { + /// Reconstructs a `CString` from this `RustString`. + /// + /// Useful to take ownership back from a `RustString` that was given to C + /// code. + /// + /// # Examples + /// ``` + /// # use tor_util::RustString; + /// use std::ffi::CString; + /// + /// let cs = CString::new("asdf").unwrap(); + /// let r = RustString::from(cs.clone()); + /// let cs2 = r.into(); + /// assert_eq!(cs, cs2); + /// ``` + fn into(self) -> CString { + // Calling from_raw is always OK here: We only construct self using + // valid CStrings and don't expose anything that could mutate it + let ret = unsafe { CString::from_raw(self.0) }; + forget(self); + ret + } +} + +impl Drop for RustString { + fn drop(&mut self) { + // Don't use into() here, because we would need to move out of + // self. Same safety consideration. Immediately drop the created + // CString, which takes care of freeing the wrapped string. + unsafe { CString::from_raw(self.0) }; + } +} + +#[cfg(test)] +mod test { + use std::mem; + use super::*; + + use libc; + + /// Ensures we're not adding overhead by using RustString. + #[test] + fn size_of() { + assert_eq!(mem::size_of::<*mut libc::c_char>(), + mem::size_of::<RustString>()) + } +} diff --git a/src/rust/tor_util/tests/rust_string.rs b/src/rust/tor_util/tests/rust_string.rs new file mode 100644 index 0000000000..1ff605a43c --- /dev/null +++ b/src/rust/tor_util/tests/rust_string.rs @@ -0,0 +1,37 @@ +extern crate tor_util; +extern crate libc; + +use std::ffi::CString; +use tor_util::RustString; + +#[test] +fn rust_string_conversions_preserve_c_string() { + let s = CString::new("asdf foo").unwrap(); + let r = RustString::from(s.clone()); + let r2 = RustString::from(s.clone()); + let c = r2.as_ptr(); + assert_eq!(unsafe { libc::strlen(c) }, 8); + let c_str = r.into(); + assert_eq!(s, c_str); +} + +#[test] +fn empty_string() { + let s = CString::new("").unwrap(); + let r = RustString::from(s.clone()); + let c = r.as_ptr(); + assert_eq!(unsafe { libc::strlen(c) }, 0); + let c_str = r.into(); + assert_eq!(s, c_str); +} + +#[test] +fn c_string_with_unicode() { + // The euro sign is three bytes + let s = CString::new("asd€asd").unwrap(); + let r = RustString::from(s.clone()); + let c = r.as_ptr(); + assert_eq!(unsafe { libc::strlen(c) }, 9); + let c_str = r.into(); + assert_eq!(s, c_str); +} diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 0ba56d7036..605f1a92c3 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -12,11 +12,12 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ crypt32.lib gdi32.lib user32.lib TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_channeltls.obj \ - test_containers.obj \ + test_consdiff.obj test_containers.obj \ test_controller_events.obj test_crypto.obj test_data.obj test_dir.obj \ test_checkdir.obj test_microdesc.obj test_pt.obj test_util.obj \ test_config.obj test_connection.obj \ test_cell_formats.obj test_relay.obj test_replay.obj \ + test_channelpadding.obj \ test_scheduler.obj test_introduce.obj test_hs.obj tinytest.obj tinytest.obj: ..\ext\tinytest.c diff --git a/src/test/bench.c b/src/test/bench.c index 99bc686f30..a44dc94a61 100644 --- a/src/test/bench.c +++ b/src/test/bench.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ extern const char tor_git_revision[]; @@ -28,6 +28,7 @@ const char tor_git_revision[] = ""; #include "crypto_curve25519.h" #include "onion_ntor.h" #include "crypto_ed25519.h" +#include "consdiff.h" #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID) static uint64_t nanostart; @@ -673,6 +674,28 @@ main(int argc, const char **argv) or_options_t *options; tor_threads_init(); + tor_compress_init(); + + if (argc == 4 && !strcmp(argv[1], "diff")) { + init_logging(1); + const int N = 200; + char *f1 = read_file_to_str(argv[2], RFTS_BIN, NULL); + char *f2 = read_file_to_str(argv[3], RFTS_BIN, NULL); + if (! f1 || ! f2) { + perror("X"); + return 1; + } + for (i = 0; i < N; ++i) { + char *diff = consensus_diff_generate(f1, f2); + tor_free(diff); + } + char *diff = consensus_diff_generate(f1, f2); + printf("%s", diff); + tor_free(f1); + tor_free(f2); + tor_free(diff); + return 0; + } for (i = 1; i < argc; ++i) { if (!strcmp(argv[i], "--list")) { diff --git a/src/test/bt_test.py b/src/test/bt_test.py index 30591453b9..4cb3326042 100755 --- a/src/test/bt_test.py +++ b/src/test/bt_test.py @@ -1,4 +1,4 @@ -# Copyright 2013-2015, The Tor Project, Inc +# Copyright 2013-2017, The Tor Project, Inc # See LICENSE for licensing information """ diff --git a/src/test/ed25519_exts_ref.py b/src/test/ed25519_exts_ref.py index d5a3a79910..af5010415e 100644 --- a/src/test/ed25519_exts_ref.py +++ b/src/test/ed25519_exts_ref.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# Copyright 2014-2015, The Tor Project, Inc +# Copyright 2014-2017, The Tor Project, Inc # See LICENSE for licensing information """ diff --git a/src/test/fakechans.h b/src/test/fakechans.h index fa0e37dbe6..c0de430e3d 100644 --- a/src/test/fakechans.h +++ b/src/test/fakechans.h @@ -1,4 +1,4 @@ - /* Copyright (c) 2014-2016, The Tor Project, Inc. */ + /* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_FAKECHANS_H diff --git a/src/test/fuzz/dict/http b/src/test/fuzz/dict/http index 1a7b61e8d4..3b0531579d 100644 --- a/src/test/fuzz/dict/http +++ b/src/test/fuzz/dict/http @@ -4,7 +4,7 @@ # # Extracted from directory_handle_command() in the tor source code # -# Copyright (c) 2016, The Tor Project, Inc. +# Copyright (c) 2016-2017, The Tor Project, Inc. # See LICENSE for licensing information # # Usage: diff --git a/src/test/fuzz/fuzz_consensus.c b/src/test/fuzz/fuzz_consensus.c index f5d22f69ae..6610ade7ad 100644 --- a/src/test/fuzz/fuzz_consensus.c +++ b/src/test/fuzz/fuzz_consensus.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTERPARSE_PRIVATE #include "or.h" diff --git a/src/test/fuzz/fuzz_descriptor.c b/src/test/fuzz/fuzz_descriptor.c index d19386d77f..1a50beae17 100644 --- a/src/test/fuzz/fuzz_descriptor.c +++ b/src/test/fuzz/fuzz_descriptor.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTERPARSE_PRIVATE #include "or.h" diff --git a/src/test/fuzz/fuzz_diff.c b/src/test/fuzz/fuzz_diff.c new file mode 100644 index 0000000000..642380b512 --- /dev/null +++ b/src/test/fuzz/fuzz_diff.c @@ -0,0 +1,69 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CONSDIFF_PRIVATE + +#include "orconfig.h" +#include "or.h" +#include "consdiff.h" + +#include "fuzzing.h" + +static int +mock_consensus_compute_digest_(const char *c, consensus_digest_t *d) +{ + (void)c; + memset(d->sha3_256, 3, sizeof(d->sha3_256)); + return 0; +} + +int +fuzz_init(void) +{ + MOCK(consensus_compute_digest, mock_consensus_compute_digest_); + MOCK(consensus_compute_digest_as_signed, mock_consensus_compute_digest_); + return 0; +} + +int +fuzz_cleanup(void) +{ + UNMOCK(consensus_compute_digest); + UNMOCK(consensus_compute_digest_as_signed); + return 0; +} + +int +fuzz_main(const uint8_t *stdin_buf, size_t data_size) +{ +#define SEP "=====\n" +#define SEPLEN strlen(SEP) + const uint8_t *separator = tor_memmem(stdin_buf, data_size, SEP, SEPLEN); + if (! separator) + return 0; + size_t c1_len = separator - stdin_buf; + char *c1 = tor_memdup_nulterm(stdin_buf, c1_len); + size_t c2_len = data_size - c1_len - SEPLEN; + char *c2 = tor_memdup_nulterm(separator + SEPLEN, c2_len); + + char *c3 = consensus_diff_generate(c1, c2); + + if (c3) { + char *c4 = consensus_diff_apply(c1, c3); + tor_assert(c4); + if (strcmp(c2, c4)) { + printf("%s\n", escaped(c1)); + printf("%s\n", escaped(c2)); + printf("%s\n", escaped(c3)); + printf("%s\n", escaped(c4)); + } + tor_assert(! strcmp(c2, c4)); + tor_free(c3); + tor_free(c4); + } + tor_free(c1); + tor_free(c2); + + return 0; +} + diff --git a/src/test/fuzz/fuzz_diff_apply.c b/src/test/fuzz/fuzz_diff_apply.c new file mode 100644 index 0000000000..8d7bf751bf --- /dev/null +++ b/src/test/fuzz/fuzz_diff_apply.c @@ -0,0 +1,65 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CONSDIFF_PRIVATE + +#include "orconfig.h" +#include "or.h" +#include "consdiff.h" + +#include "fuzzing.h" + +static int +mock_consensus_compute_digest_(const char *c, consensus_digest_t *d) +{ + (void)c; + memset(d->sha3_256, 3, sizeof(d->sha3_256)); + return 0; +} + +static int +mock_consensus_digest_eq_(const uint8_t *a, const uint8_t *b) +{ + (void)a; + (void)b; + return 1; +} + +int +fuzz_init(void) +{ + MOCK(consensus_compute_digest, mock_consensus_compute_digest_); + MOCK(consensus_digest_eq, mock_consensus_digest_eq_); + return 0; +} + +int +fuzz_cleanup(void) +{ + UNMOCK(consensus_compute_digest); + UNMOCK(consensus_digest_eq); + return 0; +} + +int +fuzz_main(const uint8_t *stdin_buf, size_t data_size) +{ +#define SEP "=====\n" +#define SEPLEN strlen(SEP) + const uint8_t *separator = tor_memmem(stdin_buf, data_size, SEP, SEPLEN); + if (! separator) + return 0; + size_t c1_len = separator - stdin_buf; + char *c1 = tor_memdup_nulterm(stdin_buf, c1_len); + size_t c2_len = data_size - c1_len - SEPLEN; + char *c2 = tor_memdup_nulterm(separator + SEPLEN, c2_len); + + char *c3 = consensus_diff_apply(c1, c2); + + tor_free(c1); + tor_free(c2); + tor_free(c3); + + return 0; +} + diff --git a/src/test/fuzz/fuzz_extrainfo.c b/src/test/fuzz/fuzz_extrainfo.c index 6251e606d0..2a3de7ecf7 100644 --- a/src/test/fuzz/fuzz_extrainfo.c +++ b/src/test/fuzz/fuzz_extrainfo.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTERPARSE_PRIVATE #include "or.h" diff --git a/src/test/fuzz/fuzz_hsdescv2.c b/src/test/fuzz/fuzz_hsdescv2.c index 53b7cbe2f7..19db265716 100644 --- a/src/test/fuzz/fuzz_hsdescv2.c +++ b/src/test/fuzz/fuzz_hsdescv2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTERPARSE_PRIVATE #include "or.h" diff --git a/src/test/fuzz/fuzz_http.c b/src/test/fuzz/fuzz_http.c index 01c3815f18..2ffeb60244 100644 --- a/src/test/fuzz/fuzz_http.c +++ b/src/test/fuzz/fuzz_http.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -18,10 +18,10 @@ static void mock_connection_write_to_buf_impl_(const char *string, size_t len, - connection_t *conn, int zlib) + connection_t *conn, int compressed) { log_debug(LD_GENERAL, "%sResponse:\n%u\nConnection: %p\n%s\n", - zlib ? "Compressed " : "", (unsigned)len, conn, string); + compressed ? "Compressed " : "", (unsigned)len, conn, string); } static int diff --git a/src/test/fuzz/fuzz_iptsv2.c b/src/test/fuzz/fuzz_iptsv2.c index 341d4880bd..4abde0c16d 100644 --- a/src/test/fuzz/fuzz_iptsv2.c +++ b/src/test/fuzz/fuzz_iptsv2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTERPARSE_PRIVATE #include "or.h" diff --git a/src/test/fuzz/fuzz_microdesc.c b/src/test/fuzz/fuzz_microdesc.c index bb89546191..396115026e 100644 --- a/src/test/fuzz/fuzz_microdesc.c +++ b/src/test/fuzz/fuzz_microdesc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTERPARSE_PRIVATE #include "or.h" diff --git a/src/test/fuzz/fuzz_vrs.c b/src/test/fuzz/fuzz_vrs.c index 9301a9bcc8..baf0610a0b 100644 --- a/src/test/fuzz/fuzz_vrs.c +++ b/src/test/fuzz/fuzz_vrs.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTERPARSE_PRIVATE #define NETWORKSTATUS_PRIVATE diff --git a/src/test/fuzz/fuzzing.h b/src/test/fuzz/fuzzing.h index 4295743458..aecdbb4e52 100644 --- a/src/test/fuzz/fuzzing.h +++ b/src/test/fuzz/fuzzing.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef FUZZING_H #define FUZZING_H diff --git a/src/test/fuzz/fuzzing_common.c b/src/test/fuzz/fuzzing_common.c index e4920d3ee7..7aee92df63 100644 --- a/src/test/fuzz/fuzzing_common.c +++ b/src/test/fuzz/fuzzing_common.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CRYPTO_ED25519_PRIVATE #include "orconfig.h" @@ -96,6 +96,7 @@ static void global_init(void) { tor_threads_init(); + tor_compress_init(); { struct sipkey sipkey = { 1337, 7331 }; siphash_set_global_key(&sipkey); diff --git a/src/test/fuzz/include.am b/src/test/fuzz/include.am index 806710879b..2961dab56f 100644 --- a/src/test/fuzz/include.am +++ b/src/test/fuzz/include.am @@ -1,4 +1,5 @@ - +# This file was generated by fuzzing_include_am.py; do not hand-edit unless +# you enjoy having your changes erased. FUZZING_CPPFLAGS = \ $(src_test_AM_CPPFLAGS) $(TEST_CPPFLAGS) FUZZING_CFLAGS = \ @@ -17,7 +18,10 @@ FUZZING_LIBS = \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ @TOR_LIBEVENT_LIBS@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_SYSTEMD_LIBS@ + @TOR_SYSTEMD_LIBS@ \ + @TOR_LZMA_LIBS@ \ + @TOR_ZSTD_LIBS@ \ + $(rust_ldadd) oss-fuzz-prereqs: \ src/or/libtor-testing.a \ @@ -32,6 +36,16 @@ oss-fuzz-prereqs: \ noinst_HEADERS += \ src/test/fuzz/fuzzing.h +LIBFUZZER = -lFuzzer +LIBFUZZER_CPPFLAGS = $(FUZZING_CPPFLAGS) -DLLVM_FUZZ +LIBFUZZER_CFLAGS = $(FUZZING_CFLAGS) +LIBFUZZER_LDFLAG = $(FUZZING_LDFLAG) +LIBFUZZER_LIBS = $(FUZZING_LIBS) $(LIBFUZZER) -lstdc++ + +LIBOSS_FUZZ_CPPFLAGS = $(FUZZING_CPPFLAGS) -DLLVM_FUZZ +LIBOSS_FUZZ_CFLAGS = $(FUZZING_CFLAGS) + +# ===== AFL fuzzers src_test_fuzz_fuzz_consensus_SOURCES = \ src/test/fuzz/fuzzing_common.c \ src/test/fuzz/fuzz_consensus.c @@ -48,13 +62,29 @@ src_test_fuzz_fuzz_descriptor_CFLAGS = $(FUZZING_CFLAGS) src_test_fuzz_fuzz_descriptor_LDFLAGS = $(FUZZING_LDFLAG) src_test_fuzz_fuzz_descriptor_LDADD = $(FUZZING_LIBS) -src_test_fuzz_fuzz_http_SOURCES = \ +src_test_fuzz_fuzz_diff_SOURCES = \ src/test/fuzz/fuzzing_common.c \ - src/test/fuzz/fuzz_http.c -src_test_fuzz_fuzz_http_CPPFLAGS = $(FUZZING_CPPFLAGS) -src_test_fuzz_fuzz_http_CFLAGS = $(FUZZING_CFLAGS) -src_test_fuzz_fuzz_http_LDFLAGS = $(FUZZING_LDFLAG) -src_test_fuzz_fuzz_http_LDADD = $(FUZZING_LIBS) + src/test/fuzz/fuzz_diff.c +src_test_fuzz_fuzz_diff_CPPFLAGS = $(FUZZING_CPPFLAGS) +src_test_fuzz_fuzz_diff_CFLAGS = $(FUZZING_CFLAGS) +src_test_fuzz_fuzz_diff_LDFLAGS = $(FUZZING_LDFLAG) +src_test_fuzz_fuzz_diff_LDADD = $(FUZZING_LIBS) + +src_test_fuzz_fuzz_diff_apply_SOURCES = \ + src/test/fuzz/fuzzing_common.c \ + src/test/fuzz/fuzz_diff_apply.c +src_test_fuzz_fuzz_diff_apply_CPPFLAGS = $(FUZZING_CPPFLAGS) +src_test_fuzz_fuzz_diff_apply_CFLAGS = $(FUZZING_CFLAGS) +src_test_fuzz_fuzz_diff_apply_LDFLAGS = $(FUZZING_LDFLAG) +src_test_fuzz_fuzz_diff_apply_LDADD = $(FUZZING_LIBS) + +src_test_fuzz_fuzz_extrainfo_SOURCES = \ + src/test/fuzz/fuzzing_common.c \ + src/test/fuzz/fuzz_extrainfo.c +src_test_fuzz_fuzz_extrainfo_CPPFLAGS = $(FUZZING_CPPFLAGS) +src_test_fuzz_fuzz_extrainfo_CFLAGS = $(FUZZING_CFLAGS) +src_test_fuzz_fuzz_extrainfo_LDFLAGS = $(FUZZING_LDFLAG) +src_test_fuzz_fuzz_extrainfo_LDADD = $(FUZZING_LIBS) src_test_fuzz_fuzz_hsdescv2_SOURCES = \ src/test/fuzz/fuzzing_common.c \ @@ -64,6 +94,14 @@ src_test_fuzz_fuzz_hsdescv2_CFLAGS = $(FUZZING_CFLAGS) src_test_fuzz_fuzz_hsdescv2_LDFLAGS = $(FUZZING_LDFLAG) src_test_fuzz_fuzz_hsdescv2_LDADD = $(FUZZING_LIBS) +src_test_fuzz_fuzz_http_SOURCES = \ + src/test/fuzz/fuzzing_common.c \ + src/test/fuzz/fuzz_http.c +src_test_fuzz_fuzz_http_CPPFLAGS = $(FUZZING_CPPFLAGS) +src_test_fuzz_fuzz_http_CFLAGS = $(FUZZING_CFLAGS) +src_test_fuzz_fuzz_http_LDFLAGS = $(FUZZING_LDFLAG) +src_test_fuzz_fuzz_http_LDADD = $(FUZZING_LIBS) + src_test_fuzz_fuzz_iptsv2_SOURCES = \ src/test/fuzz/fuzzing_common.c \ src/test/fuzz/fuzz_iptsv2.c @@ -72,14 +110,6 @@ src_test_fuzz_fuzz_iptsv2_CFLAGS = $(FUZZING_CFLAGS) src_test_fuzz_fuzz_iptsv2_LDFLAGS = $(FUZZING_LDFLAG) src_test_fuzz_fuzz_iptsv2_LDADD = $(FUZZING_LIBS) -src_test_fuzz_fuzz_extrainfo_SOURCES = \ - src/test/fuzz/fuzzing_common.c \ - src/test/fuzz/fuzz_extrainfo.c -src_test_fuzz_fuzz_extrainfo_CPPFLAGS = $(FUZZING_CPPFLAGS) -src_test_fuzz_fuzz_extrainfo_CFLAGS = $(FUZZING_CFLAGS) -src_test_fuzz_fuzz_extrainfo_LDFLAGS = $(FUZZING_LDFLAG) -src_test_fuzz_fuzz_extrainfo_LDADD = $(FUZZING_LIBS) - src_test_fuzz_fuzz_microdesc_SOURCES = \ src/test/fuzz/fuzzing_common.c \ src/test/fuzz/fuzz_microdesc.c @@ -99,19 +129,16 @@ src_test_fuzz_fuzz_vrs_LDADD = $(FUZZING_LIBS) FUZZERS = \ src/test/fuzz/fuzz-consensus \ src/test/fuzz/fuzz-descriptor \ + src/test/fuzz/fuzz-diff \ + src/test/fuzz/fuzz-diff-apply \ src/test/fuzz/fuzz-extrainfo \ - src/test/fuzz/fuzz-http \ src/test/fuzz/fuzz-hsdescv2 \ + src/test/fuzz/fuzz-http \ src/test/fuzz/fuzz-iptsv2 \ src/test/fuzz/fuzz-microdesc \ src/test/fuzz/fuzz-vrs - -LIBFUZZER = /home/nickm/build/libfuzz/libFuzzer.a -LIBFUZZER_CPPFLAGS = $(FUZZING_CPPFLAGS) -DLLVM_FUZZ -LIBFUZZER_CFLAGS = $(FUZZING_CFLAGS) -LIBFUZZER_LDFLAG = $(FUZZING_LDFLAG) -LIBFUZZER_LIBS = $(FUZZING_LIBS) $(LIBFUZZER) -lstdc++ +# ===== libfuzzer if LIBFUZZER_ENABLED src_test_fuzz_lf_fuzz_consensus_SOURCES = \ @@ -128,6 +155,20 @@ src_test_fuzz_lf_fuzz_descriptor_CFLAGS = $(LIBFUZZER_CFLAGS) src_test_fuzz_lf_fuzz_descriptor_LDFLAGS = $(LIBFUZZER_LDFLAG) src_test_fuzz_lf_fuzz_descriptor_LDADD = $(LIBFUZZER_LIBS) +src_test_fuzz_lf_fuzz_diff_SOURCES = \ + $(src_test_fuzz_fuzz_diff_SOURCES) +src_test_fuzz_lf_fuzz_diff_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) +src_test_fuzz_lf_fuzz_diff_CFLAGS = $(LIBFUZZER_CFLAGS) +src_test_fuzz_lf_fuzz_diff_LDFLAGS = $(LIBFUZZER_LDFLAG) +src_test_fuzz_lf_fuzz_diff_LDADD = $(LIBFUZZER_LIBS) + +src_test_fuzz_lf_fuzz_diff_apply_SOURCES = \ + $(src_test_fuzz_fuzz_diff_apply_SOURCES) +src_test_fuzz_lf_fuzz_diff_apply_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) +src_test_fuzz_lf_fuzz_diff_apply_CFLAGS = $(LIBFUZZER_CFLAGS) +src_test_fuzz_lf_fuzz_diff_apply_LDFLAGS = $(LIBFUZZER_LDFLAG) +src_test_fuzz_lf_fuzz_diff_apply_LDADD = $(LIBFUZZER_LIBS) + src_test_fuzz_lf_fuzz_extrainfo_SOURCES = \ $(src_test_fuzz_fuzz_extrainfo_SOURCES) src_test_fuzz_lf_fuzz_extrainfo_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) @@ -135,13 +176,6 @@ src_test_fuzz_lf_fuzz_extrainfo_CFLAGS = $(LIBFUZZER_CFLAGS) src_test_fuzz_lf_fuzz_extrainfo_LDFLAGS = $(LIBFUZZER_LDFLAG) src_test_fuzz_lf_fuzz_extrainfo_LDADD = $(LIBFUZZER_LIBS) -src_test_fuzz_lf_fuzz_http_SOURCES = \ - $(src_test_fuzz_fuzz_http_SOURCES) -src_test_fuzz_lf_fuzz_http_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) -src_test_fuzz_lf_fuzz_http_CFLAGS = $(LIBFUZZER_CFLAGS) -src_test_fuzz_lf_fuzz_http_LDFLAGS = $(LIBFUZZER_LDFLAG) -src_test_fuzz_lf_fuzz_http_LDADD = $(LIBFUZZER_LIBS) - src_test_fuzz_lf_fuzz_hsdescv2_SOURCES = \ $(src_test_fuzz_fuzz_hsdescv2_SOURCES) src_test_fuzz_lf_fuzz_hsdescv2_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) @@ -149,6 +183,13 @@ src_test_fuzz_lf_fuzz_hsdescv2_CFLAGS = $(LIBFUZZER_CFLAGS) src_test_fuzz_lf_fuzz_hsdescv2_LDFLAGS = $(LIBFUZZER_LDFLAG) src_test_fuzz_lf_fuzz_hsdescv2_LDADD = $(LIBFUZZER_LIBS) +src_test_fuzz_lf_fuzz_http_SOURCES = \ + $(src_test_fuzz_fuzz_http_SOURCES) +src_test_fuzz_lf_fuzz_http_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) +src_test_fuzz_lf_fuzz_http_CFLAGS = $(LIBFUZZER_CFLAGS) +src_test_fuzz_lf_fuzz_http_LDFLAGS = $(LIBFUZZER_LDFLAG) +src_test_fuzz_lf_fuzz_http_LDADD = $(LIBFUZZER_LIBS) + src_test_fuzz_lf_fuzz_iptsv2_SOURCES = \ $(src_test_fuzz_fuzz_iptsv2_SOURCES) src_test_fuzz_lf_fuzz_iptsv2_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) @@ -173,9 +214,11 @@ src_test_fuzz_lf_fuzz_vrs_LDADD = $(LIBFUZZER_LIBS) LIBFUZZER_FUZZERS = \ src/test/fuzz/lf-fuzz-consensus \ src/test/fuzz/lf-fuzz-descriptor \ + src/test/fuzz/lf-fuzz-diff \ + src/test/fuzz/lf-fuzz-diff-apply \ src/test/fuzz/lf-fuzz-extrainfo \ - src/test/fuzz/lf-fuzz-http \ src/test/fuzz/lf-fuzz-hsdescv2 \ + src/test/fuzz/lf-fuzz-http \ src/test/fuzz/lf-fuzz-iptsv2 \ src/test/fuzz/lf-fuzz-microdesc \ src/test/fuzz/lf-fuzz-vrs @@ -184,10 +227,9 @@ else LIBFUZZER_FUZZERS = endif -if OSS_FUZZ_ENABLED -LIBOSS_FUZZ_CPPFLAGS = $(FUZZING_CPPFLAGS) -DLLVM_FUZZ -LIBOSS_FUZZ_CFLAGS = $(FUZZING_CFLAGS) +# ===== oss-fuzz +if OSS_FUZZ_ENABLED src_test_fuzz_liboss_fuzz_consensus_a_SOURCES = \ $(src_test_fuzz_fuzz_consensus_SOURCES) src_test_fuzz_liboss_fuzz_consensus_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) @@ -198,21 +240,31 @@ src_test_fuzz_liboss_fuzz_descriptor_a_SOURCES = \ src_test_fuzz_liboss_fuzz_descriptor_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) src_test_fuzz_liboss_fuzz_descriptor_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) +src_test_fuzz_liboss_fuzz_diff_a_SOURCES = \ + $(src_test_fuzz_fuzz_diff_SOURCES) +src_test_fuzz_liboss_fuzz_diff_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) +src_test_fuzz_liboss_fuzz_diff_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) + +src_test_fuzz_liboss_fuzz_diff_apply_a_SOURCES = \ + $(src_test_fuzz_fuzz_diff_apply_SOURCES) +src_test_fuzz_liboss_fuzz_diff_apply_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) +src_test_fuzz_liboss_fuzz_diff_apply_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) + src_test_fuzz_liboss_fuzz_extrainfo_a_SOURCES = \ $(src_test_fuzz_fuzz_extrainfo_SOURCES) src_test_fuzz_liboss_fuzz_extrainfo_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) src_test_fuzz_liboss_fuzz_extrainfo_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) -src_test_fuzz_liboss_fuzz_http_a_SOURCES = \ - $(src_test_fuzz_fuzz_http_SOURCES) -src_test_fuzz_liboss_fuzz_http_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) -src_test_fuzz_liboss_fuzz_http_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) - src_test_fuzz_liboss_fuzz_hsdescv2_a_SOURCES = \ $(src_test_fuzz_fuzz_hsdescv2_SOURCES) src_test_fuzz_liboss_fuzz_hsdescv2_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) src_test_fuzz_liboss_fuzz_hsdescv2_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) +src_test_fuzz_liboss_fuzz_http_a_SOURCES = \ + $(src_test_fuzz_fuzz_http_SOURCES) +src_test_fuzz_liboss_fuzz_http_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) +src_test_fuzz_liboss_fuzz_http_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) + src_test_fuzz_liboss_fuzz_iptsv2_a_SOURCES = \ $(src_test_fuzz_fuzz_iptsv2_SOURCES) src_test_fuzz_liboss_fuzz_iptsv2_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) @@ -231,12 +283,15 @@ src_test_fuzz_liboss_fuzz_vrs_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) OSS_FUZZ_FUZZERS = \ src/test/fuzz/liboss-fuzz-consensus.a \ src/test/fuzz/liboss-fuzz-descriptor.a \ + src/test/fuzz/liboss-fuzz-diff.a \ + src/test/fuzz/liboss-fuzz-diff-apply.a \ src/test/fuzz/liboss-fuzz-extrainfo.a \ - src/test/fuzz/liboss-fuzz-http.a \ src/test/fuzz/liboss-fuzz-hsdescv2.a \ + src/test/fuzz/liboss-fuzz-http.a \ src/test/fuzz/liboss-fuzz-iptsv2.a \ src/test/fuzz/liboss-fuzz-microdesc.a \ src/test/fuzz/liboss-fuzz-vrs.a + else OSS_FUZZ_FUZZERS = endif diff --git a/src/test/fuzz_static_testcases.sh b/src/test/fuzz_static_testcases.sh index bfe1677573..3cb45ad5e6 100755 --- a/src/test/fuzz_static_testcases.sh +++ b/src/test/fuzz_static_testcases.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Copyright (c) 2016, The Tor Project, Inc. +# Copyright (c) 2016-2017, The Tor Project, Inc. # See LICENSE for licensing information set -e diff --git a/src/test/hs_ntor_ref.py b/src/test/hs_ntor_ref.py new file mode 100644 index 0000000000..2ed9324e1f --- /dev/null +++ b/src/test/hs_ntor_ref.py @@ -0,0 +1,425 @@ +#!/usr/bin/python +# Copyright 2017, The Tor Project, Inc +# See LICENSE for licensing information + +""" +hs_ntor_ref.py + +This module is a reference implementation of the modified ntor protocol +proposed for Tor hidden services in proposal 224 (Next Generation Hidden +Services) in section [NTOR-WITH-EXTRA-DATA]. + +The modified ntor protocol is a single-round protocol, with three steps in total: + + 1: Client generates keys and sends them to service via INTRODUCE cell + + 2: Service computes key material based on client's keys, and sends its own + keys to client via RENDEZVOUS cell + + 3: Client computes key material as well. + +It's meant to be used to validate Tor's HS ntor implementation by conducting +various integration tests. Specifically it conducts the following three tests: + +- Tests our Python implementation by running the whole protocol in Python and + making sure that results are consistent. + +- Tests little-t-tor ntor implementation. We use this Python code to instrument + little-t-tor and carry out the handshake by using little-t-tor code. The + small C wrapper at src/test/test-hs-ntor-cl is used for this Python module to + interface with little-t-tor. + +- Cross-tests Python and little-t-tor implementation by running half of the + protocol in Python code and the other in little-t-tor. This is actually two + tests so that all parts of the protocol are run both by little-t-tor and + Python. + +It requires the curve25519 python module from the curve25519-donna package. + +The whole logic and concept for this test suite was taken from ntor_ref.py. + + *** DO NOT USE THIS IN PRODUCTION. *** +""" + +import struct +import os, sys +import binascii +import subprocess + +try: + import curve25519 + curve25519mod = curve25519.keys +except ImportError: + curve25519 = None + import slownacl_curve25519 + curve25519mod = slownacl_curve25519 + +import hashlib +try: + import sha3 +except ImportError: + # In python 3.6, the sha3 functions are in hashlib whether we + # import sha3 or not. + sha3 = None + +try: + # Pull the sha3 functions in. + from hashlib import sha3_256, shake_256 + shake_squeeze = shake_256.digest +except ImportError: + if hasattr(sha3, "SHA3256"): + # If this happens, then we have the old "sha3" module which + # hashlib and pysha3 superseded. + sha3_256 = sha3.SHA3256 + shake_256 = sha3.SHAKE256 + shake_squeeze = shake_256.squeeze + else: + # error code 77 tells automake to skip this test + sys.exit(77) + +# Import Nick's ntor reference implementation in Python +# We are gonna use a few of its utilities. +from ntor_ref import hash_nil +from ntor_ref import PrivateKey + +# String constants used in this protocol +PROTOID = b"tor-hs-ntor-curve25519-sha3-256-1" +T_HSENC = PROTOID + b":hs_key_extract" +T_HSVERIFY = PROTOID + b":hs_verify" +T_HSMAC = PROTOID + b":hs_mac" +M_HSEXPAND = PROTOID + b":hs_key_expand" + +INTRO_SECRET_LEN = 161 +REND_SECRET_LEN = 225 +AUTH_INPUT_LEN = 199 + +# Implements MAC(k,m) = H(htonll(len(k)) | k | m) +def mac(k,m): + def htonll(num): + return struct.pack('!q', num) + + s = sha3_256() + s.update(htonll(len(k))) + s.update(k) + s.update(m) + return s.digest() + +###################################################################### + +# Functions that implement the modified HS ntor protocol + +"""As client compute key material for INTRODUCE cell as follows: + + intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID + info = m_hsexpand | subcredential + hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) + ENC_KEY = hs_keys[0:S_KEY_LEN] + MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] +""" +def intro2_ntor_client(intro_auth_pubkey_str, intro_enc_pubkey, + client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential): + + dh_result = client_ephemeral_enc_privkey.get_shared_key(intro_enc_pubkey, hash_nil) + secret = dh_result + intro_auth_pubkey_str + client_ephemeral_enc_pubkey.serialize() + intro_enc_pubkey.serialize() + PROTOID + assert(len(secret) == INTRO_SECRET_LEN) + info = M_HSEXPAND + subcredential + + kdf = shake_256() + kdf.update(secret + T_HSENC + info) + key_material = shake_squeeze(kdf, 64*8) + + enc_key = key_material[0:32] + mac_key = key_material[32:64] + + return enc_key, mac_key + +"""Wrapper over intro2_ntor_client()""" +def client_part1(intro_auth_pubkey_str, intro_enc_pubkey, + client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential): + enc_key, mac_key = intro2_ntor_client(intro_auth_pubkey_str, intro_enc_pubkey, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential) + assert(enc_key) + assert(mac_key) + + return enc_key, mac_key + +"""As service compute key material for INTRODUCE cell as follows: + + intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID + info = m_hsexpand | subcredential + hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) + HS_DEC_KEY = hs_keys[0:S_KEY_LEN] + HS_MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] +""" +def intro2_ntor_service(intro_auth_pubkey_str, client_enc_pubkey, service_enc_privkey, service_enc_pubkey, subcredential): + dh_result = service_enc_privkey.get_shared_key(client_enc_pubkey, hash_nil) + secret = dh_result + intro_auth_pubkey_str + client_enc_pubkey.serialize() + service_enc_pubkey.serialize() + PROTOID + assert(len(secret) == INTRO_SECRET_LEN) + info = M_HSEXPAND + subcredential + + kdf = shake_256() + kdf.update(secret + T_HSENC + info) + key_material = shake_squeeze(kdf, 64*8) + + enc_key = key_material[0:32] + mac_key = key_material[32:64] + + return enc_key, mac_key + +"""As service compute key material for INTRODUCE and REDNEZVOUS cells. + + Use intro2_ntor_service() to calculate the INTRODUCE key material, and use + the following computations to do the RENDEZVOUS ones: + + rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID + NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc) + verify = MAC(rend_secret_hs_input, t_hsverify) + auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" + AUTH_INPUT_MAC = MAC(auth_input, t_hsmac) +""" +def service_part1(intro_auth_pubkey_str, client_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential): + intro_enc_key, intro_mac_key = intro2_ntor_service(intro_auth_pubkey_str, client_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential) + assert(intro_enc_key) + assert(intro_mac_key) + + service_ephemeral_privkey = PrivateKey() + service_ephemeral_pubkey = service_ephemeral_privkey.get_public() + + dh_result1 = service_ephemeral_privkey.get_shared_key(client_enc_pubkey, hash_nil) + dh_result2 = intro_enc_privkey.get_shared_key(client_enc_pubkey, hash_nil) + rend_secret_hs_input = dh_result1 + dh_result2 + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + client_enc_pubkey.serialize() + service_ephemeral_pubkey.serialize() + PROTOID + assert(len(rend_secret_hs_input) == REND_SECRET_LEN) + + ntor_key_seed = mac(rend_secret_hs_input, T_HSENC) + verify = mac(rend_secret_hs_input, T_HSVERIFY) + auth_input = verify + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + service_ephemeral_pubkey.serialize() + client_enc_pubkey.serialize() + PROTOID + b"Server" + assert(len(auth_input) == AUTH_INPUT_LEN) + auth_input_mac = mac(auth_input, T_HSMAC) + + assert(ntor_key_seed) + assert(auth_input_mac) + assert(service_ephemeral_pubkey) + + return intro_enc_key, intro_mac_key, ntor_key_seed, auth_input_mac, service_ephemeral_pubkey + +"""As client compute key material for rendezvous cells as follows: + + rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID + NTOR_KEY_SEED = MAC(ntor_secret_input, t_hsenc) + verify = MAC(ntor_secret_input, t_hsverify) + auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" + AUTH_INPUT_MAC = MAC(auth_input, t_hsmac) +""" +def client_part2(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, + intro_enc_pubkey, service_ephemeral_rend_pubkey): + dh_result1 = client_ephemeral_enc_privkey.get_shared_key(service_ephemeral_rend_pubkey, hash_nil) + dh_result2 = client_ephemeral_enc_privkey.get_shared_key(intro_enc_pubkey, hash_nil) + rend_secret_hs_input = dh_result1 + dh_result2 + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + client_ephemeral_enc_pubkey.serialize() + service_ephemeral_rend_pubkey.serialize() + PROTOID + assert(len(rend_secret_hs_input) == REND_SECRET_LEN) + + ntor_key_seed = mac(rend_secret_hs_input, T_HSENC) + verify = mac(rend_secret_hs_input, T_HSVERIFY) + auth_input = verify + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + service_ephemeral_rend_pubkey.serialize() + client_ephemeral_enc_pubkey.serialize() + PROTOID + b"Server" + assert(len(auth_input) == AUTH_INPUT_LEN) + auth_input_mac = mac(auth_input, T_HSMAC) + + assert(ntor_key_seed) + assert(auth_input_mac) + + return ntor_key_seed, auth_input_mac + +################################################################################# + +""" +Utilities for communicating with the little-t-tor ntor wrapper to conduct the +integration tests +""" + +PROG = b"./src/test/test-hs-ntor-cl" +enhex=lambda s: binascii.b2a_hex(s) +dehex=lambda s: binascii.a2b_hex(s.strip()) + +def tor_client1(intro_auth_pubkey_str, intro_enc_pubkey, + client_ephemeral_enc_privkey, subcredential): + p = subprocess.Popen([PROG, "client1", + enhex(intro_auth_pubkey_str), + enhex(intro_enc_pubkey.serialize()), + enhex(client_ephemeral_enc_privkey.serialize()), + enhex(subcredential)], + stdout=subprocess.PIPE) + return map(dehex, p.stdout.readlines()) + +def tor_server1(intro_auth_pubkey_str, intro_enc_privkey, + client_ephemeral_enc_pubkey, subcredential): + p = subprocess.Popen([PROG, "server1", + enhex(intro_auth_pubkey_str), + enhex(intro_enc_privkey.serialize()), + enhex(client_ephemeral_enc_pubkey.serialize()), + enhex(subcredential)], + stdout=subprocess.PIPE) + return map(dehex, p.stdout.readlines()) + +def tor_client2(intro_auth_pubkey_str, client_ephemeral_enc_privkey, + intro_enc_pubkey, service_ephemeral_rend_pubkey, subcredential): + p = subprocess.Popen([PROG, "client2", + enhex(intro_auth_pubkey_str), + enhex(client_ephemeral_enc_privkey.serialize()), + enhex(intro_enc_pubkey.serialize()), + enhex(service_ephemeral_rend_pubkey.serialize()), + enhex(subcredential)], + stdout=subprocess.PIPE) + return map(dehex, p.stdout.readlines()) + +################################################################################## + +# Perform a pure python ntor test +def do_pure_python_ntor_test(): + # Initialize all needed key material + client_ephemeral_enc_privkey = PrivateKey() + client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public() + intro_enc_privkey = PrivateKey() + intro_enc_pubkey = intro_enc_privkey.get_public() + intro_auth_pubkey_str = os.urandom(32) + subcredential = os.urandom(32) + + client_enc_key, client_mac_key = client_part1(intro_auth_pubkey_str, intro_enc_pubkey, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential) + + service_enc_key, service_mac_key, service_ntor_key_seed, service_auth_input_mac, service_ephemeral_pubkey = service_part1(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential) + + assert(client_enc_key == service_enc_key) + assert(client_mac_key == service_mac_key) + + client_ntor_key_seed, client_auth_input_mac = client_part2(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, + intro_enc_pubkey, service_ephemeral_pubkey) + + assert(client_ntor_key_seed == service_ntor_key_seed) + assert(client_auth_input_mac == service_auth_input_mac) + + print("DONE: python dance [%s]" % repr(client_auth_input_mac)) + +# Perform a pure little-t-tor integration test. +def do_little_t_tor_ntor_test(): + # Initialize all needed key material + subcredential = os.urandom(32) + client_ephemeral_enc_privkey = PrivateKey() + client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public() + intro_enc_privkey = PrivateKey() + intro_enc_pubkey = intro_enc_privkey.get_public() # service-side enc key + intro_auth_pubkey_str = os.urandom(32) + + client_enc_key, client_mac_key = tor_client1(intro_auth_pubkey_str, intro_enc_pubkey, + client_ephemeral_enc_privkey, subcredential) + assert(client_enc_key) + assert(client_mac_key) + + service_enc_key, service_mac_key, service_ntor_auth_mac, service_ntor_key_seed, service_eph_pubkey = tor_server1(intro_auth_pubkey_str, + intro_enc_privkey, + client_ephemeral_enc_pubkey, + subcredential) + assert(service_enc_key) + assert(service_mac_key) + assert(service_ntor_auth_mac) + assert(service_ntor_key_seed) + + assert(client_enc_key == service_enc_key) + assert(client_mac_key == service_mac_key) + + # Turn from bytes to key + service_eph_pubkey = curve25519mod.Public(service_eph_pubkey) + + client_ntor_auth_mac, client_ntor_key_seed = tor_client2(intro_auth_pubkey_str, client_ephemeral_enc_privkey, + intro_enc_pubkey, service_eph_pubkey, subcredential) + assert(client_ntor_auth_mac) + assert(client_ntor_key_seed) + + assert(client_ntor_key_seed == service_ntor_key_seed) + assert(client_ntor_auth_mac == service_ntor_auth_mac) + + print("DONE: tor dance [%s]" % repr(client_ntor_auth_mac)) + +""" +Do mixed test as follows: + 1. C -> S (python mode) + 2. C <- S (tor mode) + 3. Client computes keys (python mode) +""" +def do_first_mixed_test(): + subcredential = os.urandom(32) + + client_ephemeral_enc_privkey = PrivateKey() + client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public() + intro_enc_privkey = PrivateKey() + intro_enc_pubkey = intro_enc_privkey.get_public() # service-side enc key + + intro_auth_pubkey_str = os.urandom(32) + + # Let's do mixed + client_enc_key, client_mac_key = client_part1(intro_auth_pubkey_str, intro_enc_pubkey, + client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, + subcredential) + + service_enc_key, service_mac_key, service_ntor_auth_mac, service_ntor_key_seed, service_eph_pubkey = tor_server1(intro_auth_pubkey_str, + intro_enc_privkey, + client_ephemeral_enc_pubkey, + subcredential) + assert(service_enc_key) + assert(service_mac_key) + assert(service_ntor_auth_mac) + assert(service_ntor_key_seed) + assert(service_eph_pubkey) + + assert(client_enc_key == service_enc_key) + assert(client_mac_key == service_mac_key) + + # Turn from bytes to key + service_eph_pubkey = curve25519mod.Public(service_eph_pubkey) + + client_ntor_key_seed, client_auth_input_mac = client_part2(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, + intro_enc_pubkey, service_eph_pubkey) + + assert(client_auth_input_mac == service_ntor_auth_mac) + assert(client_ntor_key_seed == service_ntor_key_seed) + + print("DONE: 1st mixed dance [%s]" % repr(client_auth_input_mac)) + +""" +Do mixed test as follows: + 1. C -> S (tor mode) + 2. C <- S (python mode) + 3. Client computes keys (tor mode) +""" +def do_second_mixed_test(): + subcredential = os.urandom(32) + + client_ephemeral_enc_privkey = PrivateKey() + client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public() + intro_enc_privkey = PrivateKey() + intro_enc_pubkey = intro_enc_privkey.get_public() # service-side enc key + + intro_auth_pubkey_str = os.urandom(32) + + # Let's do mixed + client_enc_key, client_mac_key = tor_client1(intro_auth_pubkey_str, intro_enc_pubkey, + client_ephemeral_enc_privkey, subcredential) + assert(client_enc_key) + assert(client_mac_key) + + service_enc_key, service_mac_key, service_ntor_key_seed, service_ntor_auth_mac, service_ephemeral_pubkey = service_part1(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential) + + client_ntor_auth_mac, client_ntor_key_seed = tor_client2(intro_auth_pubkey_str, client_ephemeral_enc_privkey, + intro_enc_pubkey, service_ephemeral_pubkey, subcredential) + assert(client_ntor_auth_mac) + assert(client_ntor_key_seed) + + assert(client_ntor_key_seed == service_ntor_key_seed) + assert(client_ntor_auth_mac == service_ntor_auth_mac) + + print("DONE: 2nd mixed dance [%s]" % repr(client_ntor_auth_mac)) + +def do_mixed_tests(): + do_first_mixed_test() + do_second_mixed_test() + +if __name__ == '__main__': + do_pure_python_ntor_test() + do_little_t_tor_ntor_test() + do_mixed_tests() diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c new file mode 100644 index 0000000000..3f0d6a9413 --- /dev/null +++ b/src/test/hs_test_helpers.c @@ -0,0 +1,257 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "crypto_ed25519.h" +#include "test.h" +#include "torcert.h" + +#include "hs_test_helpers.h" + +hs_desc_intro_point_t * +hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now, + const char *addr, int legacy) +{ + int ret; + ed25519_keypair_t auth_kp; + hs_desc_intro_point_t *intro_point = NULL; + hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); + ip->link_specifiers = smartlist_new(); + + { + hs_desc_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls)); + if (legacy) { + ls->type = LS_LEGACY_ID; + memcpy(ls->u.legacy_id, "0299F268FCA9D55CD157976D39AE92B4B455B3A8", + DIGEST_LEN); + } else { + ls->u.ap.port = 9001; + int family = tor_addr_parse(&ls->u.ap.addr, addr); + switch (family) { + case AF_INET: + ls->type = LS_IPV4; + break; + case AF_INET6: + ls->type = LS_IPV6; + break; + default: + /* Stop the test, not suppose to have an error. */ + tt_int_op(family, OP_EQ, AF_INET); + } + } + smartlist_add(ip->link_specifiers, ls); + } + + ret = ed25519_keypair_generate(&auth_kp, 0); + tt_int_op(ret, ==, 0); + ip->auth_key_cert = tor_cert_create(signing_kp, CERT_TYPE_AUTH_HS_IP_KEY, + &auth_kp.pubkey, now, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + tt_assert(ip->auth_key_cert); + + if (legacy) { + ip->legacy.key = crypto_pk_new(); + tt_assert(ip->legacy.key); + ret = crypto_pk_generate_key(ip->legacy.key); + tt_int_op(ret, ==, 0); + ssize_t cert_len = tor_make_rsa_ed25519_crosscert( + &signing_kp->pubkey, ip->legacy.key, + now + HS_DESC_CERT_LIFETIME, + &ip->legacy.cert.encoded); + tt_assert(ip->legacy.cert.encoded); + tt_u64_op(cert_len, OP_GT, 0); + ip->legacy.cert.len = cert_len; + } + + /* Encryption key. */ + { + int signbit; + curve25519_keypair_t curve25519_kp; + ed25519_keypair_t ed25519_kp; + tor_cert_t *cross_cert; + + ret = curve25519_keypair_generate(&curve25519_kp, 0); + tt_int_op(ret, ==, 0); + ed25519_keypair_from_curve25519_keypair(&ed25519_kp, &signbit, + &curve25519_kp); + cross_cert = tor_cert_create(signing_kp, CERT_TYPE_CROSS_HS_IP_KEYS, + &ed25519_kp.pubkey, time(NULL), + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + tt_assert(cross_cert); + ip->enc_key_cert = cross_cert; + } + + intro_point = ip; + done: + return intro_point; +} + +/* Return a valid hs_descriptor_t object. If no_ip is set, no introduction + * points are added. */ +static hs_descriptor_t * +hs_helper_build_hs_desc_impl(unsigned int no_ip, + const ed25519_keypair_t *signing_kp) +{ + int ret; + time_t now = time(NULL); + ed25519_keypair_t blinded_kp; + hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc)); + + desc->plaintext_data.version = HS_DESC_SUPPORTED_FORMAT_VERSION_MAX; + + /* Copy only the public key into the descriptor. */ + memcpy(&desc->plaintext_data.signing_pubkey, &signing_kp->pubkey, + sizeof(ed25519_public_key_t)); + + ret = ed25519_keypair_generate(&blinded_kp, 0); + tt_int_op(ret, ==, 0); + /* Copy only the public key into the descriptor. */ + memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey, + sizeof(ed25519_public_key_t)); + + desc->plaintext_data.signing_key_cert = + tor_cert_create(&blinded_kp, CERT_TYPE_SIGNING_HS_DESC, + &signing_kp->pubkey, now, 3600, + CERT_FLAG_INCLUDE_SIGNING_KEY); + tt_assert(desc->plaintext_data.signing_key_cert); + desc->plaintext_data.revision_counter = 42; + desc->plaintext_data.lifetime_sec = 3 * 60 * 60; + + /* Setup encrypted data section. */ + desc->encrypted_data.create2_ntor = 1; + desc->encrypted_data.intro_auth_types = smartlist_new(); + desc->encrypted_data.single_onion_service = 1; + smartlist_add(desc->encrypted_data.intro_auth_types, tor_strdup("ed25519")); + desc->encrypted_data.intro_points = smartlist_new(); + if (!no_ip) { + /* Add four intro points. */ + smartlist_add(desc->encrypted_data.intro_points, + hs_helper_build_intro_point(signing_kp, now, "1.2.3.4", 0)); + smartlist_add(desc->encrypted_data.intro_points, + hs_helper_build_intro_point(signing_kp, now, "[2600::1]", 0)); + smartlist_add(desc->encrypted_data.intro_points, + hs_helper_build_intro_point(signing_kp, now, "3.2.1.4", 1)); + smartlist_add(desc->encrypted_data.intro_points, + hs_helper_build_intro_point(signing_kp, now, "", 1)); + } + + descp = desc; + done: + return descp; +} + +/* Build a descriptor with introduction points. */ +hs_descriptor_t * +hs_helper_build_hs_desc_with_ip(const ed25519_keypair_t *signing_kp) +{ + return hs_helper_build_hs_desc_impl(0, signing_kp); +} + +/* Build a descriptor without any introduction points. */ +hs_descriptor_t * +hs_helper_build_hs_desc_no_ip(const ed25519_keypair_t *signing_kp) +{ + return hs_helper_build_hs_desc_impl(1, signing_kp); +} + +void +hs_helper_desc_equal(const hs_descriptor_t *desc1, + const hs_descriptor_t *desc2) +{ + char *addr1 = NULL, *addr2 = NULL; + /* Plaintext data section. */ + tt_int_op(desc1->plaintext_data.version, OP_EQ, + desc2->plaintext_data.version); + tt_uint_op(desc1->plaintext_data.lifetime_sec, OP_EQ, + desc2->plaintext_data.lifetime_sec); + tt_assert(tor_cert_eq(desc1->plaintext_data.signing_key_cert, + desc2->plaintext_data.signing_key_cert)); + tt_mem_op(desc1->plaintext_data.signing_pubkey.pubkey, OP_EQ, + desc2->plaintext_data.signing_pubkey.pubkey, + ED25519_PUBKEY_LEN); + tt_mem_op(desc1->plaintext_data.blinded_pubkey.pubkey, OP_EQ, + desc2->plaintext_data.blinded_pubkey.pubkey, + ED25519_PUBKEY_LEN); + tt_u64_op(desc1->plaintext_data.revision_counter, ==, + desc2->plaintext_data.revision_counter); + + /* NOTE: We can't compare the encrypted blob because when encoding the + * descriptor, the object is immutable thus we don't update it with the + * encrypted blob. As contrast to the decoding process where we populate a + * descriptor object. */ + + /* Encrypted data section. */ + tt_uint_op(desc1->encrypted_data.create2_ntor, ==, + desc2->encrypted_data.create2_ntor); + + /* Authentication type. */ + tt_int_op(!!desc1->encrypted_data.intro_auth_types, ==, + !!desc2->encrypted_data.intro_auth_types); + if (desc1->encrypted_data.intro_auth_types && + desc2->encrypted_data.intro_auth_types) { + tt_int_op(smartlist_len(desc1->encrypted_data.intro_auth_types), ==, + smartlist_len(desc2->encrypted_data.intro_auth_types)); + for (int i = 0; + i < smartlist_len(desc1->encrypted_data.intro_auth_types); + i++) { + tt_str_op(smartlist_get(desc1->encrypted_data.intro_auth_types, i),OP_EQ, + smartlist_get(desc2->encrypted_data.intro_auth_types, i)); + } + } + + /* Introduction points. */ + { + tt_assert(desc1->encrypted_data.intro_points); + tt_assert(desc2->encrypted_data.intro_points); + tt_int_op(smartlist_len(desc1->encrypted_data.intro_points), ==, + smartlist_len(desc2->encrypted_data.intro_points)); + for (int i=0; i < smartlist_len(desc1->encrypted_data.intro_points); i++) { + hs_desc_intro_point_t *ip1 = smartlist_get(desc1->encrypted_data + .intro_points, i), + *ip2 = smartlist_get(desc2->encrypted_data + .intro_points, i); + tt_assert(tor_cert_eq(ip1->auth_key_cert, ip2->auth_key_cert)); + if (ip1->legacy.key) { + tt_int_op(crypto_pk_cmp_keys(ip1->legacy.key, ip2->legacy.key), + OP_EQ, 0); + } else { + tt_mem_op(&ip1->enc_key, OP_EQ, &ip2->enc_key, CURVE25519_PUBKEY_LEN); + } + + tt_int_op(smartlist_len(ip1->link_specifiers), ==, + smartlist_len(ip2->link_specifiers)); + for (int j = 0; j < smartlist_len(ip1->link_specifiers); j++) { + hs_desc_link_specifier_t *ls1 = smartlist_get(ip1->link_specifiers, j), + *ls2 = smartlist_get(ip2->link_specifiers, j); + tt_int_op(ls1->type, ==, ls2->type); + switch (ls1->type) { + case LS_IPV4: + case LS_IPV6: + { + addr1 = tor_addr_to_str_dup(&ls1->u.ap.addr); + addr2 = tor_addr_to_str_dup(&ls2->u.ap.addr); + tt_str_op(addr1, OP_EQ, addr2); + tor_free(addr1); + tor_free(addr2); + tt_int_op(ls1->u.ap.port, ==, ls2->u.ap.port); + } + break; + case LS_LEGACY_ID: + tt_mem_op(ls1->u.legacy_id, OP_EQ, ls2->u.legacy_id, + sizeof(ls1->u.legacy_id)); + break; + default: + /* Unknown type, caught it and print its value. */ + tt_int_op(ls1->type, OP_EQ, -1); + } + } + } + } + + done: + tor_free(addr1); + tor_free(addr2); +} + diff --git a/src/test/hs_test_helpers.h b/src/test/hs_test_helpers.h new file mode 100644 index 0000000000..a7fedab136 --- /dev/null +++ b/src/test/hs_test_helpers.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_HS_TEST_HELPERS_H +#define TOR_HS_TEST_HELPERS_H + +#include "ed25519_cert.h" +#include "hs_descriptor.h" + +/* Set of functions to help build and test descriptors. */ +hs_desc_intro_point_t *hs_helper_build_intro_point( + const ed25519_keypair_t *signing_kp, time_t now, + const char *addr, int legacy); +hs_descriptor_t *hs_helper_build_hs_desc_no_ip( + const ed25519_keypair_t *signing_kp); +hs_descriptor_t *hs_helper_build_hs_desc_with_ip( + const ed25519_keypair_t *signing_kp); +void hs_helper_desc_equal(const hs_descriptor_t *desc1, + const hs_descriptor_t *desc2); + +#endif /* TOR_HS_TEST_HELPERS_H */ + diff --git a/src/test/include.am b/src/test/include.am index 1c0726fd3a..d5ae0bec1c 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -5,8 +5,11 @@ TESTS_ENVIRONMENT = \ export PYTHON="$(PYTHON)"; \ export SHELL="$(SHELL)"; \ export abs_top_srcdir="$(abs_top_srcdir)"; \ + export abs_top_builddir="$(abs_top_builddir)"; \ export builddir="$(builddir)"; \ - export TESTING_TOR_BINARY="$(TESTING_TOR_BINARY)"; + export TESTING_TOR_BINARY="$(TESTING_TOR_BINARY)"; \ + export CARGO="$(CARGO)"; \ + export CARGO_ONLINE="$(CARGO_ONLINE)"; TESTSCRIPTS = \ src/test/fuzz_static_testcases.sh \ @@ -19,8 +22,13 @@ TESTSCRIPTS = \ src/test/test_workqueue_socketpair.sh \ src/test/test_switch_id.sh +if USE_RUST +TESTSCRIPTS += \ + src/test/test_rust.sh +endif + if USEPYTHON -TESTSCRIPTS += src/test/test_ntor.sh src/test/test_bt.sh +TESTSCRIPTS += src/test/test_ntor.sh src/test/test_hs_ntor.sh src/test/test_bt.sh endif TESTS += src/test/test src/test/test-slow src/test/test-memwipe \ @@ -69,6 +77,7 @@ src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ src_test_test_SOURCES = \ src/test/log_test_helpers.c \ + src/test/hs_test_helpers.c \ src/test/rend_test_helpers.c \ src/test/test.c \ src/test/test_accounting.c \ @@ -78,18 +87,24 @@ src_test_test_SOURCES = \ src/test/test_cell_formats.c \ src/test/test_cell_queue.c \ src/test/test_channel.c \ + src/test/test_channelpadding.c \ src/test/test_channeltls.c \ src/test/test_checkdir.c \ src/test/test_circuitlist.c \ src/test/test_circuitmux.c \ + src/test/test_circuitbuild.c \ src/test/test_circuituse.c \ src/test/test_compat_libevent.c \ src/test/test_config.c \ src/test/test_connection.c \ + src/test/test_conscache.c \ + src/test/test_consdiff.c \ + src/test/test_consdiffmgr.c \ src/test/test_containers.c \ src/test/test_controller.c \ src/test/test_controller_events.c \ src/test/test_crypto.c \ + src/test/test_crypto_openssl.c \ src/test/test_data.c \ src/test/test_dir.c \ src/test/test_dir_common.c \ @@ -125,10 +140,12 @@ src_test_test_SOURCES = \ src/test/test_routerkeys.c \ src/test/test_routerlist.c \ src/test/test_routerset.c \ + src/test/test_rust.c \ src/test/test_scheduler.c \ src/test/test_shared_random.c \ src/test/test_socks.c \ src/test/test_status.c \ + src/test/test_storagedir.c \ src/test/test_threads.c \ src/test/test_tortls.c \ src/test/test_util.c \ @@ -174,7 +191,9 @@ src_test_test_switch_id_LDFLAGS = @TOR_LDFLAGS_zlib@ src_test_test_switch_id_LDADD = \ src/common/libor-testing.a \ src/common/libor-ctime-testing.a \ - @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ + @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ + $(rust_ldadd) src_test_test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ @@ -186,9 +205,11 @@ src_test_test_LDADD = src/or/libtor-testing.a \ src/common/libor-ctime-testing.a \ src/common/libor-event-testing.a \ src/trunnel/libor-trunnel-testing.a \ + src/trace/libor-trace.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_SYSTEMD_LIBS@ + @TOR_SYSTEMD_LIBS@ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ + $(rust_ldadd) src_test_test_slow_CPPFLAGS = $(src_test_test_CPPFLAGS) src_test_test_slow_CFLAGS = $(src_test_test_CFLAGS) @@ -209,9 +230,11 @@ src_test_bench_LDADD = src/or/libtor.a src/common/libor.a \ src/common/libor-ctime.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ + src/trace/libor-trace.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_SYSTEMD_LIBS@ + @TOR_SYSTEMD_LIBS@ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ + $(rust_ldadd) src_test_test_workqueue_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ @@ -220,8 +243,11 @@ src_test_test_workqueue_LDADD = src/or/libtor-testing.a \ src/common/libor-ctime-testing.a \ src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event-testing.a \ + src/trace/libor-trace.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ + $(rust_ldadd) src_test_test_timers_CPPFLAGS = $(src_test_test_CPPFLAGS) src_test_test_timers_CFLAGS = $(src_test_test_CFLAGS) @@ -231,11 +257,14 @@ src_test_test_timers_LDADD = \ src/common/libor-event-testing.a \ src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ + @TOR_LZMA_LIBS@ \ + $(rust_ldadd) src_test_test_timers_LDFLAGS = $(src_test_test_LDFLAGS) noinst_HEADERS+= \ src/test/fakechans.h \ + src/test/hs_test_helpers.h \ src/test/log_test_helpers.h \ src/test/rend_test_helpers.h \ src/test/test.h \ @@ -246,38 +275,58 @@ noinst_HEADERS+= \ src/test/failing_routerdescs.inc \ src/test/ed25519_vectors.inc \ src/test/test_descriptors.inc \ + src/test/test_hs_descriptor.inc \ src/test/vote_descriptors.inc noinst_PROGRAMS+= src/test/test-ntor-cl +noinst_PROGRAMS+= src/test/test-hs-ntor-cl src_test_test_ntor_cl_SOURCES = src/test/test_ntor_cl.c src_test_test_ntor_cl_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ src_test_test_ntor_cl_LDADD = src/or/libtor.a src/common/libor.a \ src/common/libor-ctime.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ + src/trace/libor-trace.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ + @TOR_LZMA_LIBS@ \ + $(rust_ldadd) src_test_test_ntor_cl_AM_CPPFLAGS = \ -I"$(top_srcdir)/src/or" +src_test_test_hs_ntor_cl_SOURCES = src/test/test_hs_ntor_cl.c +src_test_test_hs_ntor_cl_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ +src_test_test_hs_ntor_cl_LDADD = src/or/libtor.a src/common/libor.a \ + src/common/libor-ctime.a \ + src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ + @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ +src_test_test_hs_ntor_cl_AM_CPPFLAGS = \ + -I"$(top_srcdir)/src/or" + + noinst_PROGRAMS += src/test/test-bt-cl src_test_test_bt_cl_SOURCES = src/test/test_bt_cl.c src_test_test_bt_cl_LDADD = src/common/libor-testing.a \ src/common/libor-ctime-testing.a \ + src/trace/libor-trace.a \ @TOR_LIB_MATH@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ \ + $(rust_ldadd) src_test_test_bt_cl_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) src_test_test_bt_cl_CPPFLAGS= $(src_test_AM_CPPFLAGS) $(TEST_CPPFLAGS) EXTRA_DIST += \ src/test/bt_test.py \ src/test/ntor_ref.py \ + src/test/hs_ntor_ref.py \ src/test/fuzz_static_testcases.sh \ src/test/slownacl_curve25519.py \ src/test/zero_length_keys.sh \ src/test/test_keygen.sh \ src/test/test_zero_length_keys.sh \ - src/test/test_ntor.sh src/test/test_bt.sh \ + src/test/test_ntor.sh src/test/test_hs_ntor.sh src/test/test_bt.sh \ src/test/test-network.sh \ + src/test/test_rust.sh \ src/test/test_switch_id.sh \ src/test/test_workqueue_cancel.sh \ src/test/test_workqueue_efd.sh \ diff --git a/src/test/log_test_helpers.c b/src/test/log_test_helpers.c index c788a33c17..d5a39cfeee 100644 --- a/src/test/log_test_helpers.c +++ b/src/test/log_test_helpers.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define LOG_PRIVATE #include "torlog.h" diff --git a/src/test/log_test_helpers.h b/src/test/log_test_helpers.h index 922c68b42f..f7798c0249 100644 --- a/src/test/log_test_helpers.h +++ b/src/test/log_test_helpers.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" @@ -71,14 +71,14 @@ void mock_dump_saved_logs(void); \ assert_log_predicate(mock_saved_log_has_message_containing(str) && \ mock_saved_log_n_entries() == 1, \ - "expected log to contain exactly 1 message: " # str); \ + "expected log to contain exactly 1 message " # str); \ } while (0); #define expect_single_log_msg_containing(str) \ do { \ assert_log_predicate(mock_saved_log_has_message_containing(str)&& \ mock_saved_log_n_entries() == 1 , \ - "expected log to contain 1 message, containing" # str); \ + "expected log to contain 1 message, containing " # str); \ } while (0); #define expect_no_log_msg(str) \ diff --git a/src/test/ntor_ref.py b/src/test/ntor_ref.py index df065853f3..c753588f97 100755 --- a/src/test/ntor_ref.py +++ b/src/test/ntor_ref.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# Copyright 2012-2015, The Tor Project, Inc +# Copyright 2012-2017, The Tor Project, Inc # See LICENSE for licensing information """ diff --git a/src/test/rend_test_helpers.c b/src/test/rend_test_helpers.c index 377337bcb9..f7880046fb 100644 --- a/src/test/rend_test_helpers.c +++ b/src/test/rend_test_helpers.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/test/rend_test_helpers.h b/src/test/rend_test_helpers.h index 180a4e8fde..486adba436 100644 --- a/src/test/rend_test_helpers.h +++ b/src/test/rend_test_helpers.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/test/test-child.c b/src/test/test-child.c index fdf3ccec0a..f0bdb3ea26 100644 --- a/src/test/test-child.c +++ b/src/test/test-child.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016, The Tor Project, Inc. */ +/* Copyright (c) 2011-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test-network.sh b/src/test/test-network.sh index 10bd370ff3..6e0f286573 100755 --- a/src/test/test-network.sh +++ b/src/test/test-network.sh @@ -1,119 +1,45 @@ #!/bin/sh -# use bash if it is available, as this script doesn't work well in non-bash sh -# this will be fixed in #19699 -# there is no simple, portable way of checking the name of the shell, so we -# exec bash even when sh is bash -if [ -x /bin/bash -a "$USING_BASH" != true ]; then - # only do this once - export USING_BASH=true - exec /bin/bash "$0" "$@" +# This script calls the equivalent script in chutney/tools + +# If we already know CHUTNEY_PATH, don't bother with argument parsing +TEST_NETWORK="$CHUTNEY_PATH/tools/test-network.sh" +# Call the chutney version of this script, if it exists, and we can find it +if [ -d "$CHUTNEY_PATH" -a -x "$TEST_NETWORK" ]; then + # we can't produce any output, because we might be --quiet + # this preserves arguments with spaces correctly + exec "$TEST_NETWORK" "$@" fi -# Please do not modify this script, it has been moved to chutney/tools - -export ECHO="${ECHO:-echo}" -export ECHO_N="${ECHO_N:-/bin/echo -n}" +# We need to go looking for CHUTNEY_PATH +# Do we output anything at all? +ECHO="${ECHO:-echo}" # Output is prefixed with the name of the script myname=$(basename $0) -# We need to find CHUTNEY_PATH, so that we can call the version of this script -# in chutney/tools. And we want to pass any arguments to that script as well. -# So we source this script, which processes its arguments to find CHUTNEY_PATH. - -# Avoid recursively sourcing this script, and don't call the chutney version -# while recursing, either -if [ "$TEST_NETWORK_RECURSING" != true ]; then - # Process the arguments into environmental variables with this script - # to make sure $CHUTNEY_PATH is set - # When we switch to using test-network.sh in chutney/tools, --dry-run - # can be removed, because this script will find chutney, then pass all - # arguments to chutney's test-network.sh - export TEST_NETWORK_RECURSING=true - # passing arguments to a sourced script only works in bash - # this will be fixed in #19699 - . "$0" --dry-run "$@" - - # Call the chutney version of this script, if it exists, and we can find it - if [ -d "$CHUTNEY_PATH" -a -x "$CHUTNEY_PATH/tools/test-network.sh" ]; then - unset NETWORK_DRY_RUN - $ECHO "$myname: Calling newer chutney script \ -$CHUTNEY_PATH/tools/test-network.sh" - "$CHUTNEY_PATH/tools/test-network.sh" "$@" - exit $? - else - $ECHO "$myname: This script has moved to chutney/tools." - $ECHO "$myname: Please update your chutney using 'git pull'." - # When we switch to using test-network.sh in chutney/tools, we should - # exit with a very loud failure here - $ECHO "$myname: Falling back to the old tor version of the script." - fi -fi +# Save the arguments before we destroy them +# This might not preserve arguments with spaces in them +ORIGINAL_ARGS="$@" +# We need to find CHUTNEY_PATH, so that we can call the version of this script +# in chutney/tools with the same arguments. We also need to respect --quiet. until [ -z "$1" ] do case "$1" in --chutney-path) - export CHUTNEY_PATH="$2" + CHUTNEY_PATH="$2" shift ;; --tor-path) - export TOR_DIR="$2" - shift - ;; - # When we switch to using test-network.sh in chutney/tools, only the - # --chutney-path and --tor-path arguments need to be processed by this - # script, everything else can be handled by chutney's test-network.sh - --flavor|--flavour|--network-flavor|--network-flavour) - export NETWORK_FLAVOUR="$2" + TOR_DIR="$2" shift ;; - --delay|--sleep|--bootstrap-time|--time) - export BOOTSTRAP_TIME="$2" - shift - ;; - # Environmental variables used by chutney verify performance tests - # Send this many bytes per client connection (10 KBytes) - --data|--data-bytes|--data-byte|--bytes|--byte) - export CHUTNEY_DATA_BYTES="$2" - shift - ;; - # Make this many connections per client (1) - # Note: If you create 7 or more connections to a hidden service from - # a single Tor 0.2.7 client, you'll likely get a verification failure due - # to #15937. This is fixed in 0.2.8. - --connections|--connection|--connection-count|--count) - export CHUTNEY_CONNECTIONS="$2" - shift - ;; - # Make each client connect to each HS (0) - # 0 means a single client connects to each HS - # 1 means every client connects to every HS - --hs-multi-client|--hs-multi-clients|--hs-client|--hs-clients) - export CHUTNEY_HS_MULTI_CLIENT="$2" - shift - ;; - --coverage) - export USE_COVERAGE_BINARY=true - ;; - --dry-run) - # process arguments, but don't call any other scripts - export NETWORK_DRY_RUN=true - ;; --quiet) - export ECHO=true - export ECHO_N=true - ;; + ECHO=true + ;; *) - $ECHO "$myname: Sorry, I don't know what to do with '$1'." - $ECHO "$myname: Maybe chutney's test-network.sh understands '$1'." - $ECHO "$myname: Please update your chutney using 'git pull', and set \ -\$CHUTNEY_PATH" - # continue processing arguments during a dry run - if [ "$NETWORK_DRY_RUN" != true ]; then - exit 2 - fi + # maybe chutney's test-network.sh can handle it ;; esac shift @@ -122,7 +48,7 @@ done # optional: $TOR_DIR is the tor build directory # it's used to find the location of tor binaries # if it's not set: -# - set it ro $BUILDDIR, or +# - set it to $BUILDDIR, or # - if $PWD looks like a tor build directory, set it to $PWD, or # - unset $TOR_DIR, and let chutney fall back to finding tor binaries in $PATH if [ ! -d "$TOR_DIR" ]; then @@ -130,12 +56,12 @@ if [ ! -d "$TOR_DIR" ]; then # Choose the build directory # But only if it looks like one $ECHO "$myname: \$TOR_DIR not set, trying \$BUILDDIR" - export TOR_DIR="$BUILDDIR" + TOR_DIR="$BUILDDIR" elif [ -d "$PWD/src/or" -a -d "$PWD/src/tools" ]; then # Guess the tor directory is the current directory # But only if it looks like one $ECHO "$myname: \$TOR_DIR not set, trying \$PWD" - export TOR_DIR="$PWD" + TOR_DIR="$PWD" else $ECHO "$myname: no \$TOR_DIR, chutney will use \$PATH for tor binaries" unset TOR_DIR @@ -150,14 +76,12 @@ fi if [ ! -d "$CHUTNEY_PATH" -o ! -x "$CHUTNEY_PATH/chutney" ]; then if [ -x "$PWD/chutney" ]; then $ECHO "$myname: \$CHUTNEY_PATH not valid, trying \$PWD" - export CHUTNEY_PATH="$PWD" + CHUTNEY_PATH="$PWD" elif [ -d "$TOR_DIR" -a -d "$TOR_DIR/../chutney" -a \ -x "$TOR_DIR/../chutney/chutney" ]; then $ECHO "$myname: \$CHUTNEY_PATH not valid, trying \$TOR_DIR/../chutney" - export CHUTNEY_PATH="$TOR_DIR/../chutney" + CHUTNEY_PATH="$TOR_DIR/../chutney" else - # TODO: work out how to package and install chutney, - # so users can find it in $PATH $ECHO "$myname: missing 'chutney' in \$CHUTNEY_PATH ($CHUTNEY_PATH)" $ECHO "$myname: Get chutney: git clone https://git.torproject.org/\ chutney.git" @@ -168,46 +92,17 @@ CHUTNEY_PATH=\`pwd\`/chutney" fi fi -# When we switch to using test-network.sh in chutney/tools, this comment and -# everything below it can be removed - -# For picking up the right tor binaries. -# If these varibles aren't set, chutney looks for tor binaries in $PATH -if [ -d "$TOR_DIR" ]; then - tor_name=tor - tor_gencert_name=tor-gencert - if [ "$USE_COVERAGE_BINARY" = true ]; then - tor_name=tor-cov - fi - export CHUTNEY_TOR="${TOR_DIR}/src/or/${tor_name}" - export CHUTNEY_TOR_GENCERT="${TOR_DIR}/src/tools/${tor_gencert_name}" -fi - -# Set the variables for the chutney network flavour -export NETWORK_FLAVOUR=${NETWORK_FLAVOUR:-"bridges+hs"} -export CHUTNEY_NETWORK=networks/$NETWORK_FLAVOUR - -# And finish up if we're doing a dry run -if [ "$NETWORK_DRY_RUN" = true ]; then - # we can't exit here, it breaks argument processing - # this only works in bash: return semantics are shell-specific - # this will be fixed in #19699 - return 2>/dev/null || exit +TEST_NETWORK="$CHUTNEY_PATH/tools/test-network.sh" +# Call the chutney version of this script, if it exists, and we can find it +if [ -d "$CHUTNEY_PATH" -a -x "$TEST_NETWORK" ]; then + $ECHO "$myname: Calling newer chutney script $TEST_NETWORK" + # this may fail if some arguments have spaces in them + # if so, set CHUTNEY_PATH before calling test-network.sh, and spaces + # will be handled correctly + exec "$TEST_NETWORK" $ORIGINAL_ARGS +else + $ECHO "$myname: Could not find tools/test-network.sh in CHUTNEY_PATH." + $ECHO "$myname: Please update your chutney using 'git pull'." + # We have failed to do what the user asked + exit 1 fi - -cd "$CHUTNEY_PATH" -./tools/bootstrap-network.sh $NETWORK_FLAVOUR || exit 3 - -# Sleep some, waiting for the network to bootstrap. -# TODO: Add chutney command 'bootstrap-status' and use that instead. -BOOTSTRAP_TIME=${BOOTSTRAP_TIME:-35} -$ECHO_N "$myname: sleeping for $BOOTSTRAP_TIME seconds" -n=$BOOTSTRAP_TIME; while [ $n -gt 0 ]; do - sleep 1; n=$(expr $n - 1); $ECHO_N . -done; $ECHO "" -./chutney verify $CHUTNEY_NETWORK -VERIFY_EXIT_STATUS=$? -# work around a bug/feature in make -j2 (or more) -# where make hangs if any child processes are still alive -./chutney stop $CHUTNEY_NETWORK -exit $VERIFY_EXIT_STATUS diff --git a/src/test/test-timers.c b/src/test/test-timers.c index b5fcade7f8..99715f4333 100644 --- a/src/test/test-timers.c +++ b/src/test/test-timers.c @@ -1,4 +1,4 @@ -/* Copyright 2016, The Tor Project, Inc. */ +/* Copyright 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test.c b/src/test/test.c index 866408e856..68f5f90fd7 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -44,13 +44,13 @@ double fabs(double x); #include "buffers.h" #include "circuitlist.h" #include "circuitstats.h" +#include "compress.h" #include "config.h" #include "connection_edge.h" #include "geoip.h" #include "rendcommon.h" #include "rendcache.h" #include "test.h" -#include "torgzip.h" #include "main.h" #include "memarea.h" #include "onion.h" @@ -1186,18 +1186,24 @@ struct testgroup_t testgroups[] = { { "cellfmt/", cell_format_tests }, { "cellqueue/", cell_queue_tests }, { "channel/", channel_tests }, + { "channelpadding/", channelpadding_tests }, { "channeltls/", channeltls_tests }, { "checkdir/", checkdir_tests }, + { "circuitbuild/", circuitbuild_tests }, { "circuitlist/", circuitlist_tests }, { "circuitmux/", circuitmux_tests }, { "circuituse/", circuituse_tests }, { "compat/libevent/", compat_libevent_tests }, { "config/", config_tests }, { "connection/", connection_tests }, + { "conscache/", conscache_tests }, + { "consdiff/", consdiff_tests }, + { "consdiffmgr/", consdiffmgr_tests }, { "container/", container_tests }, { "control/", controller_tests }, { "control/event/", controller_event_tests }, { "crypto/", crypto_tests }, + { "crypto/openssl/", crypto_openssl_tests }, { "dir/", dir_tests }, { "dir_handle_get/", dir_handle_get_tests }, { "dir/md/", microdesc_tests }, @@ -1228,10 +1234,12 @@ struct testgroup_t testgroups[] = { { "routerkeys/", routerkeys_tests }, { "routerlist/", routerlist_tests }, { "routerset/" , routerset_tests }, + { "rust/", rust_tests }, { "scheduler/", scheduler_tests }, { "socks/", socks_tests }, { "shared-random/", sr_tests }, { "status/" , status_tests }, + { "storagedir/", storagedir_tests }, { "tortls/", tortls_tests }, { "util/", util_tests }, { "util/format/", util_format_tests }, diff --git a/src/test/test.h b/src/test/test.h index 2bd58f51c8..6abaf39e6f 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2003, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_TEST_H @@ -181,18 +181,24 @@ extern struct testcase_t buffer_tests[]; extern struct testcase_t cell_format_tests[]; extern struct testcase_t cell_queue_tests[]; extern struct testcase_t channel_tests[]; +extern struct testcase_t channelpadding_tests[]; extern struct testcase_t channeltls_tests[]; extern struct testcase_t checkdir_tests[]; +extern struct testcase_t circuitbuild_tests[]; extern struct testcase_t circuitlist_tests[]; extern struct testcase_t circuitmux_tests[]; extern struct testcase_t circuituse_tests[]; extern struct testcase_t compat_libevent_tests[]; extern struct testcase_t config_tests[]; extern struct testcase_t connection_tests[]; +extern struct testcase_t conscache_tests[]; +extern struct testcase_t consdiff_tests[]; +extern struct testcase_t consdiffmgr_tests[]; extern struct testcase_t container_tests[]; extern struct testcase_t controller_tests[]; extern struct testcase_t controller_event_tests[]; extern struct testcase_t crypto_tests[]; +extern struct testcase_t crypto_openssl_tests[]; extern struct testcase_t dir_tests[]; extern struct testcase_t dir_handle_get_tests[]; extern struct testcase_t entryconn_tests[]; @@ -226,7 +232,9 @@ extern struct testcase_t router_tests[]; extern struct testcase_t routerkeys_tests[]; extern struct testcase_t routerlist_tests[]; extern struct testcase_t routerset_tests[]; +extern struct testcase_t rust_tests[]; extern struct testcase_t scheduler_tests[]; +extern struct testcase_t storagedir_tests[]; extern struct testcase_t socks_tests[]; extern struct testcase_t status_tests[]; extern struct testcase_t thread_tests[]; diff --git a/src/test/test_addr.c b/src/test/test_addr.c index be440a0925..2f591bdfe7 100644 --- a/src/test/test_addr.c +++ b/src/test/test_addr.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ADDRESSMAP_PRIVATE @@ -9,6 +9,24 @@ #include "test.h" #include "addressmap.h" +/** Mocking replacement: only handles localhost. */ +static int +mock_tor_addr_lookup(const char *name, uint16_t family, tor_addr_t *addr_out) +{ + if (!strcmp(name, "localhost")) { + if (family == AF_INET || family == AF_UNSPEC) { + tor_addr_from_ipv4h(addr_out, 0x7f000001); + return 0; + } else if (family == AF_INET6) { + char bytes[16] = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 }; + tor_addr_from_ipv6_bytes(addr_out, bytes); + return 0; + } + } + return -1; +} + static void test_addr_basic(void *arg) { @@ -29,6 +47,9 @@ test_addr_basic(void *arg) tt_int_op(u32,OP_EQ, 0x04030201u); tt_int_op(u16,OP_EQ, 99); tor_free(cp); + + MOCK(tor_addr_lookup, mock_tor_addr_lookup); + tt_assert(!addr_port_lookup(LOG_WARN, "nonexistent.address:4040", &cp, NULL, &u16)); tt_str_op(cp,OP_EQ, "nonexistent.address"); @@ -36,8 +57,8 @@ test_addr_basic(void *arg) tor_free(cp); tt_assert(!addr_port_lookup(LOG_WARN, "localhost:9999", &cp, &u32, &u16)); tt_str_op(cp,OP_EQ, "localhost"); - tt_int_op(u32,OP_EQ, 0x7f000001u); tt_int_op(u16,OP_EQ, 9999); + tt_int_op(u32,OP_EQ, 0x7f000001u); tor_free(cp); u32 = 3; tt_assert(!addr_port_lookup(LOG_WARN, "localhost", NULL, &u32, &u16)); @@ -75,6 +96,7 @@ test_addr_basic(void *arg) } done: + UNMOCK(tor_addr_lookup); tor_free(cp); } diff --git a/src/test/test_address.c b/src/test/test_address.c index 0d142ad483..50a0574522 100644 --- a/src/test/test_address.c +++ b/src/test/test_address.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ADDRESS_PRIVATE diff --git a/src/test/test_bt_cl.c b/src/test/test_bt_cl.c index 709d599f52..ed588ecc5b 100644 --- a/src/test/test_bt_cl.c +++ b/src/test/test_bt_cl.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_buffers.c b/src/test/test_buffers.c index 9e7bdb8911..07114a8571 100644 --- a/src/test/test_buffers.c +++ b/src/test/test_buffers.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define BUFFERS_PRIVATE @@ -578,120 +578,150 @@ test_buffer_time_tracking(void *arg) } static void -test_buffers_zlib_impl(int finalize_with_nil) +test_buffers_compress_fin_at_chunk_end_impl(compress_method_t method, + compression_level_t level) { char *msg = NULL; char *contents = NULL; char *expanded = NULL; buf_t *buf = NULL; - tor_zlib_state_t *zlib_state = NULL; + tor_compress_state_t *compress_state = NULL; size_t out_len, in_len; - int done; + size_t sz, headerjunk; buf = buf_new_with_capacity(128); /* will round up */ - zlib_state = tor_zlib_new(1, ZLIB_METHOD, HIGH_COMPRESSION); + sz = buf_get_default_chunk_size(buf); + msg = tor_malloc_zero(sz); - msg = tor_malloc(512); - crypto_rand(msg, 512); - tt_int_op(write_to_buf_zlib(buf, zlib_state, msg, 128, 0), OP_EQ, 0); - tt_int_op(write_to_buf_zlib(buf, zlib_state, msg+128, 128, 0), OP_EQ, 0); - tt_int_op(write_to_buf_zlib(buf, zlib_state, msg+256, 256, 0), OP_EQ, 0); - done = !finalize_with_nil; - tt_int_op(write_to_buf_zlib(buf, zlib_state, "all done", 9, done), OP_EQ, 0); - if (finalize_with_nil) { - tt_int_op(write_to_buf_zlib(buf, zlib_state, "", 0, 1), OP_EQ, 0); - } + write_to_buf(msg, 1, buf); + tt_assert(buf->head); + + /* Fill up the chunk so the compression stuff won't fit in one chunk. */ + tt_uint_op(buf->head->memlen, OP_LT, sz); + headerjunk = buf->head->memlen - 7; + write_to_buf(msg, headerjunk-1, buf); + tt_uint_op(buf->head->datalen, OP_EQ, headerjunk); + tt_uint_op(buf_datalen(buf), OP_EQ, headerjunk); + /* Write an empty string, with finalization on. */ + compress_state = tor_compress_new(1, method, level); + tt_int_op(write_to_buf_compress(buf, compress_state, "", 0, 1), OP_EQ, 0); in_len = buf_datalen(buf); contents = tor_malloc(in_len); tt_int_op(fetch_from_buf(contents, in_len, buf), OP_EQ, 0); - tt_int_op(0, OP_EQ, tor_gzip_uncompress(&expanded, &out_len, - contents, in_len, - ZLIB_METHOD, 1, - LOG_WARN)); + if (method == NO_METHOD) { + tt_uint_op(in_len, OP_EQ, headerjunk); + } else { + tt_uint_op(in_len, OP_GT, headerjunk); + } - tt_int_op(out_len, OP_GE, 128); - tt_mem_op(msg, OP_EQ, expanded, 128); - tt_int_op(out_len, OP_GE, 512); - tt_mem_op(msg, OP_EQ, expanded, 512); - tt_int_op(out_len, OP_EQ, 512+9); - tt_mem_op("all done", OP_EQ, expanded+512, 9); + tt_int_op(0, OP_EQ, tor_uncompress(&expanded, &out_len, + contents + headerjunk, + in_len - headerjunk, + method, 1, + LOG_WARN)); + + tt_int_op(out_len, OP_EQ, 0); + tt_assert(expanded); done: buf_free(buf); - tor_zlib_free(zlib_state); + tor_compress_free(compress_state); tor_free(contents); tor_free(expanded); tor_free(msg); } static void -test_buffers_zlib(void *arg) -{ - (void) arg; - test_buffers_zlib_impl(0); -} -static void -test_buffers_zlib_fin_with_nil(void *arg) -{ - (void) arg; - test_buffers_zlib_impl(1); -} - -static void -test_buffers_zlib_fin_at_chunk_end(void *arg) +test_buffers_compress_impl(compress_method_t method, + compression_level_t level, + int finalize_with_nil) { char *msg = NULL; char *contents = NULL; char *expanded = NULL; buf_t *buf = NULL; - tor_zlib_state_t *zlib_state = NULL; + tor_compress_state_t *compress_state = NULL; size_t out_len, in_len; - size_t sz, headerjunk; - (void) arg; + int done; buf = buf_new_with_capacity(128); /* will round up */ - sz = buf_get_default_chunk_size(buf); - msg = tor_malloc_zero(sz); - - write_to_buf(msg, 1, buf); - tt_assert(buf->head); + compress_state = tor_compress_new(1, method, level); - /* Fill up the chunk so the zlib stuff won't fit in one chunk. */ - tt_uint_op(buf->head->memlen, OP_LT, sz); - headerjunk = buf->head->memlen - 7; - write_to_buf(msg, headerjunk-1, buf); - tt_uint_op(buf->head->datalen, OP_EQ, headerjunk); - tt_uint_op(buf_datalen(buf), OP_EQ, headerjunk); - /* Write an empty string, with finalization on. */ - zlib_state = tor_zlib_new(1, ZLIB_METHOD, HIGH_COMPRESSION); - tt_int_op(write_to_buf_zlib(buf, zlib_state, "", 0, 1), OP_EQ, 0); + msg = tor_malloc(512); + crypto_rand(msg, 512); + tt_int_op(write_to_buf_compress(buf, compress_state, + msg, 128, 0), OP_EQ, 0); + tt_int_op(write_to_buf_compress(buf, compress_state, + msg+128, 128, 0), OP_EQ, 0); + tt_int_op(write_to_buf_compress(buf, compress_state, + msg+256, 256, 0), OP_EQ, 0); + done = !finalize_with_nil; + tt_int_op(write_to_buf_compress(buf, compress_state, + "all done", 9, done), OP_EQ, 0); + if (finalize_with_nil) { + tt_int_op(write_to_buf_compress(buf, compress_state, "", 0, 1), OP_EQ, 0); + } in_len = buf_datalen(buf); contents = tor_malloc(in_len); tt_int_op(fetch_from_buf(contents, in_len, buf), OP_EQ, 0); - tt_uint_op(in_len, OP_GT, headerjunk); - - tt_int_op(0, OP_EQ, tor_gzip_uncompress(&expanded, &out_len, - contents + headerjunk, in_len - headerjunk, - ZLIB_METHOD, 1, - LOG_WARN)); + tt_int_op(0, OP_EQ, tor_uncompress(&expanded, &out_len, + contents, in_len, + method, 1, + LOG_WARN)); - tt_int_op(out_len, OP_EQ, 0); - tt_assert(expanded); + tt_int_op(out_len, OP_GE, 128); + tt_mem_op(msg, OP_EQ, expanded, 128); + tt_int_op(out_len, OP_GE, 512); + tt_mem_op(msg, OP_EQ, expanded, 512); + tt_int_op(out_len, OP_EQ, 512+9); + tt_mem_op("all done", OP_EQ, expanded+512, 9); done: buf_free(buf); - tor_zlib_free(zlib_state); + tor_compress_free(compress_state); tor_free(contents); tor_free(expanded); tor_free(msg); } +static void +test_buffers_compress(void *arg) +{ + const char *methodname = arg; + tt_assert(methodname); + + compress_method_t method = compression_method_get_by_name(methodname); + tt_int_op(method, OP_NE, UNKNOWN_METHOD); + + if (! tor_compress_supports_method(method)) { + tt_skip(); + } + + compression_level_t levels[] = { + BEST_COMPRESSION, + HIGH_COMPRESSION, + MEDIUM_COMPRESSION, + LOW_COMPRESSION + }; + + for (unsigned l = 0; l < ARRAY_LENGTH(levels); ++l) { + compression_level_t level = levels[l]; + + test_buffers_compress_impl(method, level, 0); + test_buffers_compress_impl(method, level, 1); + test_buffers_compress_fin_at_chunk_end_impl(method, level); + } + + done: + ; +} + static const uint8_t *tls_read_ptr; static int n_remaining; static int next_reply_val[16]; @@ -816,14 +846,22 @@ struct testcase_t buffer_tests[] = { { "allocation_tracking", test_buffer_allocation_tracking, TT_FORK, NULL, NULL }, { "time_tracking", test_buffer_time_tracking, TT_FORK, NULL, NULL }, - { "zlib", test_buffers_zlib, TT_FORK, NULL, NULL }, - { "zlib_fin_with_nil", test_buffers_zlib_fin_with_nil, TT_FORK, NULL, NULL }, - { "zlib_fin_at_chunk_end", test_buffers_zlib_fin_at_chunk_end, TT_FORK, - NULL, NULL}, { "tls_read_mocked", test_buffers_tls_read_mocked, 0, NULL, NULL }, { "chunk_size", test_buffers_chunk_size, 0, NULL, NULL }, { "find_contentlen", test_buffers_find_contentlen, 0, NULL, NULL }, + + { "compress/zlib", test_buffers_compress, TT_FORK, + &passthrough_setup, (char*)"deflate" }, + { "compress/gzip", test_buffers_compress, TT_FORK, + &passthrough_setup, (char*)"gzip" }, + { "compress/zstd", test_buffers_compress, TT_FORK, + &passthrough_setup, (char*)"x-zstd" }, + { "compress/lzma", test_buffers_compress, TT_FORK, + &passthrough_setup, (char*)"x-tor-lzma" }, + { "compress/none", test_buffers_compress, TT_FORK, + &passthrough_setup, (char*)"identity" }, + END_OF_TESTCASES }; diff --git a/src/test/test_cell_formats.c b/src/test/test_cell_formats.c index 22c34b6d6c..007f7e3d3e 100644 --- a/src/test/test_cell_formats.c +++ b/src/test/test_cell_formats.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_cell_queue.c b/src/test/test_cell_queue.c index 93ac9854d8..69e89b69b0 100644 --- a/src/test/test_cell_queue.c +++ b/src/test/test_cell_queue.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CIRCUITLIST_PRIVATE diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 862bd6dfa6..f5999b8e67 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define TOR_CHANNEL_INTERNAL_ diff --git a/src/test/test_channelpadding.c b/src/test/test_channelpadding.c new file mode 100644 index 0000000000..d54c9cc52c --- /dev/null +++ b/src/test/test_channelpadding.c @@ -0,0 +1,1126 @@ +#define TOR_CHANNEL_INTERNAL_ +#define MAIN_PRIVATE +#define NETWORKSTATUS_PRIVATE +#define TOR_TIMERS_PRIVATE +#include "or.h" +#include "test.h" +#include "testsupport.h" +#include "connection.h" +#include "connection_or.h" +#include "channel.h" +#include "channeltls.h" +#include "channelpadding.h" +#include "compat_libevent.h" +#include "config.h" +#include <event2/event.h> +#include "compat_time.h" +#include "main.h" +#include "networkstatus.h" +#include "log_test_helpers.h" + +int channelpadding_get_netflow_inactive_timeout_ms(channel_t *chan); +int64_t channelpadding_compute_time_until_pad_for_netflow(channel_t *chan); +int channelpadding_send_disable_command(channel_t*); +int channelpadding_find_timerslot(channel_t *chan); + +void test_channelpadding_timers(void *arg); +void test_channelpadding_consensus(void *arg); +void test_channelpadding_negotiation(void *arg); +void test_channelpadding_decide_to_pad_channel(void *arg); +void test_channelpadding_killonehop(void *arg); + +void dummy_nop_timer(void); + +#define NSEC_PER_MSEC (1000*1000) + +/* Thing to cast to fake tor_tls_t * to appease assert_connection_ok() */ +static int fake_tortls = 0; /* Bleh... */ + +static int dont_stop_libevent = 0; + +// From test_channel.c +channel_t * new_fake_channel(void); +void free_fake_channel(channel_t*); + +static int +mock_channel_has_queued_writes(channel_t *chan) +{ + (void)chan; + return 0; +} + +static int tried_to_write_cell = 0; + +static channel_t *relay1_relay2; +static channel_t *relay2_relay1; +static channel_t *relay3_client; +static channel_t *client_relay3; + +static int +mock_channel_write_cell_relay2(channel_t *chan, cell_t *cell) +{ + (void)chan; + tried_to_write_cell++; + channel_tls_handle_cell(cell, ((channel_tls_t*)relay1_relay2)->conn); + event_base_loopbreak(tor_libevent_get_base()); + return 0; +} + +static int +mock_channel_write_cell_relay1(channel_t *chan, cell_t *cell) +{ + (void)chan; + tried_to_write_cell++; + channel_tls_handle_cell(cell, ((channel_tls_t*)relay2_relay1)->conn); + event_base_loopbreak(tor_libevent_get_base()); + return 0; +} + +static int +mock_channel_write_cell_relay3(channel_t *chan, cell_t *cell) +{ + (void)chan; + tried_to_write_cell++; + channel_tls_handle_cell(cell, ((channel_tls_t*)client_relay3)->conn); + event_base_loopbreak(tor_libevent_get_base()); + return 0; +} + +static int +mock_channel_write_cell_client(channel_t *chan, cell_t *cell) +{ + (void)chan; + tried_to_write_cell++; + channel_tls_handle_cell(cell, ((channel_tls_t*)relay3_client)->conn); + event_base_loopbreak(tor_libevent_get_base()); + return 0; +} + +static int +mock_channel_write_cell(channel_t *chan, cell_t *cell) +{ + tried_to_write_cell++; + channel_tls_handle_cell(cell, ((channel_tls_t*)chan)->conn); + if (!dont_stop_libevent) + event_base_loopbreak(tor_libevent_get_base()); + return 0; +} + +static void +setup_fake_connection_for_channel(channel_tls_t *chan) +{ + or_connection_t *conn = (or_connection_t*)connection_new(CONN_TYPE_OR, + AF_INET); + + conn->base_.conn_array_index = smartlist_len(connection_array); + smartlist_add(connection_array, conn); + + conn->chan = chan; + chan->conn = conn; + + conn->base_.magic = OR_CONNECTION_MAGIC; + conn->base_.state = OR_CONN_STATE_OPEN; + conn->base_.type = CONN_TYPE_OR; + conn->base_.socket_family = AF_INET; + conn->base_.address = tor_strdup("<fake>"); + + conn->base_.port = 4242; + + conn->tls = (tor_tls_t *)((void *)(&fake_tortls)); + + conn->link_proto = MIN_LINK_PROTO_FOR_CHANNEL_PADDING; + + connection_or_set_canonical(conn, 1); +} + +static channel_tls_t * +new_fake_channeltls(uint8_t id) +{ + channel_tls_t *chan = tor_realloc(new_fake_channel(), sizeof(channel_tls_t)); + chan->base_.magic = TLS_CHAN_MAGIC; + setup_fake_connection_for_channel(chan); + chan->base_.channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; + chan->base_.has_queued_writes = mock_channel_has_queued_writes; + chan->base_.write_cell = mock_channel_write_cell; + chan->base_.padding_enabled = 1; + + chan->base_.identity_digest[0] = id; + channel_register(&chan->base_); + + return chan; +} + +static void +free_fake_channeltls(channel_tls_t *chan) +{ + channel_unregister(&chan->base_); + + tor_free(((channel_tls_t*)chan)->conn->base_.address); + buf_free(((channel_tls_t*)chan)->conn->base_.inbuf); + buf_free(((channel_tls_t*)chan)->conn->base_.outbuf); + tor_free(((channel_tls_t*)chan)->conn); + + timer_free(chan->base_.padding_timer); + channel_handle_free(chan->base_.timer_handle); + channel_handles_clear(&chan->base_); + + free_fake_channel(&chan->base_); + + return; +} + +static void +setup_mock_consensus(void) +{ + current_md_consensus = current_ns_consensus + = tor_malloc_zero(sizeof(networkstatus_t)); + current_md_consensus->net_params = smartlist_new(); + current_md_consensus->routerstatus_list = smartlist_new(); + channelpadding_new_consensus_params(current_md_consensus); +} + +static void +free_mock_consensus(void) +{ + SMARTLIST_FOREACH(current_md_consensus->routerstatus_list, void *, r, + tor_free(r)); + smartlist_free(current_md_consensus->routerstatus_list); + smartlist_free(current_ns_consensus->net_params); + tor_free(current_ns_consensus); +} + +static void +setup_mock_network(void) +{ + routerstatus_t *relay; + connection_array = smartlist_new(); + + relay1_relay2 = (channel_t*)new_fake_channeltls(2); + relay1_relay2->write_cell = mock_channel_write_cell_relay1; + channel_timestamp_active(relay1_relay2); + relay = tor_malloc_zero(sizeof(routerstatus_t)); + relay->identity_digest[0] = 1; + smartlist_add(current_md_consensus->routerstatus_list, relay); + + relay2_relay1 = (channel_t*)new_fake_channeltls(1); + relay2_relay1->write_cell = mock_channel_write_cell_relay2; + channel_timestamp_active(relay2_relay1); + relay = tor_malloc_zero(sizeof(routerstatus_t)); + relay->identity_digest[0] = 2; + smartlist_add(current_md_consensus->routerstatus_list, relay); + + relay3_client = (channel_t*)new_fake_channeltls(0); + relay3_client->write_cell = mock_channel_write_cell_relay3; + relay3_client->is_client = 1; + channel_timestamp_active(relay3_client); + relay = tor_malloc_zero(sizeof(routerstatus_t)); + relay->identity_digest[0] = 3; + smartlist_add(current_md_consensus->routerstatus_list, relay); + + client_relay3 = (channel_t*)new_fake_channeltls(3); + client_relay3->write_cell = mock_channel_write_cell_client; + channel_timestamp_active(client_relay3); + + channel_do_open_actions(relay1_relay2); + channel_do_open_actions(relay2_relay1); + channel_do_open_actions(relay3_client); + channel_do_open_actions(client_relay3); +} + +static void +free_mock_network(void) +{ + free_fake_channeltls((channel_tls_t*)relay1_relay2); + free_fake_channeltls((channel_tls_t*)relay2_relay1); + free_fake_channeltls((channel_tls_t*)relay3_client); + free_fake_channeltls((channel_tls_t*)client_relay3); + + smartlist_free(connection_array); +} + +static void +dummy_timer_cb(tor_timer_t *t, void *arg, const monotime_t *now_mono) +{ + (void)t; (void)arg; (void)now_mono; + event_base_loopbreak(tor_libevent_get_base()); + return; +} + +// This hack adds a dummy timer so that the libevent base loop +// actually returns when we don't expect any timers to fire. Otherwise, +// the global_timer_event gets scheduled an hour from now, and the +// base loop never returns. +void +dummy_nop_timer(void) +{ + tor_timer_t *dummy_timer = timer_new(dummy_timer_cb, NULL); + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + timer_schedule(dummy_timer, &timeout); + + event_base_loop(tor_libevent_get_base(), 0); + timer_free(dummy_timer); +} + +#define CHANNELPADDING_MAX_TIMERS 25 +#define CHANNELS_TO_TEST (CHANNELPADDING_MAX_TIMERS*4) +/** + * Tests to ensure that we handle more than the max number of pending + * timers properly. + */ +void +test_channelpadding_timers(void *arg) +{ + channelpadding_decision_t decision; + channel_t *chans[CHANNELS_TO_TEST]; + int64_t new_time; + (void)arg; + + tor_libevent_postfork(); + + connection_array = smartlist_new(); + + monotime_init(); + monotime_enable_test_mocking(); + monotime_set_mock_time_nsec(1); + monotime_coarse_set_mock_time_nsec(1); + + timers_initialize(); + channelpadding_new_consensus_params(NULL); + + for (int i = 0; i < CHANNELS_TO_TEST; i++) { + chans[i] = (channel_t*)new_fake_channeltls(0); + channel_timestamp_active(chans[i]); + } + + for (int j = 0; j < 2; j++) { + tried_to_write_cell = 0; + int i = 0; + + /* This loop fills our timerslot array with timers of increasing time + * until they fire */ + for (; i < CHANNELPADDING_MAX_TIMERS; i++) { + chans[i]->next_padding_time_ms = monotime_coarse_absolute_msec() + + 10 + i*4; + decision = channelpadding_decide_to_pad_channel(chans[i]); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chans[i]->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + } + + /* This loop should add timers to the first position in the timerslot + * array, since its timeout is before all other timers. */ + for (; i < CHANNELS_TO_TEST/3; i++) { + chans[i]->next_padding_time_ms = monotime_coarse_absolute_msec() + 1; + decision = channelpadding_decide_to_pad_channel(chans[i]); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chans[i]->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + } + + /* This loop should add timers to our existing lists in a weak + * pseudorandom pattern. It ensures that the lists can grow with multiple + * timers in them. */ + for (; i < CHANNELS_TO_TEST/2; i++) { + chans[i]->next_padding_time_ms = monotime_coarse_absolute_msec() + 10 + + i*3 % CHANNELPADDING_MAX_TIMERS; + decision = channelpadding_decide_to_pad_channel(chans[i]); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chans[i]->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + } + + /* This loop should add timers to the last position in the timerslot + * array, since its timeout is after all other timers. */ + for (; i < CHANNELS_TO_TEST; i++) { + chans[i]->next_padding_time_ms = monotime_coarse_absolute_msec() + 500 + + i % CHANNELPADDING_MAX_TIMERS; + decision = channelpadding_decide_to_pad_channel(chans[i]); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chans[i]->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + } + + // Wait for the timers and then kill the event loop. + new_time = (monotime_coarse_absolute_msec()+1001)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + + tt_int_op(tried_to_write_cell, OP_EQ, CHANNELS_TO_TEST); + + // Test that we have no pending callbacks and all empty slots now + for (i = 0; i < CHANNELS_TO_TEST; i++) { + tt_assert(!chans[i]->pending_padding_callback); + } + } + + done: + for (int i = 0; i < CHANNELS_TO_TEST; i++) { + free_fake_channeltls((channel_tls_t*)chans[i]); + } + smartlist_free(connection_array); + + timers_shutdown(); + monotime_disable_test_mocking(); + channel_free_all(); + + return; +} + +void +test_channelpadding_killonehop(void *arg) +{ + channelpadding_decision_t decision; + int64_t new_time; + (void)arg; + tor_libevent_postfork(); + + routerstatus_t *relay = tor_malloc_zero(sizeof(routerstatus_t)); + monotime_init(); + monotime_enable_test_mocking(); + monotime_set_mock_time_nsec(1); + monotime_coarse_set_mock_time_nsec(1); + + timers_initialize(); + setup_mock_consensus(); + setup_mock_network(); + + /* Do we disable padding if tor2webmode or rsos are enabled, and + * the consensus says don't pad? */ + + /* Ensure we can kill tor2web and rsos padding if we want. */ + // First, test that padding works if either is enabled + smartlist_clear(current_md_consensus->net_params); + channelpadding_new_consensus_params(current_md_consensus); + + tried_to_write_cell = 0; + get_options_mutable()->Tor2webMode = 1; + client_relay3->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(client_relay3->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_ALREADY_SCHEDULED); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!client_relay3->pending_padding_callback); + + // Then test disabling each via consensus param + smartlist_add(current_md_consensus->net_params, + (void*)"nf_pad_tor2web=0"); + channelpadding_new_consensus_params(current_md_consensus); + + // Before the client tries to pad, the relay will still pad: + tried_to_write_cell = 0; + relay3_client->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + get_options_mutable()->ORPort_set = 1; + get_options_mutable()->Tor2webMode = 0; + decision = channelpadding_decide_to_pad_channel(relay3_client); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(relay3_client->pending_padding_callback); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!client_relay3->pending_padding_callback); + + // Test client side (it should stop immediately, but send a negotiate) + tried_to_write_cell = 0; + tt_assert(relay3_client->padding_enabled); + tt_assert(client_relay3->padding_enabled); + get_options_mutable()->Tor2webMode = 1; + /* For the relay to recieve the negotiate: */ + get_options_mutable()->ORPort_set = 1; + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!client_relay3->pending_padding_callback); + tt_assert(!relay3_client->padding_enabled); + + // Test relay side (it should have gotten the negotiation to disable) + get_options_mutable()->ORPort_set = 1; + get_options_mutable()->Tor2webMode = 0; + tt_int_op(channelpadding_decide_to_pad_channel(relay3_client), OP_EQ, + CHANNELPADDING_WONTPAD); + tt_assert(!relay3_client->padding_enabled); + + /* Repeat for SOS */ + // First, test that padding works if either is enabled + smartlist_clear(current_md_consensus->net_params); + channelpadding_new_consensus_params(current_md_consensus); + + relay3_client->padding_enabled = 1; + client_relay3->padding_enabled = 1; + + tried_to_write_cell = 0; + get_options_mutable()->ORPort_set = 0; + get_options_mutable()->HiddenServiceSingleHopMode = 1; + get_options_mutable()->HiddenServiceNonAnonymousMode = 1; + client_relay3->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(client_relay3->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_ALREADY_SCHEDULED); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!client_relay3->pending_padding_callback); + + // Then test disabling each via consensus param + smartlist_add(current_md_consensus->net_params, + (void*)"nf_pad_single_onion=0"); + channelpadding_new_consensus_params(current_md_consensus); + + // Before the client tries to pad, the relay will still pad: + tried_to_write_cell = 0; + relay3_client->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + get_options_mutable()->ORPort_set = 1; + get_options_mutable()->HiddenServiceSingleHopMode = 0; + get_options_mutable()->HiddenServiceNonAnonymousMode = 0; + decision = channelpadding_decide_to_pad_channel(relay3_client); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(relay3_client->pending_padding_callback); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!client_relay3->pending_padding_callback); + + // Test client side (it should stop immediately) + get_options_mutable()->HiddenServiceSingleHopMode = 1; + get_options_mutable()->HiddenServiceNonAnonymousMode = 1; + /* For the relay to recieve the negotiate: */ + get_options_mutable()->ORPort_set = 1; + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_assert(!client_relay3->pending_padding_callback); + + // Test relay side (it should have gotten the negotiation to disable) + get_options_mutable()->ORPort_set = 1; + get_options_mutable()->HiddenServiceSingleHopMode = 0; + get_options_mutable()->HiddenServiceNonAnonymousMode = 0; + tt_int_op(channelpadding_decide_to_pad_channel(relay3_client), OP_EQ, + CHANNELPADDING_WONTPAD); + tt_assert(!relay3_client->padding_enabled); + + done: + free_mock_consensus(); + free_mock_network(); + tor_free(relay); + + timers_shutdown(); + monotime_disable_test_mocking(); + channel_free_all(); +} + +void +test_channelpadding_consensus(void *arg) +{ + channelpadding_decision_t decision; + or_options_t *options = get_options_mutable(); + int64_t val; + int64_t new_time; + (void)arg; + + tor_libevent_postfork(); + + /* + * Params tested: + * nf_pad_before_usage + * nf_pad_relays + * nf_ito_low + * nf_ito_high + * + * Plan: + * 1. Padding can be completely disabled via consensus + * 2. Negotiation can't re-enable consensus-disabled padding + * 3. Negotiation can't increase padding from relays beyond + * consensus defaults + * 4. Relay-to-relay padding can be enabled/disabled in consensus + * 5. Can enable/disable padding before actually using a connection + * 6. Can we control circ and TLS conn lifetime from the consensus? + */ + channel_t *chan; + routerstatus_t *relay = tor_malloc_zero(sizeof(routerstatus_t)); + monotime_enable_test_mocking(); + monotime_set_mock_time_nsec(1); + monotime_coarse_set_mock_time_nsec(1); + timers_initialize(); + + connection_array = smartlist_new(); + chan = (channel_t*)new_fake_channeltls(0); + channel_timestamp_active(chan); + + setup_mock_consensus(); + + get_options_mutable()->ORPort_set = 1; + + /* Test 1: Padding can be completely disabled via consensus */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chan->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_ALREADY_SCHEDULED); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!chan->pending_padding_callback); + + smartlist_add(current_md_consensus->net_params, + (void*)"nf_ito_low=0"); + smartlist_add(current_md_consensus->net_params, + (void*)"nf_ito_high=0"); + get_options_mutable()->ConnectionPadding = 1; + channelpadding_new_consensus_params(current_md_consensus); + + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_assert(!chan->pending_padding_callback); + val = channelpadding_get_netflow_inactive_timeout_ms(chan); + tt_i64_op(val, OP_EQ, 0); + val = channelpadding_compute_time_until_pad_for_netflow(chan); + tt_i64_op(val, OP_EQ, -2); + + /* Test 2: Negotiation can't re-enable consensus-disabled padding */ + channelpadding_send_enable_command(chan, 100, 200); + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_assert(!chan->pending_padding_callback); + val = channelpadding_get_netflow_inactive_timeout_ms(chan); + tt_i64_op(val, OP_EQ, 0); + val = channelpadding_compute_time_until_pad_for_netflow(chan); + tt_i64_op(val, OP_EQ, -2); + tt_assert(!chan->next_padding_time_ms); + + smartlist_clear(current_md_consensus->net_params); + + /* Test 3: Negotiation can't increase padding from relays beyond consensus + * values */ + smartlist_add(current_md_consensus->net_params, + (void*)"nf_ito_low=100"); + smartlist_add(current_md_consensus->net_params, + (void*)"nf_ito_high=200"); + channelpadding_new_consensus_params(current_md_consensus); + + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chan->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + val = channelpadding_get_netflow_inactive_timeout_ms(chan); + tt_i64_op(val, OP_GE, 100); + tt_i64_op(val, OP_LE, 200); + val = channelpadding_compute_time_until_pad_for_netflow(chan); + tt_i64_op(val, OP_LE, 200); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+201)*NSEC_PER_MSEC; + monotime_set_mock_time_nsec(new_time); + monotime_coarse_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!chan->pending_padding_callback); + + smartlist_clear(current_md_consensus->net_params); + smartlist_add(current_md_consensus->net_params, + (void*)"nf_ito_low=1500"); + smartlist_add(current_md_consensus->net_params, + (void*)"nf_ito_high=4500"); + channelpadding_new_consensus_params(current_md_consensus); + + channelpadding_send_enable_command(chan, 100, 200); + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); + tt_assert(!chan->pending_padding_callback); + val = channelpadding_get_netflow_inactive_timeout_ms(chan); + tt_i64_op(val, OP_GE, 1500); + tt_i64_op(val, OP_LE, 4500); + val = channelpadding_compute_time_until_pad_for_netflow(chan); + tt_i64_op(val, OP_LE, 4500); + + /* Test 4: Relay-to-relay padding can be enabled/disabled in consensus */ + /* Make this channel a relay's channel */ + memcpy(relay->identity_digest, + ((channel_tls_t *)chan)->conn->identity_digest, DIGEST_LEN); + smartlist_add(current_md_consensus->routerstatus_list, relay); + + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_assert(!chan->pending_padding_callback); + + smartlist_add(current_md_consensus->net_params, + (void*)"nf_pad_relays=1"); + channelpadding_new_consensus_params(current_md_consensus); + + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); + tt_assert(!chan->pending_padding_callback); + val = channelpadding_get_netflow_inactive_timeout_ms(chan); + tt_i64_op(val, OP_GE, 1500); + tt_i64_op(val, OP_LE, 4500); + val = channelpadding_compute_time_until_pad_for_netflow(chan); + tt_i64_op(val, OP_LE, 4500); + + /* Test 5: If we disable padding before channel usage, does that work? */ + smartlist_add(current_md_consensus->net_params, + (void*)"nf_pad_before_usage=0"); + channelpadding_new_consensus_params(current_md_consensus); + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_assert(!chan->pending_padding_callback); + + /* Test 6: Can we control circ and TLS conn lifetime from the consensus? */ + val = channelpadding_get_channel_idle_timeout(NULL, 0); + tt_i64_op(val, OP_GE, 180); + tt_i64_op(val, OP_LE, 180+90); + val = channelpadding_get_channel_idle_timeout(chan, 0); + tt_i64_op(val, OP_GE, 180); + tt_i64_op(val, OP_LE, 180+90); + options->ReducedConnectionPadding = 1; + val = channelpadding_get_channel_idle_timeout(chan, 0); + tt_i64_op(val, OP_GE, 180/2); + tt_i64_op(val, OP_LE, (180+90)/2); + + options->ReducedConnectionPadding = 0; + options->ORPort_set = 1; + smartlist_add(current_md_consensus->net_params, + (void*)"nf_conntimeout_relays=600"); + channelpadding_new_consensus_params(current_md_consensus); + val = channelpadding_get_channel_idle_timeout(chan, 1); + tt_i64_op(val, OP_GE, 450); + tt_i64_op(val, OP_LE, 750); + + val = channelpadding_get_circuits_available_timeout(); + tt_i64_op(val, OP_GE, 30*60); + tt_i64_op(val, OP_LE, 30*60*2); + + options->ReducedConnectionPadding = 1; + smartlist_add(current_md_consensus->net_params, + (void*)"nf_conntimeout_clients=600"); + channelpadding_new_consensus_params(current_md_consensus); + val = channelpadding_get_circuits_available_timeout(); + tt_i64_op(val, OP_GE, 600/2); + tt_i64_op(val, OP_LE, 600*2/2); + + options->ReducedConnectionPadding = 0; + options->CircuitsAvailableTimeout = 24*60*60; + val = channelpadding_get_circuits_available_timeout(); + tt_i64_op(val, OP_GE, 24*60*60); + tt_i64_op(val, OP_LE, 24*60*60*2); + + done: + free_mock_consensus(); + free_fake_channeltls((channel_tls_t*)chan); + smartlist_free(connection_array); + + timers_shutdown(); + monotime_disable_test_mocking(); + channel_free_all(); + + return; +} + +void +test_channelpadding_negotiation(void *arg) +{ + channelpadding_negotiate_t disable; + cell_t cell; + channelpadding_decision_t decision; + int val; + (void)arg; + + /* Plan: + * 1. Clients reject negotiation, relays accept it. + * * Bridges accept negotiation from their clients, + * but not from relays. + * 2. Torrc options can override client-side negotiation + * 3. Test a version issue in channelpadidng cell + * 4. Test channelpadding_reduced_padding + */ + monotime_init(); + monotime_enable_test_mocking(); + monotime_set_mock_time_nsec(1); + monotime_coarse_set_mock_time_nsec(1); + timers_initialize(); + setup_mock_consensus(); + setup_mock_network(); + + /* Test case #1: Do the right things ignore negotiation? */ + /* relay-to-client case: */ + channelpadding_send_disable_command(relay3_client); + tt_assert(client_relay3->padding_enabled); + + /* client-to-relay case: */ + get_options_mutable()->ORPort_set = 1; + channelpadding_disable_padding_on_channel(client_relay3); + tt_int_op(channelpadding_decide_to_pad_channel(relay3_client), OP_EQ, + CHANNELPADDING_WONTPAD); + tt_assert(!relay3_client->padding_enabled); + relay3_client->padding_enabled = 1; + client_relay3->padding_enabled = 1; + + /* Bridge case from relay */ + get_options_mutable()->BridgeRelay = 1; + channelpadding_disable_padding_on_channel(relay2_relay1); + tt_assert(relay1_relay2->padding_enabled); + + /* Bridge case from client */ + channelpadding_disable_padding_on_channel(client_relay3); + tt_assert(!relay3_client->padding_enabled); + tt_int_op(channelpadding_decide_to_pad_channel(relay3_client), OP_EQ, + CHANNELPADDING_WONTPAD); + relay3_client->padding_enabled = 1; + client_relay3->padding_enabled = 1; + get_options_mutable()->BridgeRelay = 0; + get_options_mutable()->ORPort_set = 0; + + /* Test case #2: Torrc options */ + /* ConnectionPadding auto; Relay doesn't suport us */ + ((channel_tls_t*)relay3_client)->conn->link_proto = 4; + relay3_client->padding_enabled = 0; + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(relay3_client); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_assert(!relay3_client->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + ((channel_tls_t*)relay3_client)->conn->link_proto = 5; + relay3_client->padding_enabled = 1; + + /* ConnectionPadding 1; Relay doesn't suport us */ + get_options_mutable()->ConnectionPadding = 1; + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); + tt_assert(!client_relay3->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + get_options_mutable()->ConnectionPadding = 0; + + /* Test case #3: Test a version issue in channelpadding cell */ + get_options_mutable()->ORPort_set = 1; + client_relay3->padding_enabled = 1; + relay3_client->padding_enabled = 1; + memset(&cell, 0, sizeof(cell_t)); + memset(&disable, 0, sizeof(channelpadding_negotiate_t)); + cell.command = CELL_PADDING_NEGOTIATE; + + channelpadding_negotiate_set_command(&disable, CHANNELPADDING_COMMAND_STOP); + disable.version = 1; + channelpadding_negotiate_encode(cell.payload, CELL_PAYLOAD_SIZE, &disable); + client_relay3->write_cell(client_relay3, &cell); + tt_assert(relay3_client->padding_enabled); + tt_int_op(channelpadding_update_padding_for_channel(client_relay3, &disable), + OP_EQ, -1); + tt_assert(client_relay3->padding_enabled); + + disable.version = 0; + channelpadding_negotiate_encode(cell.payload, CELL_PAYLOAD_SIZE, &disable); + client_relay3->write_cell(client_relay3, &cell); + tt_assert(!relay3_client->padding_enabled); + + /* Test case 4: Reducing padding actually reduces it */ + relay3_client->padding_enabled = 1; + client_relay3->padding_enabled = 1; + + decision = channelpadding_decide_to_pad_channel(relay3_client); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); + + channelpadding_reduce_padding_on_channel(client_relay3); + + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(relay3_client); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + + get_options_mutable()->ORPort_set = 0; + decision = channelpadding_decide_to_pad_channel(client_relay3); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); + + tt_assert(!client_relay3->pending_padding_callback); + val = channelpadding_get_netflow_inactive_timeout_ms(client_relay3); + tt_int_op(val, OP_GE, 9000); + tt_int_op(val, OP_LE, 14000); + int64_t val64 = + channelpadding_compute_time_until_pad_for_netflow(client_relay3); + tt_i64_op(val64, OP_LE, 14000); + + done: + free_mock_network(); + free_mock_consensus(); + + timers_shutdown(); + monotime_disable_test_mocking(); + channel_free_all(); + + return; +} + +void +test_channelpadding_decide_to_pad_channel(void *arg) +{ + channelpadding_decision_t decision; + /** + * Test case plan: + * + * 1. Channel that has "sent a packet" before the timeout. + * + We should decide to pad later + * 2. Channel that has not "sent a packet" before the timeout: + * 2a. Not within 1.1s of the timeout. + * + We should decide to pad later + * 2b. Within 1.1s of the timemout. + * + We should schedule padding + * + We should get feedback that we wrote a cell + * 2c. Within 0.1s of the timeout. + * + We should schedule padding + * + We should get feedback that we wrote a cell + * 2d. Channel that asks to pad while timeout is scheduled + * + We should schedule padding + * + We should get feedback that we wrote a cell + * 2e. 0s of the timeout + * + We should send padding immediately + * + We should get feedback that we wrote a cell + * 2f. <0s of the timeout + * + We should send padding immediately + * + We should get feedback that we wrote a cell + * 3. Channel that sends a packet while timeout is scheduled + * + We should not get feedback that we wrote a cell + * 4. Channel that closes while timeout is scheduled + * + We should not get feedback that we wrote a cell + * 5. Make sure the channel still would work if repaired + * + We should be able to schedule padding and resend + * 6. Channel is not used for full circuits + * 7. Channel that disappears while timeout is scheduled + * + We should not send padding + */ + channel_t *chan; + int64_t new_time; + connection_array = smartlist_new(); + (void)arg; + + tor_libevent_postfork(); + + monotime_init(); + monotime_enable_test_mocking(); + monotime_set_mock_time_nsec(1); + monotime_coarse_set_mock_time_nsec(1); + timers_initialize(); + setup_full_capture_of_logs(LOG_WARN); + channelpadding_new_consensus_params(NULL); + + chan = (channel_t*)new_fake_channeltls(0); + channel_timestamp_active(chan); + + /* Test case #1: Channel that has "sent a packet" before the timeout. */ + tried_to_write_cell = 0; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); + tt_assert(!chan->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + /* Test case #2a: > 1.1s until timeout */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 1200; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); + tt_assert(!chan->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + /* Test case #2b: >= 1.0s until timeout */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 1000; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chan->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + // Wait for the timer from case #2b + new_time = (monotime_coarse_absolute_msec() + 1000)*NSEC_PER_MSEC; + monotime_set_mock_time_nsec(new_time); + monotime_coarse_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!chan->pending_padding_callback); + + /* Test case #2c: > 0.1s until timeout */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chan->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + /* Test case #2d: Channel that asks to pad while timeout is scheduled */ + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_ALREADY_SCHEDULED); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!chan->pending_padding_callback); + + /* Test case #2e: 0s until timeout */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec(); + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SENT); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!chan->pending_padding_callback); + + /* Test case #2f: <0s until timeout */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() - 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SENT); + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!chan->pending_padding_callback); + + /* Test case #3: Channel that sends a packet while timeout is scheduled */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + tt_assert(chan->pending_padding_callback); + + // Pretend the channel sent a packet + channel_timestamp_active(chan); + + // We don't expect any timer callbacks here. Make a dummy one to be sure. + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + + tt_int_op(tried_to_write_cell, OP_EQ, 0); + tt_assert(!chan->pending_padding_callback); + + /* Test case #4: Channel that closes while a timeout is scheduled */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + tt_assert(chan->pending_padding_callback); + + // Pretend the channel is temporarily down + chan->state = CHANNEL_STATE_MAINT; + + // We don't expect any timer callbacks here. Make a dummy one to be sure. + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + + tt_int_op(tried_to_write_cell, OP_EQ, 0); + tt_assert(!chan->pending_padding_callback); + chan->state = CHANNEL_STATE_OPEN; + + /* Test case #5: Make sure previous test case didn't break everything */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_assert(chan->pending_padding_callback); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + // Wait for the timer + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + + tt_int_op(tried_to_write_cell, OP_EQ, 1); + tt_assert(!chan->pending_padding_callback); + + /* Test case #6. Channel is not used for full circuits */ + chan->channel_usage = CHANNEL_USED_NOT_USED_FOR_FULL_CIRCS; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); + tt_assert(!chan->pending_padding_callback); + chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; + + /* Test case #7. Channel is closed while timeout is scheduled. + * + * NOTE: This test deliberately breaks the channel callback mechanism. + * It must be last. + */ + tried_to_write_cell = 0; + chan->next_padding_time_ms = monotime_coarse_absolute_msec() + 100; + decision = channelpadding_decide_to_pad_channel(chan); + tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SCHEDULED); + tt_int_op(tried_to_write_cell, OP_EQ, 0); + tt_assert(chan->pending_padding_callback); + + // Close the connection while the timer is scheduled + free_fake_channeltls((channel_tls_t*)chan); + + // We don't expect any timer callbacks here. Make a dummy one to be sure. + new_time = (monotime_coarse_absolute_msec()+101)*NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(new_time); + monotime_set_mock_time_nsec(new_time); + timers_run_pending(); + + tt_int_op(tried_to_write_cell, OP_EQ, 0); + + done: + smartlist_free(connection_array); + + teardown_capture_of_logs(); + monotime_disable_test_mocking(); + timers_shutdown(); + channel_free_all(); + + return; +} + +#define TEST_CHANNELPADDING(name, flags) \ + { #name, test_##name, (flags), NULL, NULL } + +struct testcase_t channelpadding_tests[] = { + //TEST_CHANNELPADDING(channelpadding_decide_to_pad_channel, 0), + TEST_CHANNELPADDING(channelpadding_decide_to_pad_channel, TT_FORK), + TEST_CHANNELPADDING(channelpadding_negotiation, TT_FORK), + TEST_CHANNELPADDING(channelpadding_consensus, TT_FORK), + TEST_CHANNELPADDING(channelpadding_killonehop, TT_FORK), + TEST_CHANNELPADDING(channelpadding_timers, TT_FORK), + END_OF_TESTCASES +}; + diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c index fd98ee40fb..96c5eba9a5 100644 --- a/src/test/test_channeltls.c +++ b/src/test/test_channeltls.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_checkdir.c b/src/test/test_checkdir.c index fbb33f87f6..38f3360b61 100644 --- a/src/test/test_checkdir.c +++ b/src/test/test_checkdir.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_circuitbuild.c b/src/test/test_circuitbuild.c new file mode 100644 index 0000000000..a5282df69d --- /dev/null +++ b/src/test/test_circuitbuild.c @@ -0,0 +1,133 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CIRCUITBUILD_PRIVATE + +#include "or.h" +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" +#include "config.h" +#include "circuitbuild.h" + +/* Dummy nodes smartlist for testing */ +static smartlist_t dummy_nodes; +/* Dummy exit extend_info for testing */ +static extend_info_t dummy_ei; + +static int +mock_count_acceptable_nodes(smartlist_t *nodes) +{ + (void)nodes; + + return DEFAULT_ROUTE_LEN + 1; +} + +/* Test route lengths when the caller of new_route_len() doesn't + * specify exit_ei. */ +static void +test_new_route_len_noexit(void *arg) +{ + int r; + + (void)arg; + MOCK(count_acceptable_nodes, mock_count_acceptable_nodes); + + r = new_route_len(CIRCUIT_PURPOSE_C_GENERAL, NULL, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN, OP_EQ, r); + + r = new_route_len(CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT, NULL, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN, OP_EQ, r); + + r = new_route_len(CIRCUIT_PURPOSE_S_CONNECT_REND, NULL, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN, OP_EQ, r); + + done: + UNMOCK(count_acceptable_nodes); +} + +/* Test route lengths where someone else chose the "exit" node, which + * require an extra hop for safety. */ +static void +test_new_route_len_unsafe_exit(void *arg) +{ + int r; + + (void)arg; + MOCK(count_acceptable_nodes, mock_count_acceptable_nodes); + + /* connecting to hidden service directory */ + r = new_route_len(CIRCUIT_PURPOSE_C_GENERAL, &dummy_ei, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN + 1, OP_EQ, r); + + /* client connecting to introduction point */ + r = new_route_len(CIRCUIT_PURPOSE_C_INTRODUCING, &dummy_ei, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN + 1, OP_EQ, r); + + /* hidden service connecting to rendezvous point */ + r = new_route_len(CIRCUIT_PURPOSE_S_CONNECT_REND, &dummy_ei, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN + 1, OP_EQ, r); + + done: + UNMOCK(count_acceptable_nodes); +} + +/* Test route lengths where we chose the "exit" node, which don't + * require an extra hop for safety. */ +static void +test_new_route_len_safe_exit(void *arg) +{ + int r; + + (void)arg; + MOCK(count_acceptable_nodes, mock_count_acceptable_nodes); + + /* hidden service connecting to introduction point */ + r = new_route_len(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, &dummy_ei, + &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN, OP_EQ, r); + + /* router testing its own reachability */ + r = new_route_len(CIRCUIT_PURPOSE_TESTING, &dummy_ei, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN, OP_EQ, r); + + done: + UNMOCK(count_acceptable_nodes); +} + +/* Make sure a non-fatal assertion fails when new_route_len() gets an + * unexpected circuit purpose. */ +static void +test_new_route_len_unhandled_exit(void *arg) +{ + int r; + + (void)arg; + MOCK(count_acceptable_nodes, mock_count_acceptable_nodes); + + tor_capture_bugs_(1); + setup_full_capture_of_logs(LOG_WARN); + r = new_route_len(CIRCUIT_PURPOSE_CONTROLLER, &dummy_ei, &dummy_nodes); + tt_int_op(DEFAULT_ROUTE_LEN + 1, OP_EQ, r); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(exit_ei && !known_purpose)"); + expect_single_log_msg_containing("Unhandled purpose"); + expect_single_log_msg_containing("with a chosen exit; assuming routelen"); + teardown_capture_of_logs(); + tor_end_capture_bugs_(); + + done: + UNMOCK(count_acceptable_nodes); +} + +struct testcase_t circuitbuild_tests[] = { + { "noexit", test_new_route_len_noexit, 0, NULL, NULL }, + { "safe_exit", test_new_route_len_safe_exit, 0, NULL, NULL }, + { "unsafe_exit", test_new_route_len_unsafe_exit, 0, NULL, NULL }, + { "unhandled_exit", test_new_route_len_unhandled_exit, 0, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_circuitlist.c b/src/test/test_circuitlist.c index 7eed5fe225..344ab27921 100644 --- a/src/test/test_circuitlist.c +++ b/src/test/test_circuitlist.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define TOR_CHANNEL_INTERNAL_ @@ -201,68 +201,68 @@ test_rend_token_maps(void *arg) tt_int_op(tok3[REND_TOKEN_LEN-1], OP_EQ, '.'); /* No maps; nothing there. */ - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok1)); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok1)); - hs_circuitmap_register_rend_circ(c1, tok1); - hs_circuitmap_register_intro_circ_v2(c2, tok2); + hs_circuitmap_register_rend_circ_relay_side(c1, tok1); + hs_circuitmap_register_intro_circ_v2_relay_side(c2, tok2); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok3)); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3)); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok2)); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok3)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok2)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok1)); /* Without purpose set, we don't get the circuits */ - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok1)); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok2)); c1->base_.purpose = CIRCUIT_PURPOSE_REND_POINT_WAITING; c2->base_.purpose = CIRCUIT_PURPOSE_INTRO_POINT; /* Okay, make sure they show up now. */ - tt_ptr_op(c1, OP_EQ, hs_circuitmap_get_rend_circ(tok1)); - tt_ptr_op(c2, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2)); + tt_ptr_op(c1, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok1)); + tt_ptr_op(c2, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok2)); /* Two items at the same place with the same token. */ c3->base_.purpose = CIRCUIT_PURPOSE_REND_POINT_WAITING; - hs_circuitmap_register_rend_circ(c3, tok2); - tt_ptr_op(c2, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2)); - tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_rend_circ(tok2)); + hs_circuitmap_register_rend_circ_relay_side(c3, tok2); + tt_ptr_op(c2, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok2)); + tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok2)); /* Marking a circuit makes it not get returned any more */ circuit_mark_for_close(TO_CIRCUIT(c1), END_CIRC_REASON_FINISHED); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok1)); circuit_free(TO_CIRCUIT(c1)); c1 = NULL; /* Freeing a circuit makes it not get returned any more. */ circuit_free(TO_CIRCUIT(c2)); c2 = NULL; - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok2)); /* c3 -- are you still there? */ - tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_rend_circ(tok2)); + tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok2)); /* Change its cookie. This never happens in Tor per se, but hey. */ c3->base_.purpose = CIRCUIT_PURPOSE_INTRO_POINT; - hs_circuitmap_register_intro_circ_v2(c3, tok3); + hs_circuitmap_register_intro_circ_v2_relay_side(c3, tok3); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok2)); - tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok2)); + tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3)); /* Now replace c3 with c4. */ c4->base_.purpose = CIRCUIT_PURPOSE_INTRO_POINT; - hs_circuitmap_register_intro_circ_v2(c4, tok3); + hs_circuitmap_register_intro_circ_v2_relay_side(c4, tok3); - tt_ptr_op(c4, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3)); + tt_ptr_op(c4, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3)); - tt_ptr_op(c3->hs_token, OP_EQ, NULL); - tt_ptr_op(c4->hs_token, OP_NE, NULL); - tt_mem_op(c4->hs_token->token, OP_EQ, tok3, REND_TOKEN_LEN); + tt_ptr_op(TO_CIRCUIT(c3)->hs_token, OP_EQ, NULL); + tt_ptr_op(TO_CIRCUIT(c4)->hs_token, OP_NE, NULL); + tt_mem_op(TO_CIRCUIT(c4)->hs_token->token, OP_EQ, tok3, REND_TOKEN_LEN); /* Now clear c4's cookie. */ - hs_circuitmap_remove_circuit(c4); - tt_ptr_op(c4->hs_token, OP_EQ, NULL); - tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3)); + hs_circuitmap_remove_circuit(TO_CIRCUIT(c4)); + tt_ptr_op(TO_CIRCUIT(c4)->hs_token, OP_EQ, NULL); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3)); done: if (c1) @@ -370,10 +370,89 @@ test_pick_circid(void *arg) UNMOCK(channel_dump_statistics); } +/** Test that the circuit pools of our HS circuitmap are isolated based on + * their token type. */ +static void +test_hs_circuitmap_isolation(void *arg) +{ + or_circuit_t *circ1 = NULL; + origin_circuit_t *circ2 = NULL; + or_circuit_t *circ3 = NULL; + origin_circuit_t *circ4 = NULL; + + (void)arg; + + hs_circuitmap_init(); + + { + const uint8_t tok1[REND_TOKEN_LEN] = "bet i got some of th"; + + circ1 = or_circuit_new(0, NULL); + tt_assert(circ1); + circ1->base_.purpose = CIRCUIT_PURPOSE_REND_POINT_WAITING; + + /* check that circuitmap is empty right? */ + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok1)); + + /* Register circ1 with tok1 as relay-side rend circ */ + hs_circuitmap_register_rend_circ_relay_side(circ1, tok1); + + /* check that service-side getters don't work */ + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_service_side(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_service_side(tok1)); + + /* Check that the right getter works. */ + tt_ptr_op(circ1, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok1)); + } + + { + const uint8_t tok2[REND_TOKEN_LEN] = "you dont know anythi"; + + circ2 = origin_circuit_new(); + tt_assert(circ2); + circ2->base_.purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO; + circ3 = or_circuit_new(0, NULL); + tt_assert(circ3); + circ3->base_.purpose = CIRCUIT_PURPOSE_INTRO_POINT; + circ4 = origin_circuit_new(); + tt_assert(circ4); + circ4->base_.purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO; + + /* Register circ2 with tok2 as service-side intro v2 circ */ + hs_circuitmap_register_intro_circ_v2_service_side(circ2, tok2); + /* Register circ3 with tok2 again but for different purpose */ + hs_circuitmap_register_intro_circ_v2_relay_side(circ3, tok2); + + /* Check that the getters work */ + tt_ptr_op(circ2, OP_EQ, + hs_circuitmap_get_intro_circ_v2_service_side(tok2)); + tt_ptr_op(circ3, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok2)); + + /* Register circ4 with tok2: it should override circ2 */ + hs_circuitmap_register_intro_circ_v2_service_side(circ4, tok2); + + /* check that relay-side getters don't work */ + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_relay_side(tok2)); + + /* Check that the getter returns circ4; the last circuit registered with + * that token. */ + tt_ptr_op(circ4, OP_EQ, + hs_circuitmap_get_intro_circ_v2_service_side(tok2)); + } + + done: + circuit_free(TO_CIRCUIT(circ1)); + circuit_free(TO_CIRCUIT(circ2)); + circuit_free(TO_CIRCUIT(circ3)); + circuit_free(TO_CIRCUIT(circ4)); +} + struct testcase_t circuitlist_tests[] = { { "maps", test_clist_maps, TT_FORK, NULL, NULL }, { "rend_token_maps", test_rend_token_maps, TT_FORK, NULL, NULL }, { "pick_circid", test_pick_circid, TT_FORK, NULL, NULL }, + { "hs_circuitmap_isolation", test_hs_circuitmap_isolation, + TT_FORK, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c index 1ffa17247d..99abdbc685 100644 --- a/src/test/test_circuitmux.c +++ b/src/test/test_circuitmux.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define TOR_CHANNEL_INTERNAL_ diff --git a/src/test/test_circuituse.c b/src/test/test_circuituse.c index 27a87660ff..5cc9fe571e 100644 --- a/src/test/test_circuituse.c +++ b/src/test/test_circuituse.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CIRCUITLIST_PRIVATE diff --git a/src/test/test_compat_libevent.c b/src/test/test_compat_libevent.c index 0443cc0b1c..7dd8e65194 100644 --- a/src/test/test_compat_libevent.c +++ b/src/test/test_compat_libevent.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define COMPAT_LIBEVENT_PRIVATE diff --git a/src/test/test_config.c b/src/test/test_config.c index 3c2b52ca00..beaab00f73 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -854,9 +854,23 @@ static void test_config_fix_my_family(void *arg) { char *err = NULL; - const char *family = "$1111111111111111111111111111111111111111, " - "1111111111111111111111111111111111111112, " - "$1111111111111111111111111111111111111113"; + config_line_t *family = tor_malloc_zero(sizeof(config_line_t)); + family->key = tor_strdup("MyFamily"); + family->value = tor_strdup("$1111111111111111111111111111111111111111, " + "1111111111111111111111111111111111111112, " + "$1111111111111111111111111111111111111113"); + + config_line_t *family2 = tor_malloc_zero(sizeof(config_line_t)); + family2->key = tor_strdup("MyFamily"); + family2->value = tor_strdup("1111111111111111111111111111111111111114"); + + config_line_t *family3 = tor_malloc_zero(sizeof(config_line_t)); + family3->key = tor_strdup("MyFamily"); + family3->value = tor_strdup("$1111111111111111111111111111111111111115"); + + family->next = family2; + family2->next = family3; + family3->next = NULL; or_options_t* options = options_new(); or_options_t* defaults = options_new(); @@ -864,7 +878,7 @@ test_config_fix_my_family(void *arg) options_init(options); options_init(defaults); - options->MyFamily = tor_strdup(family); + options->MyFamily_lines = family; options_validate(NULL, options, defaults, 0, &err) ; @@ -872,18 +886,23 @@ test_config_fix_my_family(void *arg) TT_FAIL(("options_validate failed: %s", err)); } - tt_str_op(options->MyFamily,OP_EQ, - "$1111111111111111111111111111111111111111, " - "$1111111111111111111111111111111111111112, " - "$1111111111111111111111111111111111111113"); - - done: - if (err != NULL) { - tor_free(err); - } + const char *valid[] = { "$1111111111111111111111111111111111111111", + "$1111111111111111111111111111111111111112", + "$1111111111111111111111111111111111111113", + "$1111111111111111111111111111111111111114", + "$1111111111111111111111111111111111111115" }; + int ret_size = 0; + config_line_t *ret; + for (ret = options->MyFamily; ret && ret_size < 5; ret = ret->next) { + tt_str_op(ret->value, OP_EQ, valid[ret_size]); + ret_size++; + } + tt_int_op(ret_size, OP_EQ, 5); - or_options_free(options); - or_options_free(defaults); + done: + tor_free(err); + or_options_free(options); + or_options_free(defaults); } static int n_hostname_01010101 = 0; @@ -3864,144 +3883,6 @@ mock_config_line(const char *key, const char *val) } static void -test_config_parse_port_config__listenaddress(void *data) -{ - (void)data; - int ret; - config_line_t *config_listen_address = NULL, *config_listen_address2 = NULL, - *config_listen_address3 = NULL; - config_line_t *config_port1 = NULL, *config_port2 = NULL, - *config_port3 = NULL, *config_port4 = NULL, *config_port5 = NULL; - smartlist_t *slout = NULL; - port_cfg_t *port_cfg = NULL; - - // Test basic invocation with no arguments - ret = parse_port_config(NULL, NULL, NULL, NULL, 0, NULL, 0, 0); - tt_int_op(ret, OP_EQ, 0); - - // Setup some test data - config_listen_address = mock_config_line("DNSListenAddress", "127.0.0.1"); - config_listen_address2 = mock_config_line("DNSListenAddress", "x$$$:::345"); - config_listen_address3 = mock_config_line("DNSListenAddress", - "127.0.0.1:1442"); - config_port1 = mock_config_line("DNSPort", "42"); - config_port2 = mock_config_line("DNSPort", "43"); - config_port1->next = config_port2; - config_port3 = mock_config_line("DNSPort", "auto"); - config_port4 = mock_config_line("DNSPort", "55542"); - config_port5 = mock_config_line("DNSPort", "666777"); - - // Test failure when we have a ListenAddress line and several - // Port lines for the same portname - ret = parse_port_config(NULL, config_port1, config_listen_address, "DNS", 0, - NULL, 0, 0); - - tt_int_op(ret, OP_EQ, -1); - - // Test case when we have a listen address, no default port and allow - // spurious listen address lines - ret = parse_port_config(NULL, NULL, config_listen_address, "DNS", 0, NULL, - 0, CL_PORT_ALLOW_EXTRA_LISTENADDR); - tt_int_op(ret, OP_EQ, 1); - - // Test case when we have a listen address, no default port but doesn't - // allow spurious listen address lines - ret = parse_port_config(NULL, NULL, config_listen_address, "DNS", 0, NULL, - 0, 0); - tt_int_op(ret, OP_EQ, -1); - - // Test case when we have a listen address, and a port that points to auto, - // should use the AUTO port - slout = smartlist_new(); - ret = parse_port_config(slout, config_port3, config_listen_address, "DNS", - 0, NULL, 0, 0); - 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->port, OP_EQ, CFG_AUTO_PORT); - - // Test when we have a listen address and a custom port - ret = parse_port_config(slout, config_port4, config_listen_address, "DNS", - 0, NULL, 0, 0); - tt_int_op(ret, OP_EQ, 0); - tt_int_op(smartlist_len(slout), OP_EQ, 2); - port_cfg = (port_cfg_t *)smartlist_get(slout, 1); - tt_int_op(port_cfg->port, OP_EQ, 55542); - - // Test when we have a listen address and an invalid custom port - ret = parse_port_config(slout, config_port5, config_listen_address, "DNS", - 0, NULL, 0, 0); - tt_int_op(ret, OP_EQ, -1); - - // Test we get a server port configuration when asked for it - ret = parse_port_config(slout, NULL, config_listen_address, "DNS", 0, NULL, - 123, CL_PORT_SERVER_OPTIONS); - tt_int_op(ret, OP_EQ, 0); - tt_int_op(smartlist_len(slout), OP_EQ, 4); - port_cfg = (port_cfg_t *)smartlist_get(slout, 2); - tt_int_op(port_cfg->port, OP_EQ, 123); - tt_int_op(port_cfg->server_cfg.no_listen, OP_EQ, 1); - tt_int_op(port_cfg->server_cfg.bind_ipv4_only, OP_EQ, 1); - - // Test an invalid ListenAddress configuration - ret = parse_port_config(NULL, NULL, config_listen_address2, "DNS", 0, NULL, - 222, 0); - tt_int_op(ret, OP_EQ, -1); - - // Test default to the port in the listen address if available - ret = parse_port_config(slout, config_port2, config_listen_address3, "DNS", - 0, NULL, 0, 0); - tt_int_op(ret, OP_EQ, 0); - tt_int_op(smartlist_len(slout), OP_EQ, 5); - port_cfg = (port_cfg_t *)smartlist_get(slout, 4); - tt_int_op(port_cfg->port, OP_EQ, 1442); - - // Test we work correctly without an out, but with a listen address - // and a port - ret = parse_port_config(NULL, config_port2, config_listen_address, "DNS", - 0, NULL, 0, 0); - tt_int_op(ret, OP_EQ, 0); - - // Test warning nonlocal control - ret = parse_port_config(slout, config_port2, config_listen_address, "DNS", - CONN_TYPE_CONTROL_LISTENER, NULL, 0, - CL_PORT_WARN_NONLOCAL); - tt_int_op(ret, OP_EQ, 0); - - // Test warning nonlocal ext or listener - ret = parse_port_config(slout, config_port2, config_listen_address, "DNS", - CONN_TYPE_EXT_OR_LISTENER, NULL, 0, - CL_PORT_WARN_NONLOCAL); - tt_int_op(ret, OP_EQ, 0); - - // Test warning nonlocal other - SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); - smartlist_clear(slout); - ret = parse_port_config(slout, config_port2, config_listen_address, "DNS", - 0, NULL, 0, CL_PORT_WARN_NONLOCAL); - tt_int_op(ret, OP_EQ, 0); - - // Test warning nonlocal control without an out - ret = parse_port_config(NULL, config_port2, config_listen_address, "DNS", - CONN_TYPE_CONTROL_LISTENER, NULL, 0, - CL_PORT_WARN_NONLOCAL); - tt_int_op(ret, OP_EQ, 0); - - done: - config_free_lines(config_listen_address); - config_free_lines(config_listen_address2); - config_free_lines(config_listen_address3); - config_free_lines(config_port1); - /* 2 was linked from 1. */ - config_free_lines(config_port3); - config_free_lines(config_port4); - config_free_lines(config_port5); - if (slout) - SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); - smartlist_free(slout); -} - -static void test_config_parse_port_config__ports__no_ports_given(void *data) { (void)data; @@ -4012,40 +3893,40 @@ test_config_parse_port_config__ports__no_ports_given(void *data) slout = smartlist_new(); // Test no defaultport, no defaultaddress and no out - ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, NULL, 0, 0); + ret = parse_port_config(NULL, NULL, "DNS", 0, NULL, 0, 0); tt_int_op(ret, OP_EQ, 0); // Test with defaultport, no defaultaddress and no out - ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, NULL, 42, 0); + ret = parse_port_config(NULL, NULL, "DNS", 0, NULL, 42, 0); tt_int_op(ret, OP_EQ, 0); // Test no defaultport, with defaultaddress and no out - ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, "127.0.0.2", 0, 0); + ret = parse_port_config(NULL, NULL, "DNS", 0, "127.0.0.2", 0, 0); tt_int_op(ret, OP_EQ, 0); // Test with defaultport, with defaultaddress and no out - ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, "127.0.0.2", 42, 0); + ret = parse_port_config(NULL, NULL, "DNS", 0, "127.0.0.2", 42, 0); tt_int_op(ret, OP_EQ, 0); // Test no defaultport, no defaultaddress and with out - ret = parse_port_config(slout, NULL, NULL, "DNS", 0, NULL, 0, 0); + ret = parse_port_config(slout, NULL, "DNS", 0, NULL, 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 0); // Test with defaultport, no defaultaddress and with out - ret = parse_port_config(slout, NULL, NULL, "DNS", 0, NULL, 42, 0); + ret = parse_port_config(slout, NULL, "DNS", 0, NULL, 42, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 0); // Test no defaultport, with defaultaddress and with out - ret = parse_port_config(slout, NULL, NULL, "DNS", 0, "127.0.0.2", 0, 0); + ret = parse_port_config(slout, NULL, "DNS", 0, "127.0.0.2", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 0); // Test with defaultport, with defaultaddress and out, adds a new port cfg SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); - ret = parse_port_config(slout, NULL, NULL, "DNS", 0, "127.0.0.2", 42, 0); + ret = parse_port_config(slout, NULL, "DNS", 0, "127.0.0.2", 42, 0); 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); @@ -4056,7 +3937,7 @@ test_config_parse_port_config__ports__no_ports_given(void *data) // for a unix address SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); - ret = parse_port_config(slout, NULL, NULL, "DNS", 0, "/foo/bar/unixdomain", + ret = parse_port_config(slout, NULL, "DNS", 0, "/foo/bar/unixdomain", 42, CL_PORT_IS_UNIXSOCKET); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4085,28 +3966,28 @@ test_config_parse_port_config__ports__ports_given(void *data) // Test error when encounters an invalid Port specification config_port_invalid = mock_config_line("DNSPort", ""); - ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, NULL, + ret = parse_port_config(NULL, config_port_invalid, "DNS", 0, NULL, 0, 0); tt_int_op(ret, OP_EQ, -1); // Test error when encounters an empty unix domain specification config_free_lines(config_port_invalid); config_port_invalid = NULL; config_port_invalid = mock_config_line("DNSPort", "unix:"); - ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, NULL, + ret = parse_port_config(NULL, config_port_invalid, "DNS", 0, NULL, 0, 0); tt_int_op(ret, OP_EQ, -1); // Test error when encounters a unix domain specification but the listener // doesn't support domain sockets config_port_valid = mock_config_line("DNSPort", "unix:/tmp/foo/bar"); - ret = parse_port_config(NULL, config_port_valid, NULL, "DNS", + ret = parse_port_config(NULL, config_port_valid, "DNS", CONN_TYPE_AP_DNS_LISTENER, NULL, 0, 0); tt_int_op(ret, OP_EQ, -1); // Test valid unix domain SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, 0); #ifdef _WIN32 tt_int_op(ret, OP_EQ, -1); @@ -4131,7 +4012,7 @@ test_config_parse_port_config__ports__ports_given(void *data) "unix:/tmp/foo/bar NoIPv4Traffic " "NoIPv6Traffic " "NoOnionTraffic"); - ret = parse_port_config(NULL, config_port_invalid, NULL, "SOCKS", + ret = parse_port_config(NULL, config_port_invalid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, -1); @@ -4140,7 +4021,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_free_lines(config_port_invalid); config_port_invalid = NULL; config_port_invalid = mock_config_line("DNSPort", "127.0.0.1:80 NoDNSRequest"); - ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", + ret = parse_port_config(NULL, config_port_invalid, "DNS", CONN_TYPE_AP_DNS_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, -1); @@ -4153,7 +4034,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_port_valid = mock_config_line("DNSPort", "127.0.0.1:80 " "NoIPv6Traffic " "NoIPv4Traffic NoOnionTraffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", + ret = parse_port_config(slout, config_port_valid, "DNS", CONN_TYPE_AP_DNS_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, 0); @@ -4169,7 +4050,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_port_invalid = mock_config_line("SOCKSPort", "NoIPv6Traffic " "unix:/tmp/foo/bar NoIPv4Traffic"); - ret = parse_port_config(NULL, config_port_invalid, NULL, "SOCKS", + ret = parse_port_config(NULL, config_port_invalid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, -1); @@ -4182,7 +4063,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_port_valid = mock_config_line("SOCKSPort", "unix:/tmp/foo/bar " "NoIPv6Traffic " "NoDNSRequest NoIPv4Traffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); #ifdef _WIN32 @@ -4204,7 +4085,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_port_valid = mock_config_line("SOCKSPort", "unix:\"/tmp/foo/ bar\" " "NoIPv6Traffic " "NoDNSRequest NoIPv4Traffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); #ifdef _WIN32 @@ -4226,7 +4107,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_port_valid = mock_config_line("SOCKSPort", "unix:\"/tmp/foo/ bar " "NoIPv6Traffic " "NoDNSRequest NoIPv4Traffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, -1); @@ -4238,7 +4119,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_port_valid = mock_config_line("SOCKSPort", "unix:\"\" " "NoIPv6Traffic " "NoDNSRequest NoIPv4Traffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, -1); @@ -4249,7 +4130,7 @@ test_config_parse_port_config__ports__ports_given(void *data) smartlist_clear(slout); config_port_valid = mock_config_line("SOCKSPort", "unix:/tmp/foo/bar " "OnionTrafficOnly"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); #ifdef _WIN32 @@ -4270,7 +4151,7 @@ test_config_parse_port_config__ports__ports_given(void *data) smartlist_clear(slout); config_port_valid = mock_config_line("SOCKSPort", "unix:/tmp/foo/bar " "NoIPv4Traffic IPv6Traffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); #ifdef _WIN32 @@ -4289,7 +4170,7 @@ test_config_parse_port_config__ports__ports_given(void *data) smartlist_clear(slout); config_port_valid = mock_config_line("SOCKSPort", "unix:/tmp/foo/bar " "IPv4Traffic IPv6Traffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, NULL, 0, CL_PORT_TAKES_HOSTNAMES); #ifdef _WIN32 @@ -4305,28 +4186,28 @@ test_config_parse_port_config__ports__ports_given(void *data) // Test failure if we specify world writable for an IP Port config_free_lines(config_port_invalid); config_port_invalid = NULL; config_port_invalid = mock_config_line("DNSPort", "42 WorldWritable"); - ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(NULL, config_port_invalid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, -1); // Test failure if we specify group writable for an IP Port config_free_lines(config_port_invalid); config_port_invalid = NULL; config_port_invalid = mock_config_line("DNSPort", "42 GroupWritable"); - ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(NULL, config_port_invalid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, -1); // Test failure if we specify group writable for an IP Port config_free_lines(config_port_invalid); config_port_invalid = NULL; config_port_invalid = mock_config_line("DNSPort", "42 RelaxDirModeCheck"); - ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(NULL, config_port_invalid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, -1); // Test success with only a port (this will fail without a default address) config_free_lines(config_port_valid); config_port_valid = NULL; config_port_valid = mock_config_line("DNSPort", "42"); - ret = parse_port_config(NULL, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(NULL, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); @@ -4335,7 +4216,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 IsolateDestPort"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4348,7 +4229,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 NoIsolateDestPorts"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4361,7 +4242,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 IsolateDestAddr"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4374,7 +4255,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 IsolateSOCKSAuth"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4387,7 +4268,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 IsolateClientProtocol"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4400,7 +4281,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 IsolateClientAddr"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4411,7 +4292,7 @@ test_config_parse_port_config__ports__ports_given(void *data) // Test success with ignored unknown options config_free_lines(config_port_valid); config_port_valid = NULL; config_port_valid = mock_config_line("DNSPort", "42 ThisOptionDoesntExist"); - ret = parse_port_config(NULL, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(NULL, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); @@ -4420,7 +4301,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 NoIsolateSOCKSAuth"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.3", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4433,7 +4314,7 @@ test_config_parse_port_config__ports__ports_given(void *data) smartlist_clear(slout); config_port_valid = mock_config_line("SOCKSPort", "42 IPv6Traffic PreferIPv6"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, "127.0.0.42", 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, 0); @@ -4446,7 +4327,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 CacheIPv4DNS"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4459,7 +4340,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 CacheIPv6DNS"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4472,7 +4353,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 NoCacheIPv4DNS"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4485,7 +4366,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 CacheDNS"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, CL_PORT_TAKES_HOSTNAMES); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4498,7 +4379,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 UseIPv4Cache"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4511,7 +4392,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 UseIPv6Cache"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4524,7 +4405,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 UseDNSCache"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4537,7 +4418,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 NoPreferIPv6Automap"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4549,7 +4430,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 PreferSOCKSNoAuth"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4564,14 +4445,14 @@ test_config_parse_port_config__ports__ports_given(void *data) config_port_invalid = mock_config_line("DNSPort", "0"); config_port_valid = mock_config_line("DNSPort", "42"); config_port_invalid->next = config_port_valid; - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, "127.0.0.42", 0, 0); tt_int_op(ret, OP_EQ, -1); // Test success with warn non-local control SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); - ret = parse_port_config(slout, config_port_valid, NULL, "Control", + ret = parse_port_config(slout, config_port_valid, "Control", CONN_TYPE_CONTROL_LISTENER, "127.0.0.42", 0, CL_PORT_WARN_NONLOCAL); tt_int_op(ret, OP_EQ, 0); @@ -4579,7 +4460,7 @@ test_config_parse_port_config__ports__ports_given(void *data) // Test success with warn non-local listener SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); - ret = parse_port_config(slout, config_port_valid, NULL, "ExtOR", + ret = parse_port_config(slout, config_port_valid, "ExtOR", CONN_TYPE_EXT_OR_LISTENER, "127.0.0.42", 0, CL_PORT_WARN_NONLOCAL); tt_int_op(ret, OP_EQ, 0); @@ -4587,12 +4468,12 @@ test_config_parse_port_config__ports__ports_given(void *data) // Test success with warn non-local other SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.42", 0, CL_PORT_WARN_NONLOCAL); tt_int_op(ret, OP_EQ, 0); // Test success with warn non-local other without out - ret = parse_port_config(NULL, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(NULL, config_port_valid, "DNS", 0, "127.0.0.42", 0, CL_PORT_WARN_NONLOCAL); tt_int_op(ret, OP_EQ, 0); @@ -4603,7 +4484,7 @@ test_config_parse_port_config__ports__ports_given(void *data) smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 IPv4Traffic " "IPv6Traffic"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.44", 0, CL_PORT_TAKES_HOSTNAMES | CL_PORT_NO_STREAM_OPTIONS); @@ -4618,7 +4499,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "42 SessionGroup=invalid"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, "127.0.0.44", 0, CL_PORT_NO_STREAM_OPTIONS); tt_int_op(ret, OP_EQ, -1); @@ -4630,7 +4511,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "42 SessionGroup=123"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, "127.0.0.44", 0, 0); tt_int_op(ret, OP_EQ, -1); @@ -4640,7 +4521,7 @@ test_config_parse_port_config__ports__ports_given(void *data) smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "42 SessionGroup=123 " "SessionGroup=321"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, "127.0.0.44", 0, CL_PORT_NO_STREAM_OPTIONS); tt_int_op(ret, OP_EQ, -1); @@ -4649,7 +4530,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "42 SessionGroup=1111122"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.44", 0, CL_PORT_NO_STREAM_OPTIONS); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4661,7 +4542,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "0"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.45", 0, CL_PORT_IS_UNIXSOCKET); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 0); @@ -4671,7 +4552,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "something"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.45", 0, CL_PORT_IS_UNIXSOCKET); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4684,7 +4565,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "auto"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.46", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4698,7 +4579,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "127.0.0.122:auto"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.46", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4711,7 +4592,7 @@ test_config_parse_port_config__ports__ports_given(void *data) config_free_lines(config_port_invalid); config_port_invalid = NULL; config_port_invalid = mock_config_line("DNSPort", "invalidstuff!!:auto"); MOCK(tor_addr_lookup, mock_tor_addr_lookup__fail_on_bad_addrs); - ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(NULL, config_port_invalid, "DNS", 0, "127.0.0.46", 0, 0); UNMOCK(tor_addr_lookup); tt_int_op(ret, OP_EQ, -1); @@ -4721,7 +4602,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "127.0.0.123:656"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, "127.0.0.46", 0, 0); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4735,7 +4616,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "something wrong"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, "127.0.0.46", 0, 0); tt_int_op(ret, OP_EQ, -1); @@ -4744,7 +4625,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "127.0.1.0:123:auto"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, "127.0.0.46", 0, 0); tt_int_op(ret, OP_EQ, -1); @@ -4754,7 +4635,7 @@ test_config_parse_port_config__ports__ports_given(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("SOCKSPort", "unix:/tmp/somewhere"); - ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS", + ret = parse_port_config(slout, config_port_valid, "SOCKS", CONN_TYPE_AP_LISTENER, "127.0.0.46", 0, CL_PORT_DFLT_GROUP_WRITABLE); #ifdef _WIN32 @@ -4789,7 +4670,7 @@ test_config_parse_port_config__ports__server_options(void *data) config_free_lines(config_port_valid); config_port_valid = NULL; config_port_valid = mock_config_line("DNSPort", "127.0.0.124:656 NoAdvertise"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4802,7 +4683,7 @@ test_config_parse_port_config__ports__server_options(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "127.0.0.124:656 NoListen"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4816,7 +4697,7 @@ test_config_parse_port_config__ports__server_options(void *data) smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "127.0.0.124:656 NoListen " "NoAdvertise"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, -1); @@ -4825,7 +4706,7 @@ test_config_parse_port_config__ports__server_options(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "127.0.0.124:656 IPv4Only"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4838,7 +4719,7 @@ test_config_parse_port_config__ports__server_options(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "[::1]:656 IPv6Only"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4852,7 +4733,7 @@ test_config_parse_port_config__ports__server_options(void *data) smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "127.0.0.124:656 IPv6Only " "IPv4Only"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, -1); @@ -4861,7 +4742,7 @@ test_config_parse_port_config__ports__server_options(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_valid = mock_config_line("DNSPort", "127.0.0.124:656 unknown"); - ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0, + ret = parse_port_config(slout, config_port_valid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); @@ -4872,7 +4753,7 @@ test_config_parse_port_config__ports__server_options(void *data) smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "127.0.0.124:656 IPv6Only"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, -1); @@ -4881,7 +4762,7 @@ test_config_parse_port_config__ports__server_options(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_invalid = mock_config_line("DNSPort", "[::1]:656 IPv4Only"); - ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL, + ret = parse_port_config(slout, config_port_invalid, "DNS", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, -1); @@ -4890,7 +4771,7 @@ test_config_parse_port_config__ports__server_options(void *data) SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf)); smartlist_clear(slout); config_port_invalid = mock_config_line("ORPort", "unix:\"\""); - ret = parse_port_config(slout, config_port_invalid, NULL, "ORPort", 0, NULL, + ret = parse_port_config(slout, config_port_invalid, "ORPort", 0, NULL, 0, CL_PORT_SERVER_OPTIONS); tt_int_op(ret, OP_EQ, -1); @@ -4929,6 +4810,553 @@ test_config_parse_log_severity(void *data) tor_free(severity); } +static void +test_config_include_limit(void *data) +{ + (void)data; + + config_line_t *result = NULL; + char *dir = tor_strdup(get_fname("test_include_limit")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char torrc_path[PATH_MAX+1]; + tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir); + char torrc_contents[1000]; + tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s", + torrc_path); + tt_int_op(write_str_to_file(torrc_path, torrc_contents, 0), OP_EQ, 0); + + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL), + OP_EQ, -1); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_does_not_exist(void *data) +{ + (void)data; + + config_line_t *result = NULL; + char *dir = tor_strdup(get_fname("test_include_does_not_exist")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char missing_path[PATH_MAX+1]; + tor_snprintf(missing_path, sizeof(missing_path), "%s"PATH_SEPARATOR"missing", + dir); + char torrc_contents[1000]; + tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s", + missing_path); + + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL), + OP_EQ, -1); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_error_in_included_file(void *data) +{ + (void)data; + config_line_t *result = NULL; + + char *dir = tor_strdup(get_fname("test_error_in_included_file")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char invalid_path[PATH_MAX+1]; + tor_snprintf(invalid_path, sizeof(invalid_path), "%s"PATH_SEPARATOR"invalid", + dir); + tt_int_op(write_str_to_file(invalid_path, "unclosed \"", 0), OP_EQ, 0); + + char torrc_contents[1000]; + tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s", + invalid_path); + + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL), + OP_EQ, -1); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_empty_file_folder(void *data) +{ + (void)data; + config_line_t *result = NULL; + + char *dir = tor_strdup(get_fname("test_include_empty_file_folder")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char folder_path[PATH_MAX+1]; + tor_snprintf(folder_path, sizeof(folder_path), "%s"PATH_SEPARATOR"empty_dir", + dir); +#ifdef _WIN32 + tt_int_op(mkdir(folder_path), OP_EQ, 0); +#else + tt_int_op(mkdir(folder_path, 0700), OP_EQ, 0); +#endif + char file_path[PATH_MAX+1]; + tor_snprintf(file_path, sizeof(file_path), "%s"PATH_SEPARATOR"empty_file", + dir); + tt_int_op(write_str_to_file(file_path, "", 0), OP_EQ, 0); + + char torrc_contents[1000]; + tor_snprintf(torrc_contents, sizeof(torrc_contents), + "%%include %s\n" + "%%include %s\n", + folder_path, file_path); + + int include_used; + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), + OP_EQ, 0); + tt_ptr_op(result, OP_EQ, NULL); + tt_int_op(include_used, OP_EQ, 1); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_recursion_before_after(void *data) +{ + (void)data; + + config_line_t *result = NULL; + char *dir = tor_strdup(get_fname("test_include_recursion_before_after")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char torrc_path[PATH_MAX+1]; + tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir); + + char file_contents[1000]; + const int limit = MAX_INCLUDE_RECURSION_LEVEL; + int i; + // Loop backwards so file_contents has the contents of the first file by the + // end of the loop + for (i = limit; i > 0; i--) { + if (i < limit) { + tor_snprintf(file_contents, sizeof(file_contents), + "Test %d\n" + "%%include %s%d\n" + "Test %d\n", + i, torrc_path, i + 1, 2 * limit - i); + } else { + tor_snprintf(file_contents, sizeof(file_contents), "Test %d\n", i); + } + + if (i > 1) { + char file_path[PATH_MAX+1]; + tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i); + tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0); + } + } + + int include_used; + tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used), + OP_EQ, 0); + tt_ptr_op(result, OP_NE, NULL); + tt_int_op(include_used, OP_EQ, 1); + + int len = 0; + config_line_t *next; + for (next = result; next != NULL; next = next->next) { + char expected[10]; + tor_snprintf(expected, sizeof(expected), "%d", len + 1); + tt_str_op(next->key, OP_EQ, "Test"); + tt_str_op(next->value, OP_EQ, expected); + len++; + } + tt_int_op(len, OP_EQ, 2 * limit - 1); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_recursion_after_only(void *data) +{ + (void)data; + + config_line_t *result = NULL; + char *dir = tor_strdup(get_fname("test_include_recursion_after_only")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char torrc_path[PATH_MAX+1]; + tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir); + + char file_contents[1000]; + const int limit = MAX_INCLUDE_RECURSION_LEVEL; + int i; + // Loop backwards so file_contents has the contents of the first file by the + // end of the loop + for (i = limit; i > 0; i--) { + int n = (i - limit - 1) * -1; + if (i < limit) { + tor_snprintf(file_contents, sizeof(file_contents), + "%%include %s%d\n" + "Test %d\n", + torrc_path, i + 1, n); + } else { + tor_snprintf(file_contents, sizeof(file_contents), "Test %d\n", n); + } + + if (i > 1) { + char file_path[PATH_MAX+1]; + tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i); + tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0); + } + } + + int include_used; + tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used), + OP_EQ, 0); + tt_ptr_op(result, OP_NE, NULL); + tt_int_op(include_used, OP_EQ, 1); + + int len = 0; + config_line_t *next; + for (next = result; next != NULL; next = next->next) { + char expected[10]; + tor_snprintf(expected, sizeof(expected), "%d", len + 1); + tt_str_op(next->key, OP_EQ, "Test"); + tt_str_op(next->value, OP_EQ, expected); + len++; + } + tt_int_op(len, OP_EQ, limit); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_folder_order(void *data) +{ + (void)data; + + config_line_t *result = NULL; + char *dir = tor_strdup(get_fname("test_include_folder_order")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char torrcd[PATH_MAX+1]; + tor_snprintf(torrcd, sizeof(torrcd), "%s"PATH_SEPARATOR"%s", dir, "torrc.d"); + +#ifdef _WIN32 + tt_int_op(mkdir(torrcd), OP_EQ, 0); +#else + tt_int_op(mkdir(torrcd, 0700), OP_EQ, 0); +#endif + + // test that files in subfolders are ignored + char path[PATH_MAX+1]; + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, + "subfolder"); + +#ifdef _WIN32 + tt_int_op(mkdir(path), OP_EQ, 0); +#else + tt_int_op(mkdir(path, 0700), OP_EQ, 0); +#endif + + char path2[PATH_MAX+1]; + tor_snprintf(path2, sizeof(path2), "%s"PATH_SEPARATOR"%s", path, + "01_ignore"); + tt_int_op(write_str_to_file(path2, "ShouldNotSee 1\n", 0), OP_EQ, 0); + + // test that files starting with . are ignored + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, ".dot"); + tt_int_op(write_str_to_file(path, "ShouldNotSee 2\n", 0), OP_EQ, 0); + + // test file order + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "01_1st"); + tt_int_op(write_str_to_file(path, "Test 1\n", 0), OP_EQ, 0); + + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "02_2nd"); + tt_int_op(write_str_to_file(path, "Test 2\n", 0), OP_EQ, 0); + + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "aa_3rd"); + tt_int_op(write_str_to_file(path, "Test 3\n", 0), OP_EQ, 0); + + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "ab_4th"); + tt_int_op(write_str_to_file(path, "Test 4\n", 0), OP_EQ, 0); + + char torrc_contents[1000]; + tor_snprintf(torrc_contents, sizeof(torrc_contents), + "%%include %s\n", + torrcd); + + int include_used; + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), + OP_EQ, 0); + tt_ptr_op(result, OP_NE, NULL); + tt_int_op(include_used, OP_EQ, 1); + + int len = 0; + config_line_t *next; + for (next = result; next != NULL; next = next->next) { + char expected[10]; + tor_snprintf(expected, sizeof(expected), "%d", len + 1); + tt_str_op(next->key, OP_EQ, "Test"); + tt_str_op(next->value, OP_EQ, expected); + len++; + } + tt_int_op(len, OP_EQ, 4); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_path_syntax(void *data) +{ + (void)data; + + config_line_t *result = NULL; + char *dir = tor_strdup(get_fname("test_include_path_syntax")); + char *esc_dir = NULL, *dir_with_pathsep = NULL, + *esc_dir_with_pathsep = NULL, *torrc_contents = NULL; + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + esc_dir = esc_for_log(dir); + tor_asprintf(&dir_with_pathsep, "%s%s", dir, PATH_SEPARATOR); + esc_dir_with_pathsep = esc_for_log(dir_with_pathsep); + + tor_asprintf(&torrc_contents, + "%%include %s\n" + "%%include %s%s \n" // space to avoid suppressing newline + "%%include %s\n", + esc_dir, + dir, PATH_SEPARATOR, + esc_dir_with_pathsep); + + int include_used; + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), + OP_EQ, 0); + tt_ptr_op(result, OP_EQ, NULL); + tt_int_op(include_used, OP_EQ, 1); + + done: + config_free_lines(result); + tor_free(dir); + tor_free(torrc_contents); + tor_free(esc_dir); + tor_free(dir_with_pathsep); + tor_free(esc_dir_with_pathsep); +} + +static void +test_config_include_not_processed(void *data) +{ + (void)data; + + char torrc_contents[1000] = "%include does_not_exist\n"; + config_line_t *result = NULL; + tt_int_op(config_get_lines(torrc_contents, &result, 0),OP_EQ, 0); + tt_ptr_op(result, OP_NE, NULL); + + int len = 0; + config_line_t *next; + for (next = result; next != NULL; next = next->next) { + tt_str_op(next->key, OP_EQ, "%include"); + tt_str_op(next->value, OP_EQ, "does_not_exist"); + len++; + } + tt_int_op(len, OP_EQ, 1); + + done: + config_free_lines(result); +} + +static void +test_config_include_has_include(void *data) +{ + (void)data; + + config_line_t *result = NULL; + char *dir = tor_strdup(get_fname("test_include_has_include")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char torrc_contents[1000] = "Test 1\n"; + int include_used; + + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), + OP_EQ, 0); + tt_int_op(include_used, OP_EQ, 0); + config_free_lines(result); + + tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s\n", dir); + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), + OP_EQ, 0); + tt_int_op(include_used, OP_EQ, 1); + + done: + config_free_lines(result); + tor_free(dir); +} + +static void +test_config_include_flag_both_without(void *data) +{ + (void)data; + + char *errmsg = NULL; + char conf_empty[1000]; + tor_snprintf(conf_empty, sizeof(conf_empty), + "DataDirectory %s\n", + get_fname(NULL)); + // test with defaults-torrc and torrc without include + int ret = options_init_from_string(conf_empty, conf_empty, CMD_RUN_UNITTESTS, + NULL, &errmsg); + tt_int_op(ret, OP_EQ, 0); + + const or_options_t *options = get_options(); + tt_int_op(options->IncludeUsed, OP_EQ, 0); + + done: + tor_free(errmsg); +} + +static void +test_config_include_flag_torrc_only(void *data) +{ + (void)data; + + char *errmsg = NULL; + char *dir = tor_strdup(get_fname("test_include_flag_torrc_only")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char path[PATH_MAX+1]; + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy"); + tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0); + + char conf_empty[1000]; + tor_snprintf(conf_empty, sizeof(conf_empty), + "DataDirectory %s\n", + get_fname(NULL)); + char conf_include[1000]; + tor_snprintf(conf_include, sizeof(conf_include), "%%include %s", path); + + // test with defaults-torrc without include and torrc with include + int ret = options_init_from_string(conf_empty, conf_include, + CMD_RUN_UNITTESTS, NULL, &errmsg); + tt_int_op(ret, OP_EQ, 0); + + const or_options_t *options = get_options(); + tt_int_op(options->IncludeUsed, OP_EQ, 1); + + done: + tor_free(errmsg); + tor_free(dir); +} + +static void +test_config_include_flag_defaults_only(void *data) +{ + (void)data; + + char *errmsg = NULL; + char *dir = tor_strdup(get_fname("test_include_flag_defaults_only")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char path[PATH_MAX+1]; + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy"); + tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0); + + char conf_empty[1000]; + tor_snprintf(conf_empty, sizeof(conf_empty), + "DataDirectory %s\n", + get_fname(NULL)); + char conf_include[1000]; + tor_snprintf(conf_include, sizeof(conf_include), "%%include %s", path); + + // test with defaults-torrc with include and torrc without include + int ret = options_init_from_string(conf_include, conf_empty, + CMD_RUN_UNITTESTS, NULL, &errmsg); + tt_int_op(ret, OP_EQ, 0); + + const or_options_t *options = get_options(); + tt_int_op(options->IncludeUsed, OP_EQ, 0); + + done: + tor_free(errmsg); + tor_free(dir); +} + #define CONFIG_TEST(name, flags) \ { #name, test_config_ ## name, flags, NULL, NULL } @@ -4951,11 +5379,23 @@ struct testcase_t config_tests[] = { CONFIG_TEST(fix_my_family, 0), CONFIG_TEST(directory_fetch, 0), CONFIG_TEST(port_cfg_line_extract_addrport, 0), - CONFIG_TEST(parse_port_config__listenaddress, 0), CONFIG_TEST(parse_port_config__ports__no_ports_given, 0), CONFIG_TEST(parse_port_config__ports__server_options, 0), CONFIG_TEST(parse_port_config__ports__ports_given, 0), CONFIG_TEST(parse_log_severity, 0), + CONFIG_TEST(include_limit, 0), + CONFIG_TEST(include_does_not_exist, 0), + CONFIG_TEST(include_error_in_included_file, 0), + CONFIG_TEST(include_empty_file_folder, 0), + CONFIG_TEST(include_recursion_before_after, 0), + CONFIG_TEST(include_recursion_after_only, 0), + CONFIG_TEST(include_folder_order, 0), + CONFIG_TEST(include_path_syntax, 0), + CONFIG_TEST(include_not_processed, 0), + CONFIG_TEST(include_has_include, 0), + CONFIG_TEST(include_flag_both_without, TT_FORK), + CONFIG_TEST(include_flag_torrc_only, TT_FORK), + CONFIG_TEST(include_flag_defaults_only, TT_FORK), END_OF_TESTCASES }; diff --git a/src/test/test_connection.c b/src/test/test_connection.c index 5cda4f3175..7e5193b203 100644 --- a/src/test/test_connection.c +++ b/src/test/test_connection.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -265,7 +265,7 @@ test_conn_get_rend_setup(const struct testcase_t *tc) rend_cache_init(); - /* TODO: use directory_initiate_command_rend() to do this - maybe? */ + /* TODO: use directory_initiate_request() to do this - maybe? */ tor_assert(strlen(TEST_CONN_REND_ADDR) == REND_SERVICE_ID_LEN_BASE32); conn->rend_data = rend_data_client_create(TEST_CONN_REND_ADDR, NULL, NULL, REND_NO_AUTH); diff --git a/src/test/test_conscache.c b/src/test/test_conscache.c new file mode 100644 index 0000000000..aee1ba8a06 --- /dev/null +++ b/src/test/test_conscache.c @@ -0,0 +1,340 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "config.h" +#include "conscache.h" +#include "test.h" + +#ifdef HAVE_UTIME_H +#include <utime.h> +#endif + +static void +test_conscache_open_failure(void *arg) +{ + (void) arg; + /* Try opening a directory that doesn't exist and which we shouldn't be + * able to create. */ + consensus_cache_t *cache = consensus_cache_open("a/b/c/d/e/f/g", 128); + tt_ptr_op(cache, OP_EQ, NULL); + + done: + ; +} + +static void +test_conscache_simple_usage(void *arg) +{ + (void)arg; + consensus_cache_entry_t *ent = NULL, *ent2 = NULL; + + /* Make a temporary datadir for these tests */ + char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache")); + tor_free(get_options_mutable()->DataDirectory); + get_options_mutable()->DataDirectory = tor_strdup(ddir_fname); + check_private_dir(ddir_fname, CPD_CREATE, NULL); + consensus_cache_t *cache = consensus_cache_open("cons", 128); + + tt_assert(cache); + + /* Create object; make sure it exists. */ + config_line_t *labels = NULL; + config_line_append(&labels, "Hello", "world"); + config_line_append(&labels, "Adios", "planetas"); + ent = consensus_cache_add(cache, + labels, (const uint8_t *)"A\0B\0C", 5); + config_free_lines(labels); + labels = NULL; + tt_assert(ent); + + /* Make a second object */ + config_line_append(&labels, "Hello", "mundo"); + config_line_append(&labels, "Adios", "planets"); + ent2 = consensus_cache_add(cache, + labels, (const uint8_t *)"xyzzy", 5); + config_free_lines(labels); + labels = NULL; + tt_assert(ent2); + tt_assert(! consensus_cache_entry_is_mapped(ent2)); + consensus_cache_entry_decref(ent2); + ent2 = NULL; + + /* Check get_value */ + tt_ptr_op(NULL, OP_EQ, consensus_cache_entry_get_value(ent, "hebbo")); + tt_str_op("world", OP_EQ, consensus_cache_entry_get_value(ent, "Hello")); + + /* Check find_first */ + ent2 = consensus_cache_find_first(cache, "Hello", "world!"); + tt_ptr_op(ent2, OP_EQ, NULL); + ent2 = consensus_cache_find_first(cache, "Hello", "world"); + tt_ptr_op(ent2, OP_EQ, ent); + ent2 = consensus_cache_find_first(cache, "Hello", "mundo"); + tt_ptr_op(ent2, OP_NE, ent); + + tt_assert(! consensus_cache_entry_is_mapped(ent)); + + /* Check get_body */ + const uint8_t *bp = NULL; + size_t sz = 0; + int r = consensus_cache_entry_get_body(ent, &bp, &sz); + tt_int_op(r, OP_EQ, 0); + tt_u64_op(sz, OP_EQ, 5); + tt_mem_op(bp, OP_EQ, "A\0B\0C", 5); + tt_assert(consensus_cache_entry_is_mapped(ent)); + + /* Free and re-create the cache, to rescan the directory. */ + consensus_cache_free(cache); + consensus_cache_entry_decref(ent); + cache = consensus_cache_open("cons", 128); + + /* Make sure the entry is still there */ + ent = consensus_cache_find_first(cache, "Hello", "mundo"); + tt_assert(ent); + ent2 = consensus_cache_find_first(cache, "Adios", "planets"); + tt_ptr_op(ent, OP_EQ, ent2); + consensus_cache_entry_incref(ent); + tt_assert(! consensus_cache_entry_is_mapped(ent)); + r = consensus_cache_entry_get_body(ent, &bp, &sz); + tt_int_op(r, OP_EQ, 0); + tt_u64_op(sz, OP_EQ, 5); + tt_mem_op(bp, OP_EQ, "xyzzy", 5); + tt_assert(consensus_cache_entry_is_mapped(ent)); + + /* There should be two entries total. */ + smartlist_t *entries = smartlist_new(); + consensus_cache_find_all(entries, cache, NULL, NULL); + int n = smartlist_len(entries); + smartlist_free(entries); + tt_int_op(n, OP_EQ, 2); + + done: + consensus_cache_entry_decref(ent); + tor_free(ddir_fname); + consensus_cache_free(cache); +} + +static void +test_conscache_cleanup(void *arg) +{ + (void)arg; + const int N = 20; + consensus_cache_entry_t **ents = + tor_calloc(N, sizeof(consensus_cache_entry_t*)); + + /* Make a temporary datadir for these tests */ + char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache")); + tor_free(get_options_mutable()->DataDirectory); + get_options_mutable()->DataDirectory = tor_strdup(ddir_fname); + check_private_dir(ddir_fname, CPD_CREATE, NULL); + consensus_cache_t *cache = consensus_cache_open("cons", 128); + + tt_assert(cache); + + /* Create a bunch of entries. */ + int i; + for (i = 0; i < N; ++i) { + config_line_t *labels = NULL; + char num[8]; + tor_snprintf(num, sizeof(num), "%d", i); + config_line_append(&labels, "test-id", "cleanup"); + config_line_append(&labels, "index", num); + size_t bodylen = i * 3; + uint8_t *body = tor_malloc(bodylen); + memset(body, i, bodylen); + ents[i] = consensus_cache_add(cache, labels, body, bodylen); + tor_free(body); + config_free_lines(labels); + tt_assert(ents[i]); + /* We're still holding a reference to each entry at this point. */ + } + + /* Page all of the entries into RAM */ + for (i = 0; i < N; ++i) { + const uint8_t *bp; + size_t sz; + tt_assert(! consensus_cache_entry_is_mapped(ents[i])); + consensus_cache_entry_get_body(ents[i], &bp, &sz); + tt_assert(consensus_cache_entry_is_mapped(ents[i])); + } + + /* Mark some of the entries as deletable. */ + for (i = 7; i < N; i += 7) { + consensus_cache_entry_mark_for_removal(ents[i]); + tt_assert(consensus_cache_entry_is_mapped(ents[i])); + } + + /* Mark some of the entries as aggressively unpaged. */ + for (i = 3; i < N; i += 3) { + consensus_cache_entry_mark_for_aggressive_release(ents[i]); + tt_assert(consensus_cache_entry_is_mapped(ents[i])); + } + + /* Incref some of the entries again */ + for (i = 0; i < N; i += 2) { + consensus_cache_entry_incref(ents[i]); + } + + /* Now we're going to decref everything. We do so at a specific time. I'm + * picking the moment when I was writing this test, at 2017-04-05 12:16:48 + * UTC. */ + const time_t example_time = 1491394608; + update_approx_time(example_time); + for (i = 0; i < N; ++i) { + consensus_cache_entry_decref(ents[i]); + if (i % 2) { + ents[i] = NULL; /* We're no longer holding any reference here. */ + } + } + + /* At this point, the aggressively-released items with refcount 1 should + * be unmapped. Nothing should be deleted. */ + consensus_cache_entry_t *e_tmp; + e_tmp = consensus_cache_find_first(cache, "index", "3"); + tt_assert(e_tmp); + tt_assert(! consensus_cache_entry_is_mapped(e_tmp)); + e_tmp = consensus_cache_find_first(cache, "index", "5"); + tt_assert(e_tmp); + tt_assert(consensus_cache_entry_is_mapped(e_tmp)); + e_tmp = consensus_cache_find_first(cache, "index", "6"); + tt_assert(e_tmp); + tt_assert(consensus_cache_entry_is_mapped(e_tmp)); + e_tmp = consensus_cache_find_first(cache, "index", "7"); + tt_assert(e_tmp == NULL); // not found because pending deletion. + + /* Delete the pending-deletion items. */ + consensus_cache_delete_pending(cache, 0); + { + smartlist_t *entries = smartlist_new(); + consensus_cache_find_all(entries, cache, NULL, NULL); + int n = smartlist_len(entries); + smartlist_free(entries); + tt_int_op(n, OP_EQ, 20 - 2); /* 1 entry was deleted; 1 is not-found. */ + } + e_tmp = consensus_cache_find_first(cache, "index", "7"); // refcnt == 1... + tt_assert(e_tmp == NULL); // so deleted. + e_tmp = consensus_cache_find_first(cache, "index", "14"); // refcnt == 2 + tt_assert(e_tmp == NULL); // not deleted; but not found. + + /* Now do lazy unmapping. */ + // should do nothing. + consensus_cache_unmap_lazy(cache, example_time - 10); + e_tmp = consensus_cache_find_first(cache, "index", "11"); + tt_assert(e_tmp); + tt_assert(consensus_cache_entry_is_mapped(e_tmp)); + // should actually unmap + consensus_cache_unmap_lazy(cache, example_time + 10); + e_tmp = consensus_cache_find_first(cache, "index", "11"); + tt_assert(e_tmp); + tt_assert(! consensus_cache_entry_is_mapped(e_tmp)); + // This one will still be mapped, since it has a reference. + e_tmp = consensus_cache_find_first(cache, "index", "16"); + tt_assert(e_tmp); + tt_assert(consensus_cache_entry_is_mapped(e_tmp)); + + for (i = 0; i < N; ++i) { + consensus_cache_entry_decref(ents[i]); + ents[i] = NULL; + } + + /* Free and re-create the cache, to rescan the directory. Make sure the + * deleted thing is still deleted, along with the other deleted thing. */ + consensus_cache_free(cache); + cache = consensus_cache_open("cons", 128); + { + smartlist_t *entries = smartlist_new(); + consensus_cache_find_all(entries, cache, NULL, NULL); + int n = smartlist_len(entries); + smartlist_free(entries); + tt_int_op(n, OP_EQ, 18); + } + + done: + for (i = 0; i < N; ++i) { + consensus_cache_entry_decref(ents[i]); + } + tor_free(ents); + tor_free(ddir_fname); + consensus_cache_free(cache); +} + +static void +test_conscache_filter(void *arg) +{ + (void)arg; + const int N = 30; + smartlist_t *lst = NULL; + + /* Make a temporary datadir for these tests */ + char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache")); + tor_free(get_options_mutable()->DataDirectory); + get_options_mutable()->DataDirectory = tor_strdup(ddir_fname); + check_private_dir(ddir_fname, CPD_CREATE, NULL); + consensus_cache_t *cache = consensus_cache_open("cons", 128); + + tt_assert(cache); + + /* Create a bunch of entries with different labels */ + int i; + for (i = 0; i < N; ++i) { + config_line_t *labels = NULL; + char num[8]; + tor_snprintf(num, sizeof(num), "%d", i); + config_line_append(&labels, "test-id", "filter"); + config_line_append(&labels, "index", num); + tor_snprintf(num, sizeof(num), "%d", i % 3); + config_line_append(&labels, "mod3", num); + tor_snprintf(num, sizeof(num), "%d", i % 5); + config_line_append(&labels, "mod5", num); + + size_t bodylen = i * 3; + uint8_t *body = tor_malloc(bodylen); + memset(body, i, bodylen); + consensus_cache_entry_t *ent = + consensus_cache_add(cache, labels, body, bodylen); + tor_free(body); + config_free_lines(labels); + tt_assert(ent); + consensus_cache_entry_decref(ent); + } + + lst = smartlist_new(); + /* Find nothing. */ + consensus_cache_find_all(lst, cache, "mod5", "5"); + tt_int_op(smartlist_len(lst), OP_EQ, 0); + /* Find everything. */ + consensus_cache_find_all(lst, cache, "test-id", "filter"); + tt_int_op(smartlist_len(lst), OP_EQ, N); + + /* Now filter to find the entries that have i%3 == 1 */ + consensus_cache_filter_list(lst, "mod3", "1"); + tt_int_op(smartlist_len(lst), OP_EQ, 10); + /* Now filter to find the entries that also have i%5 == 3 */ + consensus_cache_filter_list(lst, "mod5", "3"); + tt_int_op(smartlist_len(lst), OP_EQ, 2); + /* So now we have those entries for which i%15 == 13. */ + + consensus_cache_entry_t *ent1 = smartlist_get(lst, 0); + consensus_cache_entry_t *ent2 = smartlist_get(lst, 1); + const char *idx1 = consensus_cache_entry_get_value(ent1, "index"); + const char *idx2 = consensus_cache_entry_get_value(ent2, "index"); + tt_assert( (!strcmp(idx1, "28") && !strcmp(idx2, "13")) || + (!strcmp(idx1, "13") && !strcmp(idx2, "28")) ); + + done: + tor_free(ddir_fname); + consensus_cache_free(cache); + smartlist_free(lst); +} + +#define ENT(name) \ + { #name, test_conscache_ ## name, TT_FORK, NULL, NULL } + +struct testcase_t conscache_tests[] = { + ENT(open_failure), + ENT(simple_usage), + ENT(cleanup), + ENT(filter), + END_OF_TESTCASES +}; + diff --git a/src/test/test_consdiff.c b/src/test/test_consdiff.c new file mode 100644 index 0000000000..7cf8d6ba2b --- /dev/null +++ b/src/test/test_consdiff.c @@ -0,0 +1,1184 @@ +/* Copyright (c) 2014, Daniel Martà + * Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CONSDIFF_PRIVATE + +#include "or.h" +#include "test.h" + +#include "consdiff.h" +#include "memarea.h" +#include "log_test_helpers.h" + +#define tt_str_eq_line(a,b) \ + tt_assert(line_str_eq((b),(a))) + +static void +test_consdiff_smartlist_slice(void *arg) +{ + smartlist_t *sl = smartlist_new(); + smartlist_slice_t *sls; + + /* Create a regular smartlist. */ + (void)arg; + smartlist_add(sl, (void*)1); + smartlist_add(sl, (void*)2); + smartlist_add(sl, (void*)3); + smartlist_add(sl, (void*)4); + smartlist_add(sl, (void*)5); + + /* See if the slice was done correctly. */ + sls = smartlist_slice(sl, 2, 5); + tt_ptr_op(sl, OP_EQ, sls->list); + tt_ptr_op((void*)3, OP_EQ, smartlist_get(sls->list, sls->offset)); + tt_ptr_op((void*)5, OP_EQ, + smartlist_get(sls->list, sls->offset + (sls->len-1))); + tor_free(sls); + + /* See that using -1 as the end does get to the last element. */ + sls = smartlist_slice(sl, 2, -1); + tt_ptr_op(sl, OP_EQ, sls->list); + tt_ptr_op((void*)3, OP_EQ, smartlist_get(sls->list, sls->offset)); + tt_ptr_op((void*)5, OP_EQ, + smartlist_get(sls->list, sls->offset + (sls->len-1))); + + done: + tor_free(sls); + smartlist_free(sl); +} + +static void +test_consdiff_smartlist_slice_string_pos(void *arg) +{ + smartlist_t *sl = smartlist_new(); + smartlist_slice_t *sls; + memarea_t *area = memarea_new(); + + /* Create a regular smartlist. */ + (void)arg; + consensus_split_lines(sl, "a\nd\nc\na\nb\n", area); + + /* See that smartlist_slice_string_pos respects the bounds of the slice. */ + sls = smartlist_slice(sl, 2, 5); + cdline_t a_line = { "a", 1 }; + tt_int_op(3, OP_EQ, smartlist_slice_string_pos(sls, &a_line)); + cdline_t d_line = { "d", 1 }; + tt_int_op(-1, OP_EQ, smartlist_slice_string_pos(sls, &d_line)); + + done: + tor_free(sls); + smartlist_free(sl); + memarea_drop_all(area); +} + +static void +test_consdiff_lcs_lengths(void *arg) +{ + smartlist_t *sl1 = smartlist_new(); + smartlist_t *sl2 = smartlist_new(); + smartlist_slice_t *sls1, *sls2; + int *lengths1, *lengths2; + memarea_t *area = memarea_new(); + + /* Expected lcs lengths in regular and reverse order. */ + int e_lengths1[] = { 0, 1, 2, 3, 3, 4 }; + int e_lengths2[] = { 0, 1, 1, 2, 3, 4 }; + + (void)arg; + consensus_split_lines(sl1, "a\nb\nc\nd\ne\n", area); + consensus_split_lines(sl2, "a\nc\nd\ni\ne\n", area); + + sls1 = smartlist_slice(sl1, 0, -1); + sls2 = smartlist_slice(sl2, 0, -1); + + lengths1 = lcs_lengths(sls1, sls2, 1); + lengths2 = lcs_lengths(sls1, sls2, -1); + tt_mem_op(e_lengths1, OP_EQ, lengths1, sizeof(int) * 6); + tt_mem_op(e_lengths2, OP_EQ, lengths2, sizeof(int) * 6); + + done: + tor_free(lengths1); + tor_free(lengths2); + tor_free(sls1); + tor_free(sls2); + smartlist_free(sl1); + smartlist_free(sl2); + memarea_drop_all(area); +} + +static void +test_consdiff_trim_slices(void *arg) +{ + smartlist_t *sl1 = smartlist_new(); + smartlist_t *sl2 = smartlist_new(); + smartlist_t *sl3 = smartlist_new(); + smartlist_t *sl4 = smartlist_new(); + smartlist_slice_t *sls1, *sls2, *sls3, *sls4; + memarea_t *area = memarea_new(); + + (void)arg; + consensus_split_lines(sl1, "a\nb\nb\nb\nd\n", area); + consensus_split_lines(sl2, "a\nc\nc\nc\nd\n", area); + consensus_split_lines(sl3, "a\nb\nb\nb\na\n", area); + consensus_split_lines(sl4, "c\nb\nb\nb\nc\n", area); + sls1 = smartlist_slice(sl1, 0, -1); + sls2 = smartlist_slice(sl2, 0, -1); + sls3 = smartlist_slice(sl3, 0, -1); + sls4 = smartlist_slice(sl4, 0, -1); + + /* They should be trimmed by one line at each end. */ + tt_int_op(5, OP_EQ, sls1->len); + tt_int_op(5, OP_EQ, sls2->len); + trim_slices(sls1, sls2); + tt_int_op(3, OP_EQ, sls1->len); + tt_int_op(3, OP_EQ, sls2->len); + + /* They should not be trimmed at all. */ + tt_int_op(5, OP_EQ, sls3->len); + tt_int_op(5, OP_EQ, sls4->len); + trim_slices(sls3, sls4); + tt_int_op(5, OP_EQ, sls3->len); + tt_int_op(5, OP_EQ, sls4->len); + + done: + tor_free(sls1); + tor_free(sls2); + tor_free(sls3); + tor_free(sls4); + smartlist_free(sl1); + smartlist_free(sl2); + smartlist_free(sl3); + smartlist_free(sl4); + memarea_drop_all(area); +} + +static void +test_consdiff_set_changed(void *arg) +{ + smartlist_t *sl1 = smartlist_new(); + smartlist_t *sl2 = smartlist_new(); + bitarray_t *changed1 = bitarray_init_zero(4); + bitarray_t *changed2 = bitarray_init_zero(4); + smartlist_slice_t *sls1, *sls2; + memarea_t *area = memarea_new(); + + (void)arg; + consensus_split_lines(sl1, "a\nb\na\na\n", area); + consensus_split_lines(sl2, "a\na\na\na\n", area); + + /* Length of sls1 is 0. */ + sls1 = smartlist_slice(sl1, 0, 0); + sls2 = smartlist_slice(sl2, 1, 3); + set_changed(changed1, changed2, sls1, sls2); + + /* The former is not changed, the latter changes all of its elements. */ + tt_assert(!bitarray_is_set(changed1, 0)); + tt_assert(!bitarray_is_set(changed1, 1)); + tt_assert(!bitarray_is_set(changed1, 2)); + tt_assert(!bitarray_is_set(changed1, 3)); + + tt_assert(!bitarray_is_set(changed2, 0)); + tt_assert(bitarray_is_set(changed2, 1)); + tt_assert(bitarray_is_set(changed2, 2)); + tt_assert(!bitarray_is_set(changed2, 3)); + bitarray_clear(changed2, 1); + bitarray_clear(changed2, 2); + + /* Length of sls1 is 1 and its element is in sls2. */ + tor_free(sls1); + sls1 = smartlist_slice(sl1, 0, 1); + set_changed(changed1, changed2, sls1, sls2); + + /* The latter changes all elements but the (first) common one. */ + tt_assert(!bitarray_is_set(changed1, 0)); + tt_assert(!bitarray_is_set(changed1, 1)); + tt_assert(!bitarray_is_set(changed1, 2)); + tt_assert(!bitarray_is_set(changed1, 3)); + + tt_assert(!bitarray_is_set(changed2, 0)); + tt_assert(!bitarray_is_set(changed2, 1)); + tt_assert(bitarray_is_set(changed2, 2)); + tt_assert(!bitarray_is_set(changed2, 3)); + bitarray_clear(changed2, 2); + + /* Length of sls1 is 1 and its element is not in sls2. */ + tor_free(sls1); + sls1 = smartlist_slice(sl1, 1, 2); + set_changed(changed1, changed2, sls1, sls2); + + /* The former changes its element, the latter changes all elements. */ + tt_assert(!bitarray_is_set(changed1, 0)); + tt_assert(bitarray_is_set(changed1, 1)); + tt_assert(!bitarray_is_set(changed1, 2)); + tt_assert(!bitarray_is_set(changed1, 3)); + + tt_assert(!bitarray_is_set(changed2, 0)); + tt_assert(bitarray_is_set(changed2, 1)); + tt_assert(bitarray_is_set(changed2, 2)); + tt_assert(!bitarray_is_set(changed2, 3)); + + done: + bitarray_free(changed1); + bitarray_free(changed2); + smartlist_free(sl1); + smartlist_free(sl2); + tor_free(sls1); + tor_free(sls2); + memarea_drop_all(area); +} + +static void +test_consdiff_calc_changes(void *arg) +{ + smartlist_t *sl1 = smartlist_new(); + smartlist_t *sl2 = smartlist_new(); + smartlist_slice_t *sls1, *sls2; + bitarray_t *changed1 = bitarray_init_zero(4); + bitarray_t *changed2 = bitarray_init_zero(4); + memarea_t *area = memarea_new(); + + (void)arg; + consensus_split_lines(sl1, "a\na\na\na\n", area); + consensus_split_lines(sl2, "a\na\na\na\n", area); + + sls1 = smartlist_slice(sl1, 0, -1); + sls2 = smartlist_slice(sl2, 0, -1); + calc_changes(sls1, sls2, changed1, changed2); + + /* Nothing should be set to changed. */ + tt_assert(!bitarray_is_set(changed1, 0)); + tt_assert(!bitarray_is_set(changed1, 1)); + tt_assert(!bitarray_is_set(changed1, 2)); + tt_assert(!bitarray_is_set(changed1, 3)); + + tt_assert(!bitarray_is_set(changed2, 0)); + tt_assert(!bitarray_is_set(changed2, 1)); + tt_assert(!bitarray_is_set(changed2, 2)); + tt_assert(!bitarray_is_set(changed2, 3)); + + smartlist_clear(sl2); + consensus_split_lines(sl2, "a\nb\na\nb\n", area); + tor_free(sls1); + tor_free(sls2); + sls1 = smartlist_slice(sl1, 0, -1); + sls2 = smartlist_slice(sl2, 0, -1); + calc_changes(sls1, sls2, changed1, changed2); + + /* Two elements are changed. */ + tt_assert(!bitarray_is_set(changed1, 0)); + tt_assert(bitarray_is_set(changed1, 1)); + tt_assert(bitarray_is_set(changed1, 2)); + tt_assert(!bitarray_is_set(changed1, 3)); + bitarray_clear(changed1, 1); + bitarray_clear(changed1, 2); + + tt_assert(!bitarray_is_set(changed2, 0)); + tt_assert(bitarray_is_set(changed2, 1)); + tt_assert(!bitarray_is_set(changed2, 2)); + tt_assert(bitarray_is_set(changed2, 3)); + bitarray_clear(changed1, 1); + bitarray_clear(changed1, 3); + + smartlist_clear(sl2); + consensus_split_lines(sl2, "b\nb\nb\nb\n", area); + tor_free(sls1); + tor_free(sls2); + sls1 = smartlist_slice(sl1, 0, -1); + sls2 = smartlist_slice(sl2, 0, -1); + calc_changes(sls1, sls2, changed1, changed2); + + /* All elements are changed. */ + tt_assert(bitarray_is_set(changed1, 0)); + tt_assert(bitarray_is_set(changed1, 1)); + tt_assert(bitarray_is_set(changed1, 2)); + tt_assert(bitarray_is_set(changed1, 3)); + + tt_assert(bitarray_is_set(changed2, 0)); + tt_assert(bitarray_is_set(changed2, 1)); + tt_assert(bitarray_is_set(changed2, 2)); + tt_assert(bitarray_is_set(changed2, 3)); + + done: + bitarray_free(changed1); + bitarray_free(changed2); + smartlist_free(sl1); + smartlist_free(sl2); + tor_free(sls1); + tor_free(sls2); + memarea_drop_all(area); +} + +static void +test_consdiff_get_id_hash(void *arg) +{ + (void)arg; + + cdline_t line1 = { "r name", 6 }; + cdline_t line2 = { "r name _hash_isnt_base64 etc", 28 }; + cdline_t line3 = { "r name hash+valid+base64 etc", 28 }; + cdline_t tmp; + + /* No hash. */ + tt_int_op(-1, OP_EQ, get_id_hash(&line1, &tmp)); + /* The hash contains characters that are not base64. */ + tt_int_op(-1, OP_EQ, get_id_hash(&line2, &tmp)); + + /* valid hash. */ + tt_int_op(0, OP_EQ, get_id_hash(&line3, &tmp)); + tt_ptr_op(tmp.s, OP_EQ, line3.s + 7); + tt_uint_op(tmp.len, OP_EQ, line3.len - 11); + + done: + ; +} + +static void +test_consdiff_is_valid_router_entry(void *arg) +{ + /* Doesn't start with "r ". */ + (void)arg; + cdline_t line0 = { "foo", 3 }; + tt_int_op(0, OP_EQ, is_valid_router_entry(&line0)); + + /* These are already tested with get_id_hash, but make sure it's run + * properly. */ + + cdline_t line1 = { "r name", 6 }; + cdline_t line2 = { "r name _hash_isnt_base64 etc", 28 }; + cdline_t line3 = { "r name hash+valid+base64 etc", 28 }; + tt_int_op(0, OP_EQ, is_valid_router_entry(&line1)); + tt_int_op(0, OP_EQ, is_valid_router_entry(&line2)); + tt_int_op(1, OP_EQ, is_valid_router_entry(&line3)); + + done: + ; +} + +static void +test_consdiff_next_router(void *arg) +{ + smartlist_t *sl = smartlist_new(); + memarea_t *area = memarea_new(); + (void)arg; + smartlist_add_linecpy(sl, area, "foo"); + smartlist_add_linecpy(sl, area, + "r name hash+longer+than+27+chars+and+valid+base64 etc"); + smartlist_add_linecpy(sl, area, "foo"); + smartlist_add_linecpy(sl, area, "foo"); + smartlist_add_linecpy(sl, area, + "r name hash+longer+than+27+chars+and+valid+base64 etc"); + smartlist_add_linecpy(sl, area, "foo"); + + /* Not currently on a router entry line, finding the next one. */ + tt_int_op(1, OP_EQ, next_router(sl, 0)); + tt_int_op(4, OP_EQ, next_router(sl, 2)); + + /* Already at the beginning of a router entry line, ignore it. */ + tt_int_op(4, OP_EQ, next_router(sl, 1)); + + /* There are no more router entries, so return the line after the last. */ + tt_int_op(6, OP_EQ, next_router(sl, 4)); + tt_int_op(6, OP_EQ, next_router(sl, 5)); + + done: + smartlist_free(sl); + memarea_drop_all(area); +} + +static int +base64cmp_wrapper(const char *a, const char *b) +{ + cdline_t aa = { a, a ? (uint32_t) strlen(a) : 0 }; + cdline_t bb = { b, b ? (uint32_t) strlen(b) : 0 }; + return base64cmp(&aa, &bb); +} + +static void +test_consdiff_base64cmp(void *arg) +{ + /* NULL arguments. */ + (void)arg; + tt_int_op(0, OP_EQ, base64cmp_wrapper(NULL, NULL)); + tt_int_op(-1, OP_EQ, base64cmp_wrapper(NULL, "foo")); + tt_int_op(1, OP_EQ, base64cmp_wrapper("bar", NULL)); + + /* Nil base64 values. */ + tt_int_op(0, OP_EQ, base64cmp_wrapper("", "")); + tt_int_op(0, OP_EQ, base64cmp_wrapper("_", "&")); + + /* Exact same valid strings. */ + tt_int_op(0, OP_EQ, base64cmp_wrapper("abcABC/+", "abcABC/+")); + /* Both end with an invalid base64 char other than '\0'. */ + tt_int_op(0, OP_EQ, base64cmp_wrapper("abcABC/+ ", "abcABC/+ ")); + /* Only one ends with an invalid base64 char other than '\0'. */ + tt_int_op(-1, OP_EQ, base64cmp_wrapper("abcABC/+ ", "abcABC/+a")); + + /* Comparisons that would return differently with strcmp(). */ + tt_int_op(strcmp("/foo", "Afoo"), OP_LT, 0); + tt_int_op(base64cmp_wrapper("/foo", "Afoo"), OP_GT, 0); + tt_int_op(strcmp("Afoo", "0foo"), OP_GT, 0); + tt_int_op(base64cmp_wrapper("Afoo", "0foo"), OP_LT, 0); + + /* Comparisons that would return the same as with strcmp(). */ + tt_int_op(strcmp("afoo", "Afoo"), OP_GT, 0); + tt_int_op(base64cmp_wrapper("afoo", "Afoo"), OP_GT, 0); + + /* Different lengths */ + tt_int_op(base64cmp_wrapper("afoo", "afooo"), OP_LT, 0); + tt_int_op(base64cmp_wrapper("afooo", "afoo"), OP_GT, 0); + + done: + ; +} + +static void +test_consdiff_gen_ed_diff(void *arg) +{ + smartlist_t *cons1=NULL, *cons2=NULL, *diff=NULL; + int i; + memarea_t *area = memarea_new(); + setup_capture_of_logs(LOG_WARN); + + (void)arg; + cons1 = smartlist_new(); + cons2 = smartlist_new(); + + /* Identity hashes are not sorted properly, return NULL. */ + smartlist_add_linecpy(cons1, area, "r name bbbbbbbbbbbbbbbbbbbbbbbbbbb etc"); + smartlist_add_linecpy(cons1, area, "foo"); + smartlist_add_linecpy(cons1, area, "r name aaaaaaaaaaaaaaaaaaaaaaaaaaa etc"); + smartlist_add_linecpy(cons1, area, "bar"); + + smartlist_add_linecpy(cons2, area, "r name aaaaaaaaaaaaaaaaaaaaaaaaaaa etc"); + smartlist_add_linecpy(cons2, area, "foo"); + smartlist_add_linecpy(cons2, area, "r name ccccccccccccccccccccccccccc etc"); + smartlist_add_linecpy(cons2, area, "bar"); + + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_EQ, diff); + expect_single_log_msg_containing("Refusing to generate consensus diff " + "because the base consensus doesn't have its router entries sorted " + "properly."); + + /* Same, but now with the second consensus. */ + mock_clean_saved_logs(); + diff = gen_ed_diff(cons2, cons1, area); + tt_ptr_op(NULL, OP_EQ, diff); + expect_single_log_msg_containing("Refusing to generate consensus diff " + "because the target consensus doesn't have its router entries sorted " + "properly."); + + /* Same as the two above, but with the reversed thing immediately after a + match. (The code handles this differently) */ + smartlist_del(cons1, 0); + smartlist_add_linecpy(cons1, area, "r name aaaaaaaaaaaaaaaaaaaaaaaaaaa etc"); + + mock_clean_saved_logs(); + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_EQ, diff); + expect_single_log_msg_containing("Refusing to generate consensus diff " + "because the base consensus doesn't have its router entries sorted " + "properly."); + + mock_clean_saved_logs(); + diff = gen_ed_diff(cons2, cons1, area); + tt_ptr_op(NULL, OP_EQ, diff); + expect_single_log_msg_containing("Refusing to generate consensus diff " + "because the target consensus doesn't have its router entries sorted " + "properly."); + + /* Identity hashes are repeated, return NULL. */ + smartlist_clear(cons1); + + smartlist_add_linecpy(cons1, area, "r name bbbbbbbbbbbbbbbbbbbbbbbbbbb etc"); + smartlist_add_linecpy(cons1, area, "foo"); + smartlist_add_linecpy(cons1, area, "r name bbbbbbbbbbbbbbbbbbbbbbbbbbb etc"); + smartlist_add_linecpy(cons1, area, "bar"); + + mock_clean_saved_logs(); + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_EQ, diff); + expect_single_log_msg_containing("Refusing to generate consensus diff " + "because the base consensus doesn't have its router entries sorted " + "properly."); + + /* We have to add a line that is just a dot, return NULL. */ + smartlist_clear(cons1); + smartlist_clear(cons2); + + smartlist_add_linecpy(cons1, area, "foo1"); + smartlist_add_linecpy(cons1, area, "foo2"); + + smartlist_add_linecpy(cons2, area, "foo1"); + smartlist_add_linecpy(cons2, area, "."); + smartlist_add_linecpy(cons2, area, "foo2"); + + mock_clean_saved_logs(); + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_EQ, diff); + expect_single_log_msg_containing("Cannot generate consensus diff " + "because one of the lines to be added is \".\"."); + +#define MAX_LINE_COUNT (10000) + /* Too many lines to be fed to the quadratic-time function. */ + smartlist_clear(cons1); + smartlist_clear(cons2); + + for (i=0; i < MAX_LINE_COUNT; ++i) smartlist_add_linecpy(cons1, area, "a"); + for (i=0; i < MAX_LINE_COUNT; ++i) smartlist_add_linecpy(cons1, area, "b"); + + mock_clean_saved_logs(); + diff = gen_ed_diff(cons1, cons2, area); + + tt_ptr_op(NULL, OP_EQ, diff); + expect_single_log_msg_containing("Refusing to generate consensus diff " + "because we found too few common router ids."); + + /* We have dot lines, but they don't interfere with the script format. */ + smartlist_clear(cons1); + smartlist_clear(cons2); + + smartlist_add_linecpy(cons1, area, "foo1"); + smartlist_add_linecpy(cons1, area, "."); + smartlist_add_linecpy(cons1, area, "."); + smartlist_add_linecpy(cons1, area, "foo2"); + + smartlist_add_linecpy(cons2, area, "foo1"); + smartlist_add_linecpy(cons2, area, "."); + smartlist_add_linecpy(cons2, area, "foo2"); + + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_NE, diff); + smartlist_free(diff); + + /* Empty diff tests. */ + smartlist_clear(cons1); + smartlist_clear(cons2); + + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(0, OP_EQ, smartlist_len(diff)); + smartlist_free(diff); + + smartlist_add_linecpy(cons1, area, "foo"); + smartlist_add_linecpy(cons1, area, "bar"); + + smartlist_add_linecpy(cons2, area, "foo"); + smartlist_add_linecpy(cons2, area, "bar"); + + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(0, OP_EQ, smartlist_len(diff)); + smartlist_free(diff); + + /* Everything is deleted. */ + smartlist_clear(cons2); + + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(1, OP_EQ, smartlist_len(diff)); + tt_str_eq_line("1,2d", smartlist_get(diff, 0)); + + smartlist_free(diff); + + /* Everything is added. */ + diff = gen_ed_diff(cons2, cons1, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(4, OP_EQ, smartlist_len(diff)); + tt_str_eq_line("0a", smartlist_get(diff, 0)); + tt_str_eq_line("foo", smartlist_get(diff, 1)); + tt_str_eq_line("bar", smartlist_get(diff, 2)); + tt_str_eq_line(".", smartlist_get(diff, 3)); + + smartlist_free(diff); + + /* Everything is changed. */ + smartlist_add_linecpy(cons2, area, "foo2"); + smartlist_add_linecpy(cons2, area, "bar2"); + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(4, OP_EQ, smartlist_len(diff)); + tt_str_eq_line("1,2c", smartlist_get(diff, 0)); + tt_str_eq_line("foo2", smartlist_get(diff, 1)); + tt_str_eq_line("bar2", smartlist_get(diff, 2)); + tt_str_eq_line(".", smartlist_get(diff, 3)); + + smartlist_free(diff); + + /* Test 'a', 'c' and 'd' together. See that it is done in reverse order. */ + smartlist_clear(cons1); + smartlist_clear(cons2); + consensus_split_lines(cons1, "A\nB\nC\nD\nE\n", area); + consensus_split_lines(cons2, "A\nC\nO\nE\nU\n", area); + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(7, OP_EQ, smartlist_len(diff)); + tt_str_eq_line("5a", smartlist_get(diff, 0)); + tt_str_eq_line("U", smartlist_get(diff, 1)); + tt_str_eq_line(".", smartlist_get(diff, 2)); + tt_str_eq_line("4c", smartlist_get(diff, 3)); + tt_str_eq_line("O", smartlist_get(diff, 4)); + tt_str_eq_line(".", smartlist_get(diff, 5)); + tt_str_eq_line("2d", smartlist_get(diff, 6)); + + smartlist_free(diff); + + smartlist_clear(cons1); + smartlist_clear(cons2); + consensus_split_lines(cons1, "B\n", area); + consensus_split_lines(cons2, "A\nB\n", area); + diff = gen_ed_diff(cons1, cons2, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(3, OP_EQ, smartlist_len(diff)); + tt_str_eq_line("0a", smartlist_get(diff, 0)); + tt_str_eq_line("A", smartlist_get(diff, 1)); + tt_str_eq_line(".", smartlist_get(diff, 2)); + + /* TODO: small real use-cases, i.e. consensuses. */ + + done: + teardown_capture_of_logs(); + smartlist_free(cons1); + smartlist_free(cons2); + smartlist_free(diff); + memarea_drop_all(area); +} + +static void +test_consdiff_apply_ed_diff(void *arg) +{ + smartlist_t *cons1=NULL, *cons2=NULL, *diff=NULL; + memarea_t *area = memarea_new(); + (void)arg; + cons1 = smartlist_new(); + diff = smartlist_new(); + setup_capture_of_logs(LOG_WARN); + + consensus_split_lines(cons1, "A\nB\nC\nD\nE\n", area); + + /* Command without range. */ + smartlist_add_linecpy(diff, area, "a"); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + smartlist_clear(diff); + expect_single_log_msg_containing("an ed command was missing a line number"); + + /* Range without command. */ + smartlist_add_linecpy(diff, area, "1"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("a line with no ed command was found"); + + smartlist_clear(diff); + + /* Range without end. */ + smartlist_add_linecpy(diff, area, "1,"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an ed command was missing a range " + "end line number."); + + smartlist_clear(diff); + + /* Incoherent ranges. */ + smartlist_add_linecpy(diff, area, "1,1"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an invalid range was found"); + + smartlist_clear(diff); + + smartlist_add_linecpy(diff, area, "3,2"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an invalid range was found"); + + smartlist_clear(diff); + + /* Unexpected range for add command. */ + smartlist_add_linecpy(diff, area, "1,2a"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("add lines after a range"); + + smartlist_clear(diff); + + /* $ for a non-delete command. */ + smartlist_add_linecpy(diff, area, "1,$c"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("it wanted to use $ with a command " + "other than delete"); + + smartlist_clear(diff); + + /* Script is not in reverse order. */ + smartlist_add_linecpy(diff, area, "1d"); + smartlist_add_linecpy(diff, area, "3d"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("its commands are not properly sorted"); + + smartlist_clear(diff); + + /* Script contains unrecognised commands longer than one char. */ + smartlist_add_linecpy(diff, area, "1foo"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an ed command longer than one char was " + "found"); + + smartlist_clear(diff); + + /* Script contains unrecognised commands. */ + smartlist_add_linecpy(diff, area, "1e"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an unrecognised ed command was found"); + + smartlist_clear(diff); + + /* Command that should be followed by at least one line and a ".", but + * isn't. */ + smartlist_add_linecpy(diff, area, "0a"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("it has an ed command that tries to " + "insert zero lines."); + + /* Now it is followed by a ".", but it inserts zero lines. */ + smartlist_add_linecpy(diff, area, "."); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("it has an ed command that tries to " + "insert zero lines."); + + smartlist_clear(diff); + + /* Now it it inserts something, but has no terminator. */ + smartlist_add_linecpy(diff, area, "0a"); + smartlist_add_linecpy(diff, area, "hello"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("lines to be inserted that don't end with " + "a \".\"."); + + smartlist_clear(diff); + + /* Ranges must be numeric only and cannot contain spaces. */ + smartlist_add_linecpy(diff, area, "0, 4d"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an ed command was missing a range " + "end line number."); + + smartlist_clear(diff); + + /* '+' is not a number. */ + smartlist_add_linecpy(diff, area, "+0,4d"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an ed command was missing a line number"); + + smartlist_clear(diff); + + /* range duplication */ + smartlist_add_linecpy(diff, area, "0,4d,5d"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an ed command longer than one char was " + "found"); + + smartlist_clear(diff); + + /* space before command */ + smartlist_add_linecpy(diff, area, "0,4 d"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an ed command longer than one char was " + "found"); + + smartlist_clear(diff); + + /* space inside number */ + smartlist_add_linecpy(diff, area, "0,4 5d"); + mock_clean_saved_logs(); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("an ed command longer than one char was " + "found"); + + smartlist_clear(diff); + + /* Test appending text, 'a'. */ + consensus_split_lines(diff, "3a\nU\nO\n.\n0a\nV\n.\n", area); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_NE, cons2); + tt_int_op(8, OP_EQ, smartlist_len(cons2)); + tt_str_eq_line("V", smartlist_get(cons2, 0)); + tt_str_eq_line("A", smartlist_get(cons2, 1)); + tt_str_eq_line("B", smartlist_get(cons2, 2)); + tt_str_eq_line("C", smartlist_get(cons2, 3)); + tt_str_eq_line("U", smartlist_get(cons2, 4)); + tt_str_eq_line("O", smartlist_get(cons2, 5)); + tt_str_eq_line("D", smartlist_get(cons2, 6)); + tt_str_eq_line("E", smartlist_get(cons2, 7)); + + smartlist_clear(diff); + smartlist_free(cons2); + + /* Test deleting text, 'd'. */ + consensus_split_lines(diff, "4d\n1,2d\n", area); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_NE, cons2); + tt_int_op(2, OP_EQ, smartlist_len(cons2)); + tt_str_eq_line("C", smartlist_get(cons2, 0)); + tt_str_eq_line("E", smartlist_get(cons2, 1)); + + smartlist_clear(diff); + smartlist_free(cons2); + + /* Test changing text, 'c'. */ + consensus_split_lines(diff, "4c\nT\nX\n.\n1,2c\nM\n.\n", area); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_NE, cons2); + tt_int_op(5, OP_EQ, smartlist_len(cons2)); + tt_str_eq_line("M", smartlist_get(cons2, 0)); + tt_str_eq_line("C", smartlist_get(cons2, 1)); + tt_str_eq_line("T", smartlist_get(cons2, 2)); + tt_str_eq_line("X", smartlist_get(cons2, 3)); + tt_str_eq_line("E", smartlist_get(cons2, 4)); + + smartlist_clear(diff); + smartlist_free(cons2); + + /* Test 'a', 'd' and 'c' together. */ + consensus_split_lines(diff, "4c\nT\nX\n.\n2d\n0a\nM\n.\n", area); + cons2 = apply_ed_diff(cons1, diff, 0); + tt_ptr_op(NULL, OP_NE, cons2); + tt_int_op(6, OP_EQ, smartlist_len(cons2)); + tt_str_eq_line("M", smartlist_get(cons2, 0)); + tt_str_eq_line("A", smartlist_get(cons2, 1)); + tt_str_eq_line("C", smartlist_get(cons2, 2)); + tt_str_eq_line("T", smartlist_get(cons2, 3)); + tt_str_eq_line("X", smartlist_get(cons2, 4)); + tt_str_eq_line("E", smartlist_get(cons2, 5)); + + done: + teardown_capture_of_logs(); + smartlist_free(cons1); + smartlist_free(cons2); + smartlist_free(diff); + memarea_drop_all(area); +} + +static void +test_consdiff_gen_diff(void *arg) +{ + char *cons1_str=NULL, *cons2_str=NULL; + smartlist_t *cons1=NULL, *cons2=NULL, *diff=NULL; + consensus_digest_t digests1, digests2; + memarea_t *area = memarea_new(); + (void)arg; + cons1 = smartlist_new(); + cons2 = smartlist_new(); + + /* Identity hashes are not sorted properly, return NULL. + * Already tested in gen_ed_diff, but see that a NULL ed diff also makes + * gen_diff return NULL. */ + cons1_str = tor_strdup( + "network-status-version foo\n" + "r name bbbbbbbbbbbbbbbbb etc\nfoo\n" + "r name aaaaaaaaaaaaaaaaa etc\nbar\n" + "directory-signature foo bar\nbar\n" + ); + cons2_str = tor_strdup( + "network-status-version foo\n" + "r name aaaaaaaaaaaaaaaaa etc\nfoo\n" + "r name ccccccccccccccccc etc\nbar\n" + "directory-signature foo bar\nbar\n" + ); + + tt_int_op(0, OP_EQ, + consensus_compute_digest_as_signed(cons1_str, &digests1)); + tt_int_op(0, OP_EQ, + consensus_compute_digest(cons2_str, &digests2)); + + consensus_split_lines(cons1, cons1_str, area); + consensus_split_lines(cons2, cons2_str, area); + + diff = consdiff_gen_diff(cons1, cons2, &digests1, &digests2, area); + tt_ptr_op(NULL, OP_EQ, diff); + + /* Check that the headers are done properly. */ + tor_free(cons1_str); + cons1_str = tor_strdup( + "network-status-version foo\n" + "r name ccccccccccccccccc etc\nfoo\n" + "r name eeeeeeeeeeeeeeeee etc\nbar\n" + "directory-signature foo bar\nbar\n" + ); + tt_int_op(0, OP_EQ, + consensus_compute_digest_as_signed(cons1_str, &digests1)); + smartlist_clear(cons1); + consensus_split_lines(cons1, cons1_str, area); + diff = consdiff_gen_diff(cons1, cons2, &digests1, &digests2, area); + tt_ptr_op(NULL, OP_NE, diff); + tt_int_op(11, OP_EQ, smartlist_len(diff)); + tt_assert(line_str_eq(smartlist_get(diff, 0), + "network-status-diff-version 1")); + tt_assert(line_str_eq(smartlist_get(diff, 1), "hash " + "95D70F5A3CC65F920AA8B44C4563D7781A082674329661884E19E94B79D539C2 " + "7AFECEFA4599BA33D603653E3D2368F648DF4AC4723929B0F7CF39281596B0C1")); + tt_assert(line_str_eq(smartlist_get(diff, 2), "6,$d")); + tt_assert(line_str_eq(smartlist_get(diff, 3), "3,4c")); + tt_assert(line_str_eq(smartlist_get(diff, 4), "bar")); + tt_assert(line_str_eq(smartlist_get(diff, 5), + "directory-signature foo bar")); + tt_assert(line_str_eq(smartlist_get(diff, 6), + ".")); + tt_assert(line_str_eq(smartlist_get(diff, 7), "1a")); + tt_assert(line_str_eq(smartlist_get(diff, 8), + "r name aaaaaaaaaaaaaaaaa etc")); + tt_assert(line_str_eq(smartlist_get(diff, 9), "foo")); + tt_assert(line_str_eq(smartlist_get(diff, 10), ".")); + + /* TODO: small real use-cases, i.e. consensuses. */ + + done: + tor_free(cons1_str); + tor_free(cons2_str); + smartlist_free(cons1); + smartlist_free(cons2); + smartlist_free(diff); + memarea_drop_all(area); +} + +static void +test_consdiff_apply_diff(void *arg) +{ + smartlist_t *cons1=NULL, *diff=NULL; + char *cons1_str=NULL, *cons2 = NULL; + consensus_digest_t digests1; + (void)arg; + memarea_t *area = memarea_new(); + cons1 = smartlist_new(); + diff = smartlist_new(); + setup_capture_of_logs(LOG_INFO); + + cons1_str = tor_strdup( + "network-status-version foo\n" + "r name ccccccccccccccccc etc\nfoo\n" + "r name eeeeeeeeeeeeeeeee etc\nbar\n" + "directory-signature foo bar\nbar\n" + ); + tt_int_op(0, OP_EQ, + consensus_compute_digest(cons1_str, &digests1)); + consensus_split_lines(cons1, cons1_str, area); + + /* diff doesn't have enough lines. */ + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("too short") + + /* first line doesn't match format-version string. */ + smartlist_add_linecpy(diff, area, "foo-bar"); + smartlist_add_linecpy(diff, area, "header-line"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("format is not known") + + /* The first word of the second header line is not "hash". */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "word a b"); + smartlist_add_linecpy(diff, area, "x"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("does not include the necessary digests") + + /* Wrong number of words after "hash". */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash a b c"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("does not include the necessary digests") + + /* base16 digests do not have the expected length. */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash aaa bbb"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("includes base16-encoded digests of " + "incorrect size") + + /* base16 digests contain non-base16 characters. */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash" + " ????????????????????????????????????????????????????????????????" + " ----------------------------------------------------------------"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("includes malformed digests") + + /* Invalid ed diff. + * As tested in apply_ed_diff, but check that apply_diff does return NULL if + * the ed diff can't be applied. */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash" + /* sha3 of cons1. */ + " 06646D6CF563A41869D3B02E73254372AE3140046C5E7D83C9F71E54976AF9B4" + /* sha256 of cons2. */ + " 635D34593020C08E5ECD865F9986E29D50028EFA62843766A8197AD228A7F6AA"); + smartlist_add_linecpy(diff, area, "foobar"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_single_log_msg_containing("because an ed command was missing a line " + "number") + + /* Base consensus doesn't match its digest as found in the diff. */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash" + /* bogus sha256. */ + " 3333333333333333333333333333333333333333333333333333333333333333" + /* sha256 of cons2. */ + " 635D34593020C08E5ECD865F9986E29D50028EFA62843766A8197AD228A7F6AA"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_log_msg_containing("base consensus doesn't match the digest " + "as found"); + + /* Resulting consensus doesn't match its digest as found in the diff. */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash" + /* sha3 of cons1. */ + " 06646D6CF563A41869D3B02E73254372AE3140046C5E7D83C9F71E54976AF9B4" + /* bogus sha3. */ + " 3333333333333333333333333333333333333333333333333333333333333333"); + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_log_msg_containing("resulting consensus doesn't match the " + "digest as found"); + +#if 0 + /* XXXX No longer possible, since we aren't using the other algorithm. */ + /* Resulting consensus digest cannot be computed */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash" + /* sha3 of cons1. */ + " 06646D6CF563A41869D3B02E73254372AE3140046C5E7D83C9F71E54976AF9B4" + /* bogus sha3. */ + " 3333333333333333333333333333333333333333333333333333333333333333"); + smartlist_add_linecpy(diff, area, "1,2d"); // remove starting line + mock_clean_saved_logs(); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_EQ, cons2); + expect_log_msg_containing("Could not compute digests of the consensus " + "resulting from applying a consensus diff."); +#endif + + /* Very simple test, only to see that nothing errors. */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash" + /* sha3 of cons1. */ + " 06646D6CF563A41869D3B02E73254372AE3140046C5E7D83C9F71E54976AF9B4" + /* sha3 of cons2. */ + " 90A418881B2FCAB3D9E60EE02E4D666D56CFA38F8A3B7AA3E0ADBA530DDA9353"); + smartlist_add_linecpy(diff, area, "3c"); + smartlist_add_linecpy(diff, area, "sample"); + smartlist_add_linecpy(diff, area, "."); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_NE, cons2); + tt_str_op( + "network-status-version foo\n" + "r name ccccccccccccccccc etc\nsample\n" + "r name eeeeeeeeeeeeeeeee etc\nbar\n" + "directory-signature foo bar\nbar\n", OP_EQ, + cons2); + tor_free(cons2); + + /* Check that lowercase letters in base16-encoded digests work too. */ + smartlist_clear(diff); + smartlist_add_linecpy(diff, area, "network-status-diff-version 1"); + smartlist_add_linecpy(diff, area, "hash" + /* sha3 of cons1. */ + " 06646d6cf563a41869d3b02e73254372ae3140046c5e7d83c9f71e54976af9b4" + /* sha3 of cons2. */ + " 90a418881b2fcab3d9e60ee02e4d666d56cfa38f8a3b7aa3e0adba530dda9353"); + smartlist_add_linecpy(diff, area, "3c"); + smartlist_add_linecpy(diff, area, "sample"); + smartlist_add_linecpy(diff, area, "."); + cons2 = consdiff_apply_diff(cons1, diff, &digests1); + tt_ptr_op(NULL, OP_NE, cons2); + tt_str_op( + "network-status-version foo\n" + "r name ccccccccccccccccc etc\nsample\n" + "r name eeeeeeeeeeeeeeeee etc\nbar\n" + "directory-signature foo bar\nbar\n", OP_EQ, + cons2); + tor_free(cons2); + + smartlist_clear(diff); + + done: + teardown_capture_of_logs(); + tor_free(cons1_str); + smartlist_free(cons1); + smartlist_free(diff); + memarea_drop_all(area); +} + +#define CONSDIFF_LEGACY(name) \ + { #name, test_consdiff_ ## name , 0, NULL, NULL } + +struct testcase_t consdiff_tests[] = { + CONSDIFF_LEGACY(smartlist_slice), + CONSDIFF_LEGACY(smartlist_slice_string_pos), + CONSDIFF_LEGACY(lcs_lengths), + CONSDIFF_LEGACY(trim_slices), + CONSDIFF_LEGACY(set_changed), + CONSDIFF_LEGACY(calc_changes), + CONSDIFF_LEGACY(get_id_hash), + CONSDIFF_LEGACY(is_valid_router_entry), + CONSDIFF_LEGACY(next_router), + CONSDIFF_LEGACY(base64cmp), + CONSDIFF_LEGACY(gen_ed_diff), + CONSDIFF_LEGACY(apply_ed_diff), + CONSDIFF_LEGACY(gen_diff), + CONSDIFF_LEGACY(apply_diff), + END_OF_TESTCASES +}; + diff --git a/src/test/test_consdiffmgr.c b/src/test/test_consdiffmgr.c new file mode 100644 index 0000000000..963a6e427a --- /dev/null +++ b/src/test/test_consdiffmgr.c @@ -0,0 +1,896 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CONSDIFFMGR_PRIVATE + +#include "or.h" +#include "config.h" +#include "conscache.h" +#include "consdiff.h" +#include "consdiffmgr.h" +#include "cpuworker.h" +#include "networkstatus.h" +#include "routerparse.h" +#include "workqueue.h" + +#include "test.h" +#include "log_test_helpers.h" + +// ============================== Setup/teardown the consdiffmgr +// These functions get run before/after each test in this module + +static void * +consdiffmgr_test_setup(const struct testcase_t *arg) +{ + (void)arg; + char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cdm")); + tor_free(get_options_mutable()->DataDirectory); + get_options_mutable()->DataDirectory = ddir_fname; // now owns the pointer. + check_private_dir(ddir_fname, CPD_CREATE, NULL); + + consdiff_cfg_t consdiff_cfg = { 300 }; + consdiffmgr_configure(&consdiff_cfg); + return (void *)1; // must return something non-null. +} +static int +consdiffmgr_test_teardown(const struct testcase_t *arg, void *ignore) +{ + (void)arg; + (void)ignore; + consdiffmgr_free_all(); + return 1; +} +static struct testcase_setup_t setup_diffmgr = { + consdiffmgr_test_setup, + consdiffmgr_test_teardown +}; + +// ============================== NS faking functions +// These functions are for making quick fake consensus objects and +// strings that are just good enough for consdiff and consdiffmgr. + +static networkstatus_t * +fake_ns_new(consensus_flavor_t flav, time_t valid_after) +{ + networkstatus_t *ns = tor_malloc_zero(sizeof(networkstatus_t)); + ns->type = NS_TYPE_CONSENSUS; + ns->flavor = flav; + ns->valid_after = valid_after; + return ns; +} + +static char * +fake_ns_body_new(consensus_flavor_t flav, time_t valid_after) +{ + const char *flavor_string = flav == FLAV_NS ? "" : " microdesc"; + char valid_after_string[ISO_TIME_LEN+1]; + + format_iso_time(valid_after_string, valid_after); + char *random_stuff = crypto_random_hostname(3, 25, "junk ", ""); + char *random_stuff2 = crypto_random_hostname(3, 10, "", ""); + + char *consensus; + tor_asprintf(&consensus, + "network-status-version 3%s\n" + "vote-status consensus\n" + "valid-after %s\n" + "r name ccccccccccccccccc etc\nsample\n" + "r name eeeeeeeeeeeeeeeee etc\nbar\n" + "%s\n" + "directory-signature hello-there\n" + "directory-signature %s\n", + flavor_string, + valid_after_string, + random_stuff, + random_stuff2); + tor_free(random_stuff); + tor_free(random_stuff2); + return consensus; +} + +// ============================== Cpuworker mocking code +// These mocking functions and types capture the cpuworker calls +// so we can inspect them and run them in the main thread. +static smartlist_t *fake_cpuworker_queue = NULL; +typedef struct fake_work_queue_ent_t { + enum workqueue_reply_t (*fn)(void *, void *); + void (*reply_fn)(void *); + void *arg; +} fake_work_queue_ent_t; +static struct workqueue_entry_s * +mock_cpuworker_queue_work(workqueue_priority_t prio, + enum workqueue_reply_t (*fn)(void *, void *), + void (*reply_fn)(void *), + void *arg) +{ + (void) prio; + + if (! fake_cpuworker_queue) + fake_cpuworker_queue = smartlist_new(); + + fake_work_queue_ent_t *ent = tor_malloc_zero(sizeof(*ent)); + ent->fn = fn; + ent->reply_fn = reply_fn; + ent->arg = arg; + smartlist_add(fake_cpuworker_queue, ent); + return (struct workqueue_entry_s *)ent; +} +static int +mock_cpuworker_run_work(void) +{ + if (! fake_cpuworker_queue) + return 0; + SMARTLIST_FOREACH(fake_cpuworker_queue, fake_work_queue_ent_t *, ent, { + enum workqueue_reply_t r = ent->fn(NULL, ent->arg); + if (r != WQ_RPL_REPLY) + return -1; + }); + return 0; +} +static void +mock_cpuworker_handle_replies(void) +{ + if (! fake_cpuworker_queue) + return; + SMARTLIST_FOREACH(fake_cpuworker_queue, fake_work_queue_ent_t *, ent, { + ent->reply_fn(ent->arg); + tor_free(ent); + }); + smartlist_free(fake_cpuworker_queue); + fake_cpuworker_queue = NULL; +} + +// ============================== Other helpers + +static consdiff_status_t +lookup_diff_from(consensus_cache_entry_t **out, + consensus_flavor_t flav, + const char *str1) +{ + uint8_t digest[DIGEST256_LEN]; + if (router_get_networkstatus_v3_sha3_as_signed(digest, str1)<0) { + TT_FAIL(("Unable to compute sha3-as-signed")); + return CONSDIFF_NOT_FOUND; + } + return consdiffmgr_find_diff_from(out, flav, + DIGEST_SHA3_256, digest, sizeof(digest), + NO_METHOD); +} + +static int +lookup_apply_and_verify_diff(consensus_flavor_t flav, + const char *str1, + const char *str2) +{ + consensus_cache_entry_t *ent = NULL; + consdiff_status_t status = lookup_diff_from(&ent, flav, str1); + if (ent == NULL || status != CONSDIFF_AVAILABLE) { + return -1; + } + + consensus_cache_entry_incref(ent); + size_t size; + char *diff_string = NULL; + int r = uncompress_or_copy(&diff_string, &size, ent); + consensus_cache_entry_decref(ent); + if (diff_string == NULL || r < 0) + return -1; + + char *applied = consensus_diff_apply(str1, diff_string); + tor_free(diff_string); + if (applied == NULL) + return -1; + + int match = !strcmp(applied, str2); + tor_free(applied); + return match ? 0 : -1; +} + +static void +cdm_reload(void) +{ + consdiffmgr_free_all(); + cdm_cache_get(); + consdiffmgr_rescan(); +} + +// ============================== Beginning of tests + +#if 0 +static int got_failure = 0; +static void +got_assertion_failure(void) +{ + ++got_failure; +} + +/* XXXX This test won't work, because there is currently no way to actually + * XXXX capture a real assertion failure. */ +static void +test_consdiffmgr_init_failure(void *arg) +{ + (void)arg; + // Capture assertions and bugs. + + /* As in ...test_setup, but do not create the datadir. The missing directory + * will cause a failure. */ + char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cdm")); + tor_free(get_options_mutable()->DataDirectory); + get_options_mutable()->DataDirectory = ddir_fname; // now owns the pointer. + + consdiff_cfg_t consdiff_cfg = { 7200, 300 }; + + tor_set_failed_assertion_callback(got_assertion_failure); + tor_capture_bugs_(1); + consdiffmgr_configure(&consdiff_cfg); // This should fail. + tt_int_op(got_failure, OP_EQ, 1); + const smartlist_t *bugs = tor_get_captured_bug_log_(); + tt_int_op(smartlist_len(bugs), OP_EQ, 1); + + done: + tor_end_capture_bugs_(); +} +#endif + +static void +test_consdiffmgr_sha3_helper(void *arg) +{ + (void) arg; + consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier + config_line_t *lines = NULL; + char *mem_op_hex_tmp = NULL; + config_line_prepend(&lines, "good-sha", + "F00DF00DF00DF00DF00DF00DF00DF00D" + "F00DF00DF00DF00DF00DF00DF00DF00D"); + config_line_prepend(&lines, "short-sha", + "F00DF00DF00DF00DF00DF00DF00DF00D" + "F00DF00DF00DF00DF00DF00DF00DF0"); + config_line_prepend(&lines, "long-sha", + "F00DF00DF00DF00DF00DF00DF00DF00D" + "F00DF00DF00DF00DF00DF00DF00DF00DF00D"); + config_line_prepend(&lines, "not-sha", + "F00DF00DF00DF00DF00DF00DF00DF00D" + "F00DF00DF00DF00DF00DF00DF00DXXXX"); + consensus_cache_entry_t *ent = + consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8); + + uint8_t buf[DIGEST256_LEN]; + tt_int_op(-1, OP_EQ, cdm_entry_get_sha3_value(buf, NULL, "good-sha")); + tt_int_op(0, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "good-sha")); + test_memeq_hex(buf, "F00DF00DF00DF00DF00DF00DF00DF00D" + "F00DF00DF00DF00DF00DF00DF00DF00D"); + + tt_int_op(-1, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "missing-sha")); + tt_int_op(-2, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "short-sha")); + tt_int_op(-2, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "long-sha")); + tt_int_op(-2, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "not-sha")); + + done: + consensus_cache_entry_decref(ent); + config_free_lines(lines); + tor_free(mem_op_hex_tmp); +} + +static void +test_consdiffmgr_add(void *arg) +{ + (void) arg; + time_t now = approx_time(); + + char *body = NULL; + + consensus_cache_entry_t *ent = NULL; + networkstatus_t *ns_tmp = fake_ns_new(FLAV_NS, now); + const char *dummy = "foo"; + int r = consdiffmgr_add_consensus(dummy, ns_tmp); + tt_int_op(r, OP_EQ, 0); + + /* If we add it again, it won't work */ + setup_capture_of_logs(LOG_INFO); + dummy = "bar"; + r = consdiffmgr_add_consensus(dummy, ns_tmp); + tt_int_op(r, OP_EQ, -1); + expect_single_log_msg_containing("We already have a copy of that " + "consensus"); + mock_clean_saved_logs(); + + /* But it will work fine if the flavor is different */ + dummy = "baz"; + ns_tmp->flavor = FLAV_MICRODESC; + r = consdiffmgr_add_consensus(dummy, ns_tmp); + tt_int_op(r, OP_EQ, 0); + + /* And it will work fine if the time is different */ + dummy = "quux"; + ns_tmp->flavor = FLAV_NS; + ns_tmp->valid_after = now - 60; + r = consdiffmgr_add_consensus(dummy, ns_tmp); + tt_int_op(r, OP_EQ, 0); + + /* If we add one a long long time ago, it will fail. */ + dummy = "xyzzy"; + ns_tmp->valid_after = 86400 * 100; /* A few months into 1970 */ + r = consdiffmgr_add_consensus(dummy, ns_tmp); + tt_int_op(r, OP_EQ, -1); + expect_log_msg_containing("it's too old."); + + /* Try looking up a consensuses. */ + ent = cdm_cache_lookup_consensus(FLAV_NS, now-60); + tt_assert(ent); + consensus_cache_entry_incref(ent); + size_t s; + r = uncompress_or_copy(&body, &s, ent); + tt_int_op(r, OP_EQ, 0); + tt_int_op(s, OP_EQ, 4); + tt_mem_op(body, OP_EQ, "quux", 4); + + /* Try looking up another entry, but fail */ + tt_assert(NULL == cdm_cache_lookup_consensus(FLAV_MICRODESC, now-60)); + tt_assert(NULL == cdm_cache_lookup_consensus(FLAV_NS, now-61)); + + done: + networkstatus_vote_free(ns_tmp); + teardown_capture_of_logs(); + consensus_cache_entry_decref(ent); + tor_free(body); +} + +static void +test_consdiffmgr_make_diffs(void *arg) +{ + (void)arg; + networkstatus_t *ns = NULL; + char *ns_body = NULL, *md_ns_body = NULL, *md_ns_body_2 = NULL; + char *applied = NULL, *diff_text = NULL; + time_t now = approx_time(); + int r; + consensus_cache_entry_t *diff = NULL; + uint8_t md_ns_sha3[DIGEST256_LEN]; + consdiff_status_t diff_status; + + MOCK(cpuworker_queue_work, mock_cpuworker_queue_work); + + // Try rescan with no consensuses: shouldn't crash or queue work. + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue); + + // Make two consensuses, 1 hour sec ago. + ns = fake_ns_new(FLAV_NS, now-3600); + ns_body = fake_ns_body_new(FLAV_NS, now-3600); + r = consdiffmgr_add_consensus(ns_body, ns); + networkstatus_vote_free(ns); + tor_free(ns_body); + tt_int_op(r, OP_EQ, 0); + + ns = fake_ns_new(FLAV_MICRODESC, now-3600); + md_ns_body = fake_ns_body_new(FLAV_MICRODESC, now-3600); + r = consdiffmgr_add_consensus(md_ns_body, ns); + router_get_networkstatus_v3_sha3_as_signed(md_ns_sha3, md_ns_body); + networkstatus_vote_free(ns); + tt_int_op(r, OP_EQ, 0); + + // No diffs will be generated. + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue); + + // Add a MD consensus from 45 minutes ago. This should cause one diff + // worth of work to get queued. + ns = fake_ns_new(FLAV_MICRODESC, now-45*60); + md_ns_body_2 = fake_ns_body_new(FLAV_MICRODESC, now-45*60); + r = consdiffmgr_add_consensus(md_ns_body_2, ns); + networkstatus_vote_free(ns); + tt_int_op(r, OP_EQ, 0); + + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue); + tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue)); + diff_status = consdiffmgr_find_diff_from(&diff, FLAV_MICRODESC, + DIGEST_SHA3_256, + md_ns_sha3, DIGEST256_LEN, + NO_METHOD); + tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ, diff_status); + + // Now run that process and get the diff. + r = mock_cpuworker_run_work(); + tt_int_op(r, OP_EQ, 0); + mock_cpuworker_handle_replies(); + + // At this point we should be able to get that diff. + diff_status = consdiffmgr_find_diff_from(&diff, FLAV_MICRODESC, + DIGEST_SHA3_256, + md_ns_sha3, DIGEST256_LEN, + NO_METHOD); + tt_int_op(CONSDIFF_AVAILABLE, OP_EQ, diff_status); + tt_assert(diff); + + /* Make sure applying the diff actually works */ + const uint8_t *diff_body; + size_t diff_size; + r = consensus_cache_entry_get_body(diff, &diff_body, &diff_size); + tt_int_op(r, OP_EQ, 0); + diff_text = tor_memdup_nulterm(diff_body, diff_size); + applied = consensus_diff_apply(md_ns_body, diff_text); + tt_assert(applied); + tt_str_op(applied, OP_EQ, md_ns_body_2); + + /* Rescan again: no more work to do. */ + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue); + + done: + tor_free(md_ns_body); + tor_free(md_ns_body_2); + tor_free(diff_text); + tor_free(applied); +} + +static void +test_consdiffmgr_diff_rules(void *arg) +{ + (void)arg; +#define N 6 + char *md_body[N], *ns_body[N]; + networkstatus_t *md_ns[N], *ns_ns[N]; + int i; + + MOCK(cpuworker_queue_work, mock_cpuworker_queue_work); + + /* Create a bunch of consensus things at 15-second intervals. */ + time_t start = approx_time() - 120; + for (i = 0; i < N; ++i) { + time_t when = start + i * 15; + md_body[i] = fake_ns_body_new(FLAV_MICRODESC, when); + ns_body[i] = fake_ns_body_new(FLAV_NS, when); + md_ns[i] = fake_ns_new(FLAV_MICRODESC, when); + ns_ns[i] = fake_ns_new(FLAV_NS, when); + } + + /* For the MD consensuses: add 4 of them, and make sure that + * diffs are created to one consensus (the most recent) only. */ + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[1], md_ns[1])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[2], md_ns[2])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[3], md_ns[3])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[4], md_ns[4])); + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue); + tt_int_op(3, OP_EQ, smartlist_len(fake_cpuworker_queue)); + tt_int_op(0, OP_EQ, mock_cpuworker_run_work()); + mock_cpuworker_handle_replies(); + tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue); + + /* For the NS consensuses: add 3, generate, and add one older one and + * make sure that older one is the only one whose diff is generated */ + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[0], ns_ns[0])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[1], ns_ns[1])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[5], ns_ns[5])); + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue); + tt_int_op(2, OP_EQ, smartlist_len(fake_cpuworker_queue)); + tt_int_op(0, OP_EQ, mock_cpuworker_run_work()); + mock_cpuworker_handle_replies(); + + /* At this point, we should actually have working diffs! */ + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_NS, ns_body[0], ns_body[5])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_NS, ns_body[1], ns_body[5])); + + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[4])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[2], md_body[4])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[3], md_body[4])); + + /* Self-to-self diff won't be present */ + consensus_cache_entry_t *ent; + tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ, + lookup_diff_from(&ent, FLAV_NS, ns_body[5])); + /* No diff from 2 has been added yet */ + tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ, + lookup_diff_from(&ent, FLAV_NS, ns_body[2])); + /* No diff arriving at old things. */ + tt_int_op(-1, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[2])); + /* No backwards diff */ + tt_int_op(-1, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[4], md_body[3])); + + /* Now, an update: add number 2 and make sure it's the only one whose diff + * is regenerated. */ + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[2], ns_ns[2])); + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue); + tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue)); + tt_int_op(0, OP_EQ, mock_cpuworker_run_work()); + mock_cpuworker_handle_replies(); + + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_NS, ns_body[2], ns_body[5])); + + /* Finally: reload, and make sure that the information is still indexed */ + cdm_reload(); + + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_NS, ns_body[0], ns_body[5])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_NS, ns_body[2], ns_body[5])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_NS, ns_body[1], ns_body[5])); + + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[4])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[2], md_body[4])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[3], md_body[4])); + + done: + for (i = 0; i < N; ++i) { + tor_free(md_body[i]); + tor_free(ns_body[i]); + networkstatus_vote_free(md_ns[i]); + networkstatus_vote_free(ns_ns[i]); + } + UNMOCK(cpuworker_queue_work); +#undef N +} + +static void +test_consdiffmgr_diff_failure(void *arg) +{ + (void)arg; + MOCK(cpuworker_queue_work, mock_cpuworker_queue_work); + + /* We're going to make sure that if we have a bogus request where + * we can't actually compute a diff, the world must not end. */ + networkstatus_t *ns1 = NULL; + networkstatus_t *ns2 = NULL; + int r; + + ns1 = fake_ns_new(FLAV_NS, approx_time()-100); + ns2 = fake_ns_new(FLAV_NS, approx_time()-50); + r = consdiffmgr_add_consensus("foo bar baz\n", ns1); + tt_int_op(r, OP_EQ, 0); + // We refuse to compute a diff to or from a line holding only a single dot. + // We can add it here, though. + r = consdiffmgr_add_consensus("foo bar baz\n.\n.\n", ns2); + tt_int_op(r, OP_EQ, 0); + + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue); + setup_capture_of_logs(LOG_WARN); + tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue)); + tt_int_op(0, OP_EQ, mock_cpuworker_run_work()); + expect_single_log_msg_containing("one of the lines to be added is \".\"."); + mock_clean_saved_logs(); + mock_cpuworker_handle_replies(); + expect_single_log_msg_containing("Worker was unable to compute consensus " + "diff from "); + + /* Make sure the diff is not present */ + consensus_cache_entry_t *ent; + tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ, + lookup_diff_from(&ent, FLAV_NS, "foo bar baz\n")); + + done: + teardown_capture_of_logs(); + UNMOCK(cpuworker_queue_work); + networkstatus_vote_free(ns1); + networkstatus_vote_free(ns2); +} + +static void +test_consdiffmgr_diff_pending(void *arg) +{ +#define N 3 + (void)arg; + char *md_body[N]; + networkstatus_t *md_ns[N]; + time_t start = approx_time() - 120; + int i; + for (i = 0; i < N; ++i) { + time_t when = start + i * 30; + md_body[i] = fake_ns_body_new(FLAV_MICRODESC, when); + md_ns[i] = fake_ns_new(FLAV_MICRODESC, when); + } + + MOCK(cpuworker_queue_work, mock_cpuworker_queue_work); + + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[1], md_ns[1])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[2], md_ns[2])); + /* Make a diff */ + consdiffmgr_rescan(); + tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue)); + + /* Look it up. Is it pending? */ + consensus_cache_entry_t *ent = NULL; + consdiff_status_t diff_status; + diff_status = lookup_diff_from(&ent, FLAV_MICRODESC, md_body[1]); + tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ, diff_status); + tt_ptr_op(ent, OP_EQ, NULL); + + /* Add another old consensus. only one new diff should launch! */ + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[0], md_ns[0])); + consdiffmgr_rescan(); + tt_int_op(2, OP_EQ, smartlist_len(fake_cpuworker_queue)); + + tt_int_op(0, OP_EQ, mock_cpuworker_run_work()); + mock_cpuworker_handle_replies(); + + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[0], md_body[2])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[2])); + + done: + UNMOCK(cpuworker_queue_work); + for (i = 0; i < N; ++i) { + tor_free(md_body[i]); + networkstatus_vote_free(md_ns[i]); + } +#undef N +} + +static void +test_consdiffmgr_cleanup_old(void *arg) +{ + (void)arg; + config_line_t *labels = NULL; + consensus_cache_entry_t *ent = NULL; + consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier + + /* This item will be will be cleanable because it has a valid-after + * time far in the past. */ + config_line_prepend(&labels, "document-type", "confribble-blarg"); + config_line_prepend(&labels, "consensus-valid-after", + "1980-10-10T10:10:10"); + ent = consensus_cache_add(cache, labels, (const uint8_t*)"Foo", 3); + tt_assert(ent); + consensus_cache_entry_decref(ent); + + setup_capture_of_logs(LOG_DEBUG); + tt_int_op(1, OP_EQ, consdiffmgr_cleanup()); + expect_log_msg_containing("Deleting entry because its consensus-valid-" + "after value (1980-10-10T10:10:10) was too old"); + + done: + teardown_capture_of_logs(); + config_free_lines(labels); +} + +static void +test_consdiffmgr_cleanup_bad_valid_after(void *arg) +{ + /* This will seem cleanable, but isn't, because its valid-after time is + * misformed. */ + + (void)arg; + config_line_t *labels = NULL; + consensus_cache_entry_t *ent = NULL; + consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier + + config_line_prepend(&labels, "document-type", "consensus"); + config_line_prepend(&labels, "consensus-valid-after", + "whan that aprille with his shoures soote"); // (~1385?) + ent = consensus_cache_add(cache, labels, (const uint8_t*)"Foo", 3); + tt_assert(ent); + consensus_cache_entry_decref(ent); + + setup_capture_of_logs(LOG_DEBUG); + tt_int_op(0, OP_EQ, consdiffmgr_cleanup()); + expect_log_msg_containing("Ignoring entry because its consensus-valid-" + "after value (\"whan that aprille with his " + "shoures soote\") was unparseable"); + + done: + teardown_capture_of_logs(); + config_free_lines(labels); +} + +static void +test_consdiffmgr_cleanup_no_valid_after(void *arg) +{ + (void)arg; + config_line_t *labels = NULL; + consensus_cache_entry_t *ent = NULL; + consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier + + /* This item will be will be uncleanable because it has no recognized + * valid-after. */ + config_line_prepend(&labels, "document-type", "consensus"); + config_line_prepend(&labels, "confrooble-voolid-oofter", + "2010-10-10T09:08:07"); + ent = consensus_cache_add(cache, labels, (const uint8_t*)"Foo", 3); + tt_assert(ent); + consensus_cache_entry_decref(ent); + + setup_capture_of_logs(LOG_DEBUG); + tt_int_op(0, OP_EQ, consdiffmgr_cleanup()); + expect_log_msg_containing("Ignoring entry because it had no consensus-" + "valid-after label"); + + done: + teardown_capture_of_logs(); + config_free_lines(labels); +} + +static void +test_consdiffmgr_cleanup_old_diffs(void *arg) +{ + (void)arg; +#define N 4 + char *md_body[N]; + networkstatus_t *md_ns[N]; + int i; + consensus_cache_entry_t *hold_ent = NULL, *ent; + + /* Make sure that the cleanup function removes diffs to the not-most-recent + * consensus. */ + + MOCK(cpuworker_queue_work, mock_cpuworker_queue_work); + + /* Create a bunch of consensus things at 15-second intervals. */ + time_t start = approx_time() - 120; + for (i = 0; i < N; ++i) { + time_t when = start + i * 15; + md_body[i] = fake_ns_body_new(FLAV_MICRODESC, when); + md_ns[i] = fake_ns_new(FLAV_MICRODESC, when); + } + + /* add the first 3. */ + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[0], md_ns[0])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[1], md_ns[1])); + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[2], md_ns[2])); + /* Make diffs. */ + consdiffmgr_rescan(); + tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue); + tt_int_op(2, OP_EQ, smartlist_len(fake_cpuworker_queue)); + tt_int_op(0, OP_EQ, mock_cpuworker_run_work()); + mock_cpuworker_handle_replies(); + tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue); + + /* Nothing is deletable now */ + tt_int_op(0, OP_EQ, consdiffmgr_cleanup()); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[0], md_body[2])); + tt_int_op(0, OP_EQ, + lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[2])); + + tt_int_op(CONSDIFF_AVAILABLE, OP_EQ, + lookup_diff_from(&hold_ent, FLAV_MICRODESC, md_body[1])); + consensus_cache_entry_incref(hold_ent); // incref, so it is preserved. + + /* Now add an even-more-recent consensus; this should make all previous + * diffs deletable, and make delete */ + tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[3], md_ns[3])); + tt_int_op(2 * n_diff_compression_methods() + + (n_consensus_compression_methods() - 1) , OP_EQ, + consdiffmgr_cleanup()); + + tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ, + lookup_diff_from(&ent, FLAV_MICRODESC, md_body[0])); + /* This one is marked deletable but still in the hashtable */ + tt_int_op(CONSDIFF_AVAILABLE, OP_EQ, + lookup_diff_from(&ent, FLAV_MICRODESC, md_body[1])); + tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ, + lookup_diff_from(&ent, FLAV_MICRODESC, md_body[2])); + + /* Everything should be valid at this point */ + tt_int_op(0, OP_EQ, consdiffmgr_validate()); + + /* And if we recan NOW, we'll purge the hashtable of the entries, + * and launch attempts to generate new ones */ + consdiffmgr_rescan(); + tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ, + lookup_diff_from(&ent, FLAV_MICRODESC, md_body[0])); + tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ, + lookup_diff_from(&ent, FLAV_MICRODESC, md_body[1])); + tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ, + lookup_diff_from(&ent, FLAV_MICRODESC, md_body[2])); + + /* We're still holding on to this, though, so we can still map it! */ + const uint8_t *t1 = NULL; + size_t s; + int r = consensus_cache_entry_get_body(hold_ent, &t1, &s); + tt_int_op(r, OP_EQ, 0); + tt_assert(t1); + + done: + for (i = 0; i < N; ++i) { + tor_free(md_body[i]); + networkstatus_vote_free(md_ns[i]); + } + consensus_cache_entry_decref(hold_ent); + UNMOCK(cpuworker_queue_work); +#undef N +} + +static void +test_consdiffmgr_validate(void *arg) +{ + (void)arg; + config_line_t *lines = NULL; + consensus_cache_entry_t *ent = NULL; + consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier + smartlist_t *vals = smartlist_new(); + + /* Put these: objects in the cache: one with a good sha3, one with bad sha3, + * one with a wrong sha3, and one with no sha3. */ + config_line_prepend(&lines, "id", "wrong sha3"); + config_line_prepend(&lines, "sha3-digest", + "F00DF00DF00DF00DF00DF00DF00DF00D" + "F00DF00DF00DF00DF00DF00DF00DF00D"); + ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8); + consensus_cache_entry_decref(ent); + config_free_lines(lines); + lines = NULL; + + config_line_prepend(&lines, "id", "bad sha3"); + config_line_prepend(&lines, "sha3-digest", + "now is the winter of our dicotheque"); + ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8); + consensus_cache_entry_decref(ent); + config_free_lines(lines); + lines = NULL; + + config_line_prepend(&lines, "id", "no sha3"); + ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8); + consensus_cache_entry_decref(ent); + config_free_lines(lines); + lines = NULL; + + config_line_prepend(&lines, "id", "good sha3"); + config_line_prepend(&lines, "sha3-digest", + "8d8b1998616cd6b4c4055da8d38728dc" + "93c758d4131a53c7d81aa6337dee1c05"); + ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8); + consensus_cache_entry_decref(ent); + config_free_lines(lines); + lines = NULL; + + cdm_reload(); + cache = cdm_cache_get(); + tt_int_op(1, OP_EQ, consdiffmgr_validate()); + + consensus_cache_find_all(vals, cache, "id", "good sha3"); + tt_int_op(smartlist_len(vals), OP_EQ, 1); + smartlist_clear(vals); + + consensus_cache_find_all(vals, cache, "id", "no sha3"); + tt_int_op(smartlist_len(vals), OP_EQ, 1); + smartlist_clear(vals); + + consensus_cache_find_all(vals, cache, "id", "wrong sha3"); + tt_int_op(smartlist_len(vals), OP_EQ, 0); + consensus_cache_find_all(vals, cache, "id", "bad sha3"); + tt_int_op(smartlist_len(vals), OP_EQ, 0); + + done: + smartlist_free(vals); +} + +#define TEST(name) \ + { #name, test_consdiffmgr_ ## name , TT_FORK, &setup_diffmgr, NULL } + +struct testcase_t consdiffmgr_tests[] = { +#if 0 + { "init_failure", test_consdiffmgr_init_failure, TT_FORK, NULL, NULL }, +#endif + TEST(sha3_helper), + TEST(add), + TEST(make_diffs), + TEST(diff_rules), + TEST(diff_failure), + TEST(diff_pending), + TEST(cleanup_old), + TEST(cleanup_bad_valid_after), + TEST(cleanup_no_valid_after), + TEST(cleanup_old_diffs), + TEST(validate), + + // XXXX Test: non-cacheing cases of replyfn(). + + END_OF_TESTCASES +}; + diff --git a/src/test/test_containers.c b/src/test/test_containers.c index 41f3f873de..54484a2a91 100644 --- a/src/test/test_containers.c +++ b/src/test/test_containers.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_controller.c b/src/test/test_controller.c index d9c0a1eaac..592f91a988 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CONTROL_PRIVATE @@ -109,6 +109,45 @@ test_add_onion_helper_keyarg(void *arg) } static void +test_getinfo_helper_onion(void *arg) +{ + (void)arg; + control_connection_t dummy; + /* Get results out */ + char *answer = NULL; + const char *errmsg = NULL; + char *service_id = NULL; + int rt = 0; + + dummy.ephemeral_onion_services = NULL; + + /* successfully get an empty answer */ + rt = getinfo_helper_onions(&dummy, "onions/current", &answer, &errmsg); + tt_assert(rt == 0); + tt_str_op(answer, OP_EQ, ""); + tor_free(answer); + + /* successfully get an empty answer */ + rt = getinfo_helper_onions(&dummy, "onions/detached", &answer, &errmsg); + tt_assert(rt == 0); + tt_str_op(answer, OP_EQ, ""); + tor_free(answer); + + /* get an answer for one onion service */ + service_id = tor_strdup("dummy_onion_id"); + dummy.ephemeral_onion_services = smartlist_new(); + smartlist_add(dummy.ephemeral_onion_services, service_id); + rt = getinfo_helper_onions(&dummy, "onions/current", &answer, &errmsg); + tt_assert(rt == 0); + tt_str_op(answer, OP_EQ, "dummy_onion_id"); + + done: + tor_free(answer); + tor_free(service_id); + smartlist_free(dummy.ephemeral_onion_services); +} + +static void test_rend_service_parse_port_config(void *arg) { const char *sep = ","; @@ -1332,6 +1371,7 @@ test_download_status_bridge(void *arg) struct testcase_t controller_tests[] = { { "add_onion_helper_keyarg", test_add_onion_helper_keyarg, 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 }, { "add_onion_helper_clientauth", test_add_onion_helper_clientauth, 0, NULL, diff --git a/src/test/test_controller_events.c b/src/test/test_controller_events.c index 11e1e3dc8f..901ad7ab3d 100644 --- a/src/test/test_controller_events.c +++ b/src/test/test_controller_events.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CONNECTION_PRIVATE diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index d66ddccd4f..ec9d4e2709 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -15,9 +15,6 @@ #include "crypto_ed25519.h" #include "ed25519_vectors.inc" -#include <openssl/evp.h> -#include <openssl/rand.h> - /** Run unit tests for Diffie-Hellman functionality. */ static void test_crypto_dh(void *arg) @@ -331,38 +328,6 @@ test_crypto_rng_strongest(void *arg) #undef N } -/* Test for rectifying openssl RAND engine. */ -static void -test_crypto_rng_engine(void *arg) -{ - (void)arg; - RAND_METHOD dummy_method; - memset(&dummy_method, 0, sizeof(dummy_method)); - - /* We should be a no-op if we're already on RAND_OpenSSL */ - tt_int_op(0, ==, crypto_force_rand_ssleay()); - tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); - - /* We should correct the method if it's a dummy. */ - RAND_set_rand_method(&dummy_method); -#ifdef LIBRESSL_VERSION_NUMBER - /* On libressl, you can't override the RNG. */ - tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); - tt_int_op(0, ==, crypto_force_rand_ssleay()); -#else - tt_assert(RAND_get_rand_method() == &dummy_method); - tt_int_op(1, ==, crypto_force_rand_ssleay()); -#endif - tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); - - /* Make sure we aren't calling dummy_method */ - crypto_rand((void *) &dummy_method, sizeof(dummy_method)); - crypto_rand((void *) &dummy_method, sizeof(dummy_method)); - - done: - ; -} - /** Run unit tests for our AES128 functionality */ static void test_crypto_aes128(void *arg) @@ -1477,28 +1442,6 @@ test_crypto_digest_names(void *arg) ; } -#ifndef OPENSSL_1_1_API -#define EVP_ENCODE_CTX_new() tor_malloc_zero(sizeof(EVP_ENCODE_CTX)) -#define EVP_ENCODE_CTX_free(ctx) tor_free(ctx) -#endif - -/** Encode src into dest with OpenSSL's EVP Encode interface, returning the - * length of the encoded data in bytes. - */ -static int -base64_encode_evp(char *dest, char *src, size_t srclen) -{ - const unsigned char *s = (unsigned char*)src; - EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); - int len, ret; - - EVP_EncodeInit(ctx); - EVP_EncodeUpdate(ctx, (unsigned char *)dest, &len, s, (int)srclen); - EVP_EncodeFinal(ctx, (unsigned char *)(dest + len), &ret); - EVP_ENCODE_CTX_free(ctx); - return ret+ len; -} - /** Run unit tests for misc crypto formatting functionality (base64, base32, * fingerprints, etc) */ static void @@ -1527,7 +1470,7 @@ test_crypto_formats(void *arg) tt_int_op(i, OP_GE, 0); tt_int_op(i, OP_EQ, strlen(data2)); tt_assert(! strchr(data2, '=')); - j = base64_decode_nopad((uint8_t*)data3, 1024, data2, i); + j = base64_decode(data3, 1024, data2, i); tt_int_op(j, OP_EQ, idx); tt_mem_op(data3,OP_EQ, data1, idx); } @@ -1554,20 +1497,6 @@ test_crypto_formats(void *arg) tt_assert(digest_from_base64(data3, "###") < 0); - for (i = 0; i < 256; i++) { - /* Test the multiline format Base64 encoder with 0 .. 256 bytes of - * output against OpenSSL. - */ - const size_t enclen = base64_encode_size(i, BASE64_ENCODE_MULTILINE); - data1[i] = i; - j = base64_encode(data2, 1024, data1, i, BASE64_ENCODE_MULTILINE); - tt_int_op(j, OP_EQ, enclen); - j = base64_encode_evp(data3, data1, i); - tt_int_op(j, OP_EQ, enclen); - tt_mem_op(data2, OP_EQ, data3, enclen); - tt_int_op(j, OP_EQ, strlen(data2)); - } - /* Encoding SHA256 */ crypto_rand(data2, DIGEST256_LEN); memset(data2, 100, 1024); @@ -2941,7 +2870,6 @@ struct testcase_t crypto_tests[] = { CRYPTO_LEGACY(formats), CRYPTO_LEGACY(rng), { "rng_range", test_crypto_rng_range, 0, NULL, NULL }, - { "rng_engine", test_crypto_rng_engine, TT_FORK, NULL, NULL }, { "rng_strongest", test_crypto_rng_strongest, TT_FORK, NULL, NULL }, { "rng_strongest_nosyscall", test_crypto_rng_strongest, TT_FORK, &passthrough_setup, (void*)"nosyscall" }, diff --git a/src/test/test_crypto_openssl.c b/src/test/test_crypto_openssl.c new file mode 100644 index 0000000000..3d7d2b4639 --- /dev/null +++ b/src/test/test_crypto_openssl.c @@ -0,0 +1,107 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#define CRYPTO_PRIVATE + +#include "crypto.h" +#include "util.h" +#include "util_format.h" +#include "compat.h" +#include "test.h" + +#include <openssl/evp.h> +#include <openssl/rand.h> +#include "compat_openssl.h" + +/* Test for rectifying openssl RAND engine. */ +static void +test_crypto_rng_engine(void *arg) +{ + (void)arg; + RAND_METHOD dummy_method; + memset(&dummy_method, 0, sizeof(dummy_method)); + + /* We should be a no-op if we're already on RAND_OpenSSL */ + tt_int_op(0, ==, crypto_force_rand_ssleay()); + tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); + + /* We should correct the method if it's a dummy. */ + RAND_set_rand_method(&dummy_method); +#ifdef LIBRESSL_VERSION_NUMBER + /* On libressl, you can't override the RNG. */ + tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); + tt_int_op(0, ==, crypto_force_rand_ssleay()); +#else + tt_assert(RAND_get_rand_method() == &dummy_method); + tt_int_op(1, ==, crypto_force_rand_ssleay()); +#endif + tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); + + /* Make sure we aren't calling dummy_method */ + crypto_rand((void *) &dummy_method, sizeof(dummy_method)); + crypto_rand((void *) &dummy_method, sizeof(dummy_method)); + + done: + ; +} + +#ifndef OPENSSL_1_1_API +#define EVP_ENCODE_CTX_new() tor_malloc_zero(sizeof(EVP_ENCODE_CTX)) +#define EVP_ENCODE_CTX_free(ctx) tor_free(ctx) +#endif + +/** Encode src into dest with OpenSSL's EVP Encode interface, returning the + * length of the encoded data in bytes. + */ +static int +base64_encode_evp(char *dest, char *src, size_t srclen) +{ + const unsigned char *s = (unsigned char*)src; + EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); + int len, ret; + + EVP_EncodeInit(ctx); + EVP_EncodeUpdate(ctx, (unsigned char *)dest, &len, s, (int)srclen); + EVP_EncodeFinal(ctx, (unsigned char *)(dest + len), &ret); + EVP_ENCODE_CTX_free(ctx); + return ret+ len; +} + +static void +test_crypto_base64_encode_matches(void *arg) +{ + (void)arg; + int i, j; + char data1[1024]; + char data2[1024]; + char data3[1024]; + + for (i = 0; i < 256; i++) { + /* Test the multiline format Base64 encoder with 0 .. 256 bytes of + * output against OpenSSL. + */ + const size_t enclen = base64_encode_size(i, BASE64_ENCODE_MULTILINE); + data1[i] = i; + j = base64_encode(data2, 1024, data1, i, BASE64_ENCODE_MULTILINE); + tt_int_op(j, OP_EQ, enclen); + j = base64_encode_evp(data3, data1, i); + tt_int_op(j, OP_EQ, enclen); + tt_mem_op(data2, OP_EQ, data3, enclen); + tt_int_op(j, OP_EQ, strlen(data2)); + } + + done: + ; +} + +struct testcase_t crypto_openssl_tests[] = { + { "rng_engine", test_crypto_rng_engine, TT_FORK, NULL, NULL }, + { "base64_encode_match", test_crypto_base64_encode_matches, + TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_crypto_slow.c b/src/test/test_crypto_slow.c index 0be58c9389..75c6ba9aaa 100644 --- a/src/test/test_crypto_slow.c +++ b/src/test/test_crypto_slow.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_data.c b/src/test/test_data.c index 788489a097..ce6c3394f6 100644 --- a/src/test/test_data.c +++ b/src/test/test_data.c @@ -1,6 +1,6 @@ /* Copyright 2001-2004 Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "test.h" diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 4e5876fa3c..a9d9cba7df 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -1,12 +1,13 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" #include <math.h> #define CONFIG_PRIVATE +#define CONTROL_PRIVATE #define DIRSERV_PRIVATE #define DIRVOTE_PRIVATE #define ROUTER_PRIVATE @@ -19,6 +20,7 @@ #include "or.h" #include "confparse.h" #include "config.h" +#include "control.h" #include "crypto_ed25519.h" #include "directory.h" #include "dirserv.h" @@ -329,7 +331,7 @@ test_dir_formats(void *arg) ntor_cc = make_ntor_onion_key_crosscert(&r2_onion_keypair, &kp1.pubkey, r2->cache_info.published_on, - MIN_ONION_KEY_LIFETIME, + get_onion_key_lifetime(), &ntor_cc_sign); tt_assert(ntor_cc); base64_encode(cert_buf, sizeof(cert_buf), @@ -910,6 +912,23 @@ mock_get_by_ei_desc_digest(const char *d) } } +static signed_descriptor_t * +mock_ei_get_by_ei_digest(const char *d) +{ + char hex[HEX_DIGEST_LEN+1]; + base16_encode(hex, sizeof(hex), d, DIGEST_LEN); + signed_descriptor_t *sd = &sd_ei_minimal; + + if (!strcmp(hex, "11E0EDF526950739F7769810FCACAB8C882FAEEE")) { + sd->signed_descriptor_body = (char *)EX_EI_MINIMAL; + sd->signed_descriptor_len = sizeof(EX_EI_MINIMAL); + sd->annotations_len = 0; + sd->saved_location = SAVED_NOWHERE; + return sd; + } + return NULL; +} + static smartlist_t *mock_ei_insert_list = NULL; static was_router_added_t mock_ei_insert(routerlist_t *rl, extrainfo_t *ei, int warn_if_incompatible) @@ -999,6 +1018,37 @@ test_dir_load_extrainfo(void *arg) } static void +test_dir_getinfo_extra(void *arg) +{ + int r; + char *answer = NULL; + const char *errmsg = NULL; + + (void)arg; + MOCK(extrainfo_get_by_descriptor_digest, mock_ei_get_by_ei_digest); + r = getinfo_helper_dir(NULL, "extra-info/digest/" + "11E0EDF526950739F7769810FCACAB8C882FAEEE", &answer, + &errmsg); + tt_int_op(0, OP_EQ, r); + tt_ptr_op(NULL, OP_EQ, errmsg); + tt_str_op(answer, OP_EQ, EX_EI_MINIMAL); + tor_free(answer); + + answer = NULL; + r = getinfo_helper_dir(NULL, "extra-info/digest/" + "NOTAVALIDHEXSTRINGNOTAVALIDHEXSTRINGNOTA", &answer, + &errmsg); + tt_int_op(0, OP_EQ, r); + /* getinfo_helper_dir() should maybe return an error here but doesn't */ + tt_ptr_op(NULL, OP_EQ, errmsg); + /* In any case, there should be no answer for an invalid hex string. */ + tt_ptr_op(NULL, OP_EQ, answer); + + done: + UNMOCK(extrainfo_get_by_descriptor_digest); +} + +static void test_dir_versions(void *arg) { tor_version_t ver1; @@ -1065,6 +1115,7 @@ test_dir_versions(void *arg) tt_int_op(0, OP_EQ, ver1.patchlevel); tt_int_op(VER_RELEASE, OP_EQ, ver1.status); tt_str_op("alpha", OP_EQ, ver1.status_tag); + /* Go through the full set of status tags */ tt_int_op(0, OP_EQ, tor_version_parse("2.1.700-alpha", &ver1)); tt_int_op(2, OP_EQ, ver1.major); tt_int_op(1, OP_EQ, ver1.minor); @@ -1079,6 +1130,60 @@ test_dir_versions(void *arg) tt_int_op(0, OP_EQ, ver1.patchlevel); tt_int_op(VER_RELEASE, OP_EQ, ver1.status); tt_str_op("alpha-dev", OP_EQ, ver1.status_tag); + tt_int_op(0, OP_EQ, tor_version_parse("0.2.9.5-rc", &ver1)); + tt_int_op(0, OP_EQ, ver1.major); + tt_int_op(2, OP_EQ, ver1.minor); + tt_int_op(9, OP_EQ, ver1.micro); + tt_int_op(5, OP_EQ, ver1.patchlevel); + tt_int_op(VER_RELEASE, OP_EQ, ver1.status); + tt_str_op("rc", OP_EQ, ver1.status_tag); + tt_int_op(0, OP_EQ, tor_version_parse("0.2.9.6-rc-dev", &ver1)); + tt_int_op(0, OP_EQ, ver1.major); + tt_int_op(2, OP_EQ, ver1.minor); + tt_int_op(9, OP_EQ, ver1.micro); + tt_int_op(6, OP_EQ, ver1.patchlevel); + tt_int_op(VER_RELEASE, OP_EQ, ver1.status); + tt_str_op("rc-dev", OP_EQ, ver1.status_tag); + tt_int_op(0, OP_EQ, tor_version_parse("0.2.9.8", &ver1)); + tt_int_op(0, OP_EQ, ver1.major); + tt_int_op(2, OP_EQ, ver1.minor); + tt_int_op(9, OP_EQ, ver1.micro); + tt_int_op(8, OP_EQ, ver1.patchlevel); + tt_int_op(VER_RELEASE, OP_EQ, ver1.status); + tt_str_op("", OP_EQ, ver1.status_tag); + tt_int_op(0, OP_EQ, tor_version_parse("0.2.9.9-dev", &ver1)); + tt_int_op(0, OP_EQ, ver1.major); + tt_int_op(2, OP_EQ, ver1.minor); + tt_int_op(9, OP_EQ, ver1.micro); + tt_int_op(9, OP_EQ, ver1.patchlevel); + tt_int_op(VER_RELEASE, OP_EQ, ver1.status); + tt_str_op("dev", OP_EQ, ver1.status_tag); + /* In #21450, we fixed an inconsistency in parsing versions > INT32_MAX + * between i386 and x86_64, as we used tor_parse_long, and then cast to int + */ + tt_int_op(0, OP_EQ, tor_version_parse("0.2147483647.0", &ver1)); + tt_int_op(0, OP_EQ, ver1.major); + tt_int_op(2147483647, OP_EQ, ver1.minor); + tt_int_op(0, OP_EQ, ver1.micro); + tt_int_op(0, OP_EQ, ver1.patchlevel); + tt_int_op(VER_RELEASE, OP_EQ, ver1.status); + tt_str_op("", OP_EQ, ver1.status_tag); + tt_int_op(-1, OP_EQ, tor_version_parse("0.2147483648.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0.4294967295.0", &ver1)); + /* In #21278, we reject negative version components */ + tt_int_op(-1, OP_EQ, tor_version_parse("0.-1.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0.-2147483648.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0.-4294967295.0", &ver1)); + /* In #21507, we reject version components with non-numeric prefixes */ + tt_int_op(-1, OP_EQ, tor_version_parse("0.-0.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("+1.0.0", &ver1)); + /* use the list in isspace() */ + tt_int_op(-1, OP_EQ, tor_version_parse("0.\t0.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0.\n0.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0.\v0.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0.\f0.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0.\r0.0", &ver1)); + tt_int_op(-1, OP_EQ, tor_version_parse("0. 0.0", &ver1)); #define tt_versionstatus_op(vs1, op, vs2) \ tt_assert_test_type(vs1,vs2,#vs1" "#op" "#vs2,version_status_t, \ @@ -1098,6 +1203,7 @@ test_dir_versions(void *arg) test_v_i_o(VS_RECOMMENDED, "0.0.7rc2", "0.0.7,Tor 0.0.7rc2,Tor 0.0.8"); test_v_i_o(VS_OLD, "0.0.5.0", "0.0.5.1-cvs"); test_v_i_o(VS_NEW_IN_SERIES, "0.0.5.1-cvs", "0.0.5, 0.0.6"); + test_v_i_o(VS_NEW, "0.2.9.9-dev", "0.2.9.9"); /* Not on list, but newer than any in same series. */ test_v_i_o(VS_NEW_IN_SERIES, "0.1.0.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0"); @@ -1136,6 +1242,70 @@ test_dir_versions(void *arg) "Tor 0.2.1.0-dev (r99)")); tt_int_op(1,OP_EQ, tor_version_as_new_as("Tor 0.2.1.1", "Tor 0.2.1.0-dev (r99)")); + /* And git revisions */ + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 0.2.9.9 (git-56788a2489127072)", + "Tor 0.2.9.9 (git-56788a2489127072)")); + /* a git revision is newer than no git revision */ + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 0.2.9.9 (git-56788a2489127072)", + "Tor 0.2.9.9")); + /* a longer git revision is newer than a shorter git revision + * this should be true if they prefix-match, but if they don't, they are + * incomparable, because hashes aren't ordered (but we compare their bytes + * anyway) */ + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 0.2.9.9 (git-56788a2489127072d513cf4baf35a8ff475f3c7b)", + "Tor 0.2.9.9 (git-56788a2489127072)")); + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 0.2.9.9 (git-0102)", + "Tor 0.2.9.9 (git-03)")); + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 0.2.9.9 (git-0102)", + "Tor 0.2.9.9 (git-00)")); + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 0.2.9.9 (git-01)", + "Tor 0.2.9.9 (git-00)")); + tt_int_op(0,OP_EQ, tor_version_as_new_as( + "Tor 0.2.9.9 (git-00)", + "Tor 0.2.9.9 (git-01)")); + /* In #21278, we comapre without integer overflows. + * But since #21450 limits version components to [0, INT32_MAX], it is no + * longer possible to cause an integer overflow in tor_version_compare() */ + tt_int_op(0,OP_EQ, tor_version_as_new_as( + "Tor 0.0.0.0", + "Tor 2147483647.0.0.0")); + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 2147483647.0.0.0", + "Tor 0.0.0.0")); + /* These versions used to cause an overflow, now they don't parse + * (and authorities reject their descriptors), and log a BUG message */ + setup_full_capture_of_logs(LOG_WARN); + tt_int_op(0,OP_EQ, tor_version_as_new_as( + "Tor 0.0.0.0", + "Tor 0.-2147483648.0.0")); + expect_single_log_msg_containing("unparseable"); + mock_clean_saved_logs(); + tt_int_op(0,OP_EQ, tor_version_as_new_as( + "Tor 0.2147483647.0.0", + "Tor 0.-1.0.0")); + expect_single_log_msg_containing("unparseable"); + mock_clean_saved_logs(); + tt_int_op(0,OP_EQ, tor_version_as_new_as( + "Tor 0.2147483647.0.0", + "Tor 0.-2147483648.0.0")); + expect_single_log_msg_containing("unparseable"); + mock_clean_saved_logs(); + tt_int_op(1,OP_EQ, tor_version_as_new_as( + "Tor 4294967295.0.0.0", + "Tor 0.0.0.0")); + expect_no_log_entry(); + tt_int_op(0,OP_EQ, tor_version_as_new_as( + "Tor 0.4294967295.0.0", + "Tor 0.-4294967295.0.0")); + expect_single_log_msg_containing("unparseable"); + mock_clean_saved_logs(); + teardown_capture_of_logs(); /* Now try git revisions */ tt_int_op(0,OP_EQ, tor_version_parse("0.5.6.7 (git-ff00ff)", &ver1)); @@ -1145,11 +1315,24 @@ test_dir_versions(void *arg) tt_int_op(7,OP_EQ, ver1.patchlevel); tt_int_op(3,OP_EQ, ver1.git_tag_len); tt_mem_op(ver1.git_tag,OP_EQ, "\xff\x00\xff", 3); + /* reject bad hex digits */ tt_int_op(-1,OP_EQ, tor_version_parse("0.5.6.7 (git-ff00xx)", &ver1)); + /* reject odd hex digit count */ tt_int_op(-1,OP_EQ, tor_version_parse("0.5.6.7 (git-ff00fff)", &ver1)); + /* ignore "git " */ tt_int_op(0,OP_EQ, tor_version_parse("0.5.6.7 (git ff00fff)", &ver1)); + /* standard length is 16 hex digits */ + tt_int_op(0,OP_EQ, tor_version_parse("0.5.6.7 (git-0010203040506070)", + &ver1)); + /* length limit is 40 hex digits */ + tt_int_op(0,OP_EQ, tor_version_parse( + "0.5.6.7 (git-000102030405060708090a0b0c0d0e0f10111213)", + &ver1)); + tt_int_op(-1,OP_EQ, tor_version_parse( + "0.5.6.7 (git-000102030405060708090a0b0c0d0e0f1011121314)", + &ver1)); done: - ; + teardown_capture_of_logs(); } /** Run unit tests for directory fp_pair functions. */ @@ -4399,15 +4582,7 @@ test_dir_should_use_directory_guards(void *data) } NS_DECL(void, -directory_initiate_command_routerstatus, (const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - circuit_guard_state_t *guardstate)); +directory_initiate_request, (directory_request_t *req)); static void test_dir_should_not_init_request_to_ourselves(void *data) @@ -4417,7 +4592,7 @@ test_dir_should_not_init_request_to_ourselves(void *data) crypto_pk_t *key = pk_generate(2); (void) data; - NS_MOCK(directory_initiate_command_routerstatus); + NS_MOCK(directory_initiate_request); clear_dir_servers(); routerlist_free_all(); @@ -4432,15 +4607,15 @@ test_dir_should_not_init_request_to_ourselves(void *data) dir_server_add(ourself); directory_get_from_all_authorities(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL); - tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + tt_int_op(CALLED(directory_initiate_request), OP_EQ, 0); directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, NULL); - tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + tt_int_op(CALLED(directory_initiate_request), OP_EQ, 0); done: - NS_UNMOCK(directory_initiate_command_routerstatus); + NS_UNMOCK(directory_initiate_request); clear_dir_servers(); routerlist_free_all(); crypto_pk_free(key); @@ -4454,7 +4629,7 @@ test_dir_should_not_init_request_to_dir_auths_without_v3_info(void *data) | MICRODESC_DIRINFO; (void) data; - NS_MOCK(directory_initiate_command_routerstatus); + NS_MOCK(directory_initiate_request); clear_dir_servers(); routerlist_free_all(); @@ -4465,14 +4640,14 @@ test_dir_should_not_init_request_to_dir_auths_without_v3_info(void *data) dir_server_add(ds); directory_get_from_all_authorities(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL); - tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + tt_int_op(CALLED(directory_initiate_request), OP_EQ, 0); directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, NULL); - tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + tt_int_op(CALLED(directory_initiate_request), OP_EQ, 0); done: - NS_UNMOCK(directory_initiate_command_routerstatus); + NS_UNMOCK(directory_initiate_request); clear_dir_servers(); routerlist_free_all(); } @@ -4483,7 +4658,7 @@ test_dir_should_init_request_to_dir_auths(void *data) dir_server_t *ds = NULL; (void) data; - NS_MOCK(directory_initiate_command_routerstatus); + NS_MOCK(directory_initiate_request); clear_dir_servers(); routerlist_free_all(); @@ -4494,39 +4669,23 @@ test_dir_should_init_request_to_dir_auths(void *data) dir_server_add(ds); directory_get_from_all_authorities(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL); - tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 1); + tt_int_op(CALLED(directory_initiate_request), OP_EQ, 1); directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, NULL); - tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 2); + tt_int_op(CALLED(directory_initiate_request), OP_EQ, 2); done: - NS_UNMOCK(directory_initiate_command_routerstatus); + NS_UNMOCK(directory_initiate_request); clear_dir_servers(); routerlist_free_all(); } void -NS(directory_initiate_command_routerstatus)(const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - circuit_guard_state_t *guardstate) +NS(directory_initiate_request)(directory_request_t *req) { - (void)status; - (void)dir_purpose; - (void)router_purpose; - (void)indirection; - (void)resource; - (void)payload; - (void)payload_len; - (void)if_modified_since; - (void)guardstate; - CALLED(directory_initiate_command_routerstatus)++; + (void)req; + CALLED(directory_initiate_request)++; } static void @@ -5837,6 +5996,7 @@ struct testcase_t dir_tests[] = { DIR(parse_router_list, TT_FORK), DIR(load_routers, TT_FORK), DIR(load_extrainfo, TT_FORK), + DIR(getinfo_extra, 0), DIR_LEGACY(versions), DIR_LEGACY(fp_pairs), DIR(split_fps, 0), diff --git a/src/test/test_dir_common.c b/src/test/test_dir_common.c index ca43dd4c04..fca70249bd 100644 --- a/src/test/test_dir_common.c +++ b/src/test/test_dir_common.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_dir_common.h b/src/test/test_dir_common.h index 9682b0db49..65b9cf6436 100644 --- a/src/test/test_dir_common.h +++ b/src/test/test_dir_common.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/test/test_dir_handle_get.c b/src/test/test_dir_handle_get.c index a0868f9253..75fe6249ad 100644 --- a/src/test/test_dir_handle_get.c +++ b/src/test/test_dir_handle_get.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define RENDCOMMON_PRIVATE @@ -12,8 +12,10 @@ #include "or.h" #include "config.h" #include "connection.h" +#include "consdiffmgr.h" #include "directory.h" #include "test.h" +#include "compress.h" #include "connection.h" #include "rendcommon.h" #include "rendcache.h" @@ -28,7 +30,6 @@ #include "networkstatus.h" #include "geoip.h" #include "dirserv.h" -#include "torgzip.h" #include "dirvote.h" #include "log_test_helpers.h" @@ -63,6 +64,7 @@ new_dir_conn(void) { dir_connection_t *conn = dir_connection_new(AF_INET); tor_addr_from_ipv4h(&conn->base_.addr, 0x7f000001); + TO_CONN(conn)->address = tor_strdup("127.0.0.1"); return conn; } @@ -465,6 +467,8 @@ init_mock_options(void) mock_options = tor_malloc(sizeof(or_options_t)); memset(mock_options, 0, sizeof(or_options_t)); mock_options->TestingTorNetwork = 1; + mock_options->DataDirectory = tor_strdup(get_fname_rnd("datadir_tmp")); + check_private_dir(mock_options->DataDirectory, CPD_CREATE, NULL); } static const or_options_t * @@ -501,14 +505,6 @@ test_dir_handle_get_micro_d(void *data) /* SETUP */ init_mock_options(); - const char *fn = get_fname("dir_handle_datadir_test1"); - mock_options->DataDirectory = tor_strdup(fn); - -#ifdef _WIN32 - tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory)); -#else - tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory, 0700)); -#endif /* Add microdesc to cache */ crypto_digest256(digest, microdesc, strlen(microdesc), DIGEST_SHA256); @@ -568,14 +564,6 @@ test_dir_handle_get_micro_d_server_busy(void *data) /* SETUP */ init_mock_options(); - const char *fn = get_fname("dir_handle_datadir_test2"); - mock_options->DataDirectory = tor_strdup(fn); - -#ifdef _WIN32 - tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory)); -#else - tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory, 0700)); -#endif /* Add microdesc to cache */ crypto_digest256(digest, microdesc, strlen(microdesc), DIGEST_SHA256); @@ -743,7 +731,7 @@ test_dir_handle_get_server_descriptors_not_found(void* data) NULL, NULL, 1, 0); tt_str_op(NOT_FOUND, OP_EQ, header); - tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_SERVER_BY_FP); + tt_ptr_op(conn->spool, OP_EQ, NULL); done: UNMOCK(connection_write_to_buf_impl_); @@ -773,6 +761,7 @@ test_dir_handle_get_server_descriptors_all(void* data) tt_int_op(smartlist_len(our_routerlist->routers), OP_GE, 1); mock_routerinfo = smartlist_get(our_routerlist->routers, 0); set_server_identity_key(mock_routerinfo->identity_pkey); + mock_routerinfo->cache_info.published_on = time(NULL); /* Treat "all" requests as if they were unencrypted */ mock_routerinfo->cache_info.send_unencrypted = 1; @@ -787,7 +776,7 @@ test_dir_handle_get_server_descriptors_all(void* data) //which is smaller than that by annotation_len bytes fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, &body, &body_used, - mock_routerinfo->cache_info.signed_descriptor_len+1, 0); + 1024*1024, 0); tt_assert(header); tt_assert(body); @@ -803,7 +792,7 @@ test_dir_handle_get_server_descriptors_all(void* data) tt_str_op(body, OP_EQ, mock_routerinfo->cache_info.signed_descriptor_body + mock_routerinfo->cache_info.annotations_len); - tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + tt_ptr_op(conn->spool, OP_EQ, NULL); done: NS_UNMOCK(router_get_my_routerinfo); @@ -882,6 +871,7 @@ test_dir_handle_get_server_descriptors_authority(void* data) mock_routerinfo->cache_info.signed_descriptor_len = strlen(TEST_DESCRIPTOR) - annotation_len;; mock_routerinfo->cache_info.annotations_len = annotation_len; + mock_routerinfo->cache_info.published_on = time(NULL); conn = new_dir_conn(); @@ -904,7 +894,7 @@ test_dir_handle_get_server_descriptors_authority(void* data) tt_int_op(body_used, OP_EQ, strlen(body)); tt_str_op(body, OP_EQ, TEST_DESCRIPTOR + annotation_len); - tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + tt_ptr_op(conn->spool, OP_EQ, NULL); done: NS_UNMOCK(router_get_my_routerinfo); @@ -946,6 +936,7 @@ test_dir_handle_get_server_descriptors_fp(void* data) mock_routerinfo->cache_info.signed_descriptor_len = strlen(TEST_DESCRIPTOR) - annotation_len; mock_routerinfo->cache_info.annotations_len = annotation_len; + mock_routerinfo->cache_info.published_on = time(NULL); conn = new_dir_conn(); @@ -975,7 +966,7 @@ test_dir_handle_get_server_descriptors_fp(void* data) tt_int_op(body_used, OP_EQ, strlen(body)); tt_str_op(body, OP_EQ, TEST_DESCRIPTOR + annotation_len); - tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + tt_ptr_op(conn->spool, OP_EQ, NULL); done: NS_UNMOCK(router_get_my_routerinfo); @@ -1041,7 +1032,7 @@ test_dir_handle_get_server_descriptors_d(void* data) tt_str_op(body, OP_EQ, router->cache_info.signed_descriptor_body + router->cache_info.annotations_len); - tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + tt_ptr_op(conn->spool, OP_EQ, NULL); done: UNMOCK(connection_write_to_buf_impl_); @@ -1096,7 +1087,7 @@ test_dir_handle_get_server_descriptors_busy(void* data) tt_assert(header); tt_str_op(SERVER_BUSY, OP_EQ, header); - tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + tt_ptr_op(conn->spool, OP_EQ, NULL); done: UNMOCK(get_options); @@ -1618,8 +1609,13 @@ test_dir_handle_get_status_vote_current_consensus_ns_not_enough_sigs(void* d) /* init mock */ mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t)); mock_ns_val->flavor = FLAV_NS; + mock_ns_val->type = NS_TYPE_CONSENSUS; mock_ns_val->voters = smartlist_new(); - mock_ns_val->valid_until = time(NULL); + mock_ns_val->valid_after = time(NULL) - 1800; + mock_ns_val->valid_until = time(NULL) - 60; + + #define NETWORK_STATUS "some network status string" + consdiffmgr_add_consensus(NETWORK_STATUS, mock_ns_val); /* init mock */ init_mock_options(); @@ -1707,10 +1703,17 @@ test_dir_handle_get_status_vote_current_consensus_too_old(void *data) (void)data; mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t)); + mock_ns_val->type = NS_TYPE_CONSENSUS; mock_ns_val->flavor = FLAV_MICRODESC; - mock_ns_val->valid_until = time(NULL) - (60 * 60 * 24) - 1; + mock_ns_val->valid_after = time(NULL) - (24 * 60 * 60 + 1800); + mock_ns_val->fresh_until = time(NULL) - (24 * 60 * 60 + 900); + mock_ns_val->valid_until = time(NULL) - (24 * 60 * 60 + 20); + + #define NETWORK_STATUS "some network status string" + consdiffmgr_add_consensus(NETWORK_STATUS, mock_ns_val); init_mock_options(); + MOCK(get_options, mock_get_options); MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); MOCK(networkstatus_get_latest_consensus_by_flavor, mock_ns_get_by_flavor); @@ -1731,6 +1734,17 @@ test_dir_handle_get_status_vote_current_consensus_too_old(void *data) tor_free(header); teardown_capture_of_logs(); + tor_free(mock_ns_val); + + mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t)); + mock_ns_val->type = NS_TYPE_CONSENSUS; + mock_ns_val->flavor = FLAV_NS; + mock_ns_val->valid_after = time(NULL) - (24 * 60 * 60 + 1800); + mock_ns_val->fresh_until = time(NULL) - (24 * 60 * 60 + 900); + mock_ns_val->valid_until = time(NULL) - (24 * 60 * 60 + 20); + + #define NETWORK_STATUS "some network status string" + consdiffmgr_add_consensus(NETWORK_STATUS, mock_ns_val); setup_capture_of_logs(LOG_WARN); @@ -1769,12 +1783,26 @@ static void status_vote_current_consensus_ns_test(char **header, char **body, size_t *body_len) { - common_digests_t digests; dir_connection_t *conn = NULL; #define NETWORK_STATUS "some network status string" +#if 0 + common_digests_t digests; + uint8_t sha3[DIGEST256_LEN]; + memset(&digests, 0x60, sizeof(digests)); + memset(sha3, 0x06, sizeof(sha3)); dirserv_set_cached_consensus_networkstatus(NETWORK_STATUS, "ns", &digests, + sha3, time(NULL)); +#endif + networkstatus_t *ns = tor_malloc_zero(sizeof(networkstatus_t)); + ns->type = NS_TYPE_CONSENSUS; + ns->flavor = FLAV_NS; + ns->valid_after = time(NULL) - 1800; + ns->fresh_until = time(NULL) - 900; + ns->valid_until = time(NULL) - 60; + consdiffmgr_add_consensus(NETWORK_STATUS, ns); + networkstatus_vote_free(ns); MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); @@ -1787,7 +1815,6 @@ status_vote_current_consensus_ns_test(char **header, char **body, tt_str_op("ab", OP_EQ, geoip_get_country_name(1)); conn = new_dir_conn(); - TO_CONN(conn)->address = tor_strdup("127.0.0.1"); tt_int_op(0, OP_EQ, directory_handle_command_get(conn, GET("/tor/status-vote/current/consensus-ns"), NULL, 0)); @@ -1829,8 +1856,8 @@ test_dir_handle_get_status_vote_current_consensus_ns(void* data) comp_body_used); tt_int_op(ZLIB_METHOD, OP_EQ, compression); - tor_gzip_uncompress(&body, &body_used, comp_body, comp_body_used, - compression, 0, LOG_PROTOCOL_WARN); + tor_uncompress(&body, &body_used, comp_body, comp_body_used, + compression, 0, LOG_PROTOCOL_WARN); tt_str_op(NETWORK_STATUS, OP_EQ, body); tt_int_op(strlen(NETWORK_STATUS), OP_EQ, body_used); @@ -2494,6 +2521,53 @@ test_dir_handle_get_status_vote_current_authority(void* data) dirvote_free_all(); } +static void +test_dir_handle_get_parse_accept_encoding(void *arg) +{ + (void)arg; + const unsigned B_NONE = 1u << NO_METHOD; + const unsigned B_ZLIB = 1u << ZLIB_METHOD; + const unsigned B_GZIP = 1u << GZIP_METHOD; + const unsigned B_LZMA = 1u << LZMA_METHOD; + const unsigned B_ZSTD = 1u << ZSTD_METHOD; + + unsigned encodings; + + encodings = parse_accept_encoding_header(""); + tt_uint_op(B_NONE, OP_EQ, encodings); + + encodings = parse_accept_encoding_header(" "); + tt_uint_op(B_NONE, OP_EQ, encodings); + + encodings = parse_accept_encoding_header("dewey, cheatham, and howe "); + tt_uint_op(B_NONE, OP_EQ, encodings); + + encodings = parse_accept_encoding_header("dewey, cheatham, and gzip"); + tt_uint_op(B_NONE, OP_EQ, encodings); + + encodings = parse_accept_encoding_header("dewey, cheatham, and, gzip"); + tt_uint_op(B_NONE|B_GZIP, OP_EQ, encodings); + + encodings = parse_accept_encoding_header(" gzip"); + tt_uint_op(B_NONE|B_GZIP, OP_EQ, encodings); + + encodings = parse_accept_encoding_header("gzip"); + tt_uint_op(B_NONE|B_GZIP, OP_EQ, encodings); + + encodings = parse_accept_encoding_header("x-zstd, deflate, x-tor-lzma"); + tt_uint_op(B_NONE|B_ZLIB|B_ZSTD|B_LZMA, OP_EQ, encodings); + + encodings = parse_accept_encoding_header( + "x-zstd, deflate, x-tor-lzma, gzip"); + tt_uint_op(B_NONE|B_ZLIB|B_ZSTD|B_LZMA|B_GZIP, OP_EQ, encodings); + + encodings = parse_accept_encoding_header("x-zstd,deflate,x-tor-lzma,gzip"); + tt_uint_op(B_NONE|B_ZLIB|B_ZSTD|B_LZMA|B_GZIP, OP_EQ, encodings); + + done: + ; +} + #define DIR_HANDLE_CMD(name,flags) \ { #name, test_dir_handle_get_##name, (flags), NULL, NULL } @@ -2538,11 +2612,11 @@ struct testcase_t dir_handle_get_tests[] = { DIR_HANDLE_CMD(status_vote_current_authority, 0), DIR_HANDLE_CMD(status_vote_next_authority_not_found, 0), DIR_HANDLE_CMD(status_vote_next_authority, 0), - DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_enough_sigs, 0), - DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_found, 0), - DIR_HANDLE_CMD(status_vote_current_consensus_too_old, 0), - DIR_HANDLE_CMD(status_vote_current_consensus_ns_busy, 0), - DIR_HANDLE_CMD(status_vote_current_consensus_ns, 0), + DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_enough_sigs, TT_FORK), + DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_found, TT_FORK), + DIR_HANDLE_CMD(status_vote_current_consensus_too_old, TT_FORK), + DIR_HANDLE_CMD(status_vote_current_consensus_ns_busy, TT_FORK), + DIR_HANDLE_CMD(status_vote_current_consensus_ns, TT_FORK), DIR_HANDLE_CMD(status_vote_current_d_not_found, 0), DIR_HANDLE_CMD(status_vote_next_d_not_found, 0), DIR_HANDLE_CMD(status_vote_d, 0), @@ -2552,6 +2626,7 @@ struct testcase_t dir_handle_get_tests[] = { DIR_HANDLE_CMD(status_vote_next_consensus_signatures_not_found, 0), DIR_HANDLE_CMD(status_vote_next_consensus_signatures_busy, 0), DIR_HANDLE_CMD(status_vote_next_consensus_signatures, 0), + DIR_HANDLE_CMD(parse_accept_encoding, 0), END_OF_TESTCASES }; diff --git a/src/test/test_entryconn.c b/src/test/test_entryconn.c index 50848cfec2..12a631630b 100644 --- a/src/test/test_entryconn.c +++ b/src/test/test_entryconn.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index e215c60e23..1f008d93b3 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_extorport.c b/src/test/test_extorport.c index 1f92780177..fc9f27a5ac 100644 --- a/src/test/test_extorport.c +++ b/src/test/test_extorport.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CONNECTION_PRIVATE @@ -72,9 +72,9 @@ test_ext_or_id_map(void *arg) * writes to outbuf. */ static void connection_write_to_buf_impl_replacement(const char *string, size_t len, - connection_t *conn, int zlib) + connection_t *conn, int compressed) { - (void) zlib; + (void) compressed; tor_assert(string); tor_assert(conn); diff --git a/src/test/test_guardfraction.c b/src/test/test_guardfraction.c index 8173e44d47..56006f3cc3 100644 --- a/src/test/test_guardfraction.c +++ b/src/test/test_guardfraction.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRSERV_PRIVATE diff --git a/src/test/test_handles.c b/src/test/test_handles.c index 536a478689..7ddee6e376 100644 --- a/src/test/test_handles.c +++ b/src/test/test_handles.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_helpers.c b/src/test/test_helpers.c index 5b84366e6d..9fada5a675 100644 --- a/src/test/test_helpers.c +++ b/src/test/test_helpers.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -98,9 +98,9 @@ helper_setup_fake_routerlist(void) void connection_write_to_buf_mock(const char *string, size_t len, - connection_t *conn, int zlib) + connection_t *conn, int compressed) { - (void) zlib; + (void) compressed; tor_assert(string); tor_assert(conn); diff --git a/src/test/test_helpers.h b/src/test/test_helpers.h index c6d4d9c41f..4621631cc1 100644 --- a/src/test/test_helpers.h +++ b/src/test/test_helpers.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_TEST_HELPERS_H @@ -15,7 +15,7 @@ void helper_setup_fake_routerlist(void); #define GET(path) "GET " path " HTTP/1.0\r\n\r\n" void connection_write_to_buf_mock(const char *string, size_t len, - connection_t *conn, int zlib); + connection_t *conn, int compressed); int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name, uint16_t family, tor_addr_t *out); diff --git a/src/test/test_hs.c b/src/test/test_hs.c index fbaabe91d8..5aae6c5b97 100644 --- a/src/test/test_hs.c +++ b/src/test/test_hs.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -210,9 +210,30 @@ test_hs_desc_event(void *arg) tt_str_op(received_msg,OP_EQ, expected_msg); tor_free(received_msg); - /* test valid content. */ + /* test no HSDir fingerprint type */ + rend_query.auth_type = REND_NO_AUTH; + control_event_hs_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"; + tt_assert(received_msg); + tt_str_op(received_msg,OP_EQ, expected_msg); + tor_free(received_msg); + + /* Test invalid content with no HSDir fingerprint. */ char *exp_msg; control_event_hs_descriptor_content(rend_query.onion_address, + STR_HS_CONTENT_DESC_ID, NULL, NULL); + tor_asprintf(&exp_msg, "650+HS_DESC_CONTENT " STR_HS_ADDR " "\ + STR_HS_CONTENT_DESC_ID " UNKNOWN" \ + "\r\n\r\n.\r\n650 OK\r\n"); + tt_assert(received_msg); + tt_str_op(received_msg, OP_EQ, exp_msg); + tor_free(received_msg); + tor_free(exp_msg); + + /* test valid content. */ + control_event_hs_descriptor_content(rend_query.onion_address, STR_HS_CONTENT_DESC_ID, HSDIR_EXIST_ID, hs_desc_content); tor_asprintf(&exp_msg, "650+HS_DESC_CONTENT " STR_HS_ADDR " "\ @@ -551,6 +572,7 @@ test_single_onion_poisoning(void *arg) char *dir2 = tor_strdup(get_fname_rnd("test_hs_dir2")); smartlist_t *services = smartlist_new(); char *poison_path = NULL; + char *err_msg = NULL; /* No services, no service to verify, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; @@ -586,7 +608,6 @@ test_single_onion_poisoning(void *arg) /* Add port to service 1 */ service_1->ports = smartlist_new(); service_2->ports = smartlist_new(); - char *err_msg = NULL; rend_service_port_config_t *port1 = rend_service_parse_port_config("80", " ", &err_msg); tt_assert(port1); @@ -785,6 +806,7 @@ test_single_onion_poisoning(void *arg) rend_service_free(service_2); UNMOCK(get_options); tor_free(mock_options->DataDirectory); + tor_free(err_msg); } static rend_service_t * @@ -821,7 +843,9 @@ test_prune_services_on_reload(void *arg) smartlist_add(old, e1); /* Only put the non ephemeral in the new list. */ smartlist_add(new, s1); - prune_services_on_reload(old, new); + set_rend_service_list(old); + set_rend_rend_service_staging_list(new); + rend_service_prune_list_impl_(); /* We expect that the ephemeral one is in the new list but removed from * the old one. */ tt_int_op(smartlist_len(old), OP_EQ, 1); @@ -840,7 +864,9 @@ test_prune_services_on_reload(void *arg) * one. */ smartlist_add(old, s1); smartlist_add(old, e1); - prune_services_on_reload(old, new); + set_rend_service_list(old); + set_rend_rend_service_staging_list(new); + rend_service_prune_list_impl_(); tt_int_op(smartlist_len(old), OP_EQ, 1); tt_assert(smartlist_get(old, 0) == s1); tt_int_op(smartlist_len(new), OP_EQ, 1); @@ -855,7 +881,9 @@ test_prune_services_on_reload(void *arg) * list being completely different. */ smartlist_add(new, s1); smartlist_add(new, e1); - prune_services_on_reload(old, new); + set_rend_service_list(old); + set_rend_rend_service_staging_list(new); + rend_service_prune_list_impl_(); tt_int_op(smartlist_len(old), OP_EQ, 0); tt_int_op(smartlist_len(new), OP_EQ, 2); tt_assert(smartlist_get(new, 0) == s1); @@ -871,7 +899,9 @@ test_prune_services_on_reload(void *arg) /* Setup our list. */ smartlist_add(old, s1); smartlist_add(new, s2); - prune_services_on_reload(old, new); + set_rend_service_list(old); + set_rend_rend_service_staging_list(new); + rend_service_prune_list_impl_(); tt_int_op(smartlist_len(old), OP_EQ, 1); /* Intro nodes have been moved to the s2 in theory so it must be empty. */ tt_int_op(smartlist_len(s1->intro_nodes), OP_EQ, 0); @@ -892,7 +922,9 @@ test_prune_services_on_reload(void *arg) /* Test two ephemeral services. */ smartlist_add(old, e1); smartlist_add(old, e2); - prune_services_on_reload(old, new); + set_rend_service_list(old); + set_rend_rend_service_staging_list(new); + rend_service_prune_list_impl_(); /* Check if they've all been transfered. */ tt_int_op(smartlist_len(old), OP_EQ, 0); tt_int_op(smartlist_len(new), OP_EQ, 2); diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c index 1943d0ffac..40f50b322a 100644 --- a/src/test/test_hs_cache.c +++ b/src/test/test_hs_cache.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,96 +15,10 @@ #include "directory.h" #include "connection.h" +#include "hs_test_helpers.h" #include "test_helpers.h" #include "test.h" -/* Build an intro point using a blinded key and an address. */ -static hs_desc_intro_point_t * -helper_build_intro_point(const ed25519_keypair_t *blinded_kp, - const char *addr) -{ - int ret; - ed25519_keypair_t auth_kp; - hs_desc_intro_point_t *intro_point = NULL; - hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); - ip->link_specifiers = smartlist_new(); - - { - hs_desc_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls)); - ls->u.ap.port = 9001; - int family = tor_addr_parse(&ls->u.ap.addr, addr); - switch (family) { - case AF_INET: - ls->type = LS_IPV4; - break; - case AF_INET6: - ls->type = LS_IPV6; - break; - default: - /* Stop the test, not suppose to have an error. */ - tt_int_op(family, OP_EQ, AF_INET); - } - smartlist_add(ip->link_specifiers, ls); - } - - ret = ed25519_keypair_generate(&auth_kp, 0); - tt_int_op(ret, ==, 0); - ip->auth_key_cert = tor_cert_create(blinded_kp, CERT_TYPE_AUTH_HS_IP_KEY, - &auth_kp.pubkey, time(NULL), - HS_DESC_CERT_LIFETIME, - CERT_FLAG_INCLUDE_SIGNING_KEY); - tt_assert(ip->auth_key_cert); - - ret = curve25519_keypair_generate(&ip->enc_key.curve25519, 0); - tt_int_op(ret, ==, 0); - ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519; - intro_point = ip; - done: - return intro_point; -} - -/* Return a valid hs_descriptor_t object. */ -static hs_descriptor_t * -helper_build_hs_desc(uint64_t revision_counter, uint32_t lifetime, - ed25519_public_key_t *signing_pubkey) -{ - int ret; - ed25519_keypair_t blinded_kp; - hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc)); - - desc->plaintext_data.version = HS_DESC_SUPPORTED_FORMAT_VERSION_MAX; - - /* Copy only the public key into the descriptor. */ - memcpy(&desc->plaintext_data.signing_pubkey, signing_pubkey, - sizeof(ed25519_public_key_t)); - - ret = ed25519_keypair_generate(&blinded_kp, 0); - tt_int_op(ret, ==, 0); - /* Copy only the public key into the descriptor. */ - memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey, - sizeof(ed25519_public_key_t)); - - desc->plaintext_data.signing_key_cert = - tor_cert_create(&blinded_kp, CERT_TYPE_SIGNING_HS_DESC, signing_pubkey, - time(NULL), 3600, CERT_FLAG_INCLUDE_SIGNING_KEY); - tt_assert(desc->plaintext_data.signing_key_cert); - desc->plaintext_data.revision_counter = revision_counter; - desc->plaintext_data.lifetime_sec = lifetime; - - /* Setup encrypted data section. */ - desc->encrypted_data.create2_ntor = 1; - desc->encrypted_data.auth_types = smartlist_new(); - smartlist_add(desc->encrypted_data.auth_types, tor_strdup("ed25519")); - desc->encrypted_data.intro_points = smartlist_new(); - /* Add an intro point. */ - smartlist_add(desc->encrypted_data.intro_points, - helper_build_intro_point(&blinded_kp, "1.2.3.4")); - - descp = desc; - done: - return descp; -} - /* Static variable used to encoded the HSDir query. */ static char query_b64[256]; @@ -141,7 +55,7 @@ test_directory(void *arg) /* Generate a valid descriptor with normal values. */ ret = ed25519_keypair_generate(&signing_kp1, 0); tt_int_op(ret, ==, 0); - desc1 = helper_build_hs_desc(42, 3 * 60 * 60, &signing_kp1.pubkey); + desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1); tt_assert(desc1); ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str); tt_int_op(ret, OP_EQ, 0); @@ -175,8 +89,10 @@ test_directory(void *arg) ret = ed25519_keypair_generate(&signing_kp_zero, 0); tt_int_op(ret, ==, 0); hs_descriptor_t *desc_zero_lifetime; - desc_zero_lifetime = helper_build_hs_desc(1, 0, &signing_kp_zero.pubkey); + desc_zero_lifetime = hs_helper_build_hs_desc_with_ip(&signing_kp_zero); tt_assert(desc_zero_lifetime); + desc_zero_lifetime->plaintext_data.revision_counter = 1; + desc_zero_lifetime->plaintext_data.lifetime_sec = 0; char *desc_zero_lifetime_str; ret = hs_desc_encode_descriptor(desc_zero_lifetime, &signing_kp_zero, &desc_zero_lifetime_str); @@ -262,7 +178,7 @@ test_clean_as_dir(void *arg) /* Generate a valid descriptor with values. */ ret = ed25519_keypair_generate(&signing_kp1, 0); tt_int_op(ret, ==, 0); - desc1 = helper_build_hs_desc(42, 3 * 60 * 60, &signing_kp1.pubkey); + desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1); tt_assert(desc1); ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str); tt_int_op(ret, OP_EQ, 0); @@ -333,7 +249,7 @@ helper_fetch_desc_from_hsdir(const ed25519_public_key_t *blinded_key) size_t body_used = 0; fetch_from_buf_http(TO_CONN(conn)->outbuf, &headers, MAX_HEADERS_SIZE, - &received_desc, &body_used, 10000, 0); + &received_desc, &body_used, HS_DESC_MAX_LEN, 0); tor_free(headers); } @@ -375,7 +291,7 @@ test_upload_and_download_hs_desc(void *arg) ed25519_keypair_t signing_kp; retval = ed25519_keypair_generate(&signing_kp, 0); tt_int_op(retval, ==, 0); - published_desc = helper_build_hs_desc(42, 3 * 60 * 60, &signing_kp.pubkey); + published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); tt_assert(published_desc); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, &published_desc_str); @@ -438,8 +354,7 @@ test_hsdir_revision_counter_check(void *arg) { retval = ed25519_keypair_generate(&signing_kp, 0); tt_int_op(retval, ==, 0); - published_desc = helper_build_hs_desc(1312, 3 * 60 * 60, - &signing_kp.pubkey); + published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); tt_assert(published_desc); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, &published_desc_str); @@ -470,7 +385,7 @@ test_hsdir_revision_counter_check(void *arg) tt_assert(received_desc); /* Check that the revision counter is correct */ - tt_u64_op(received_desc->plaintext_data.revision_counter, ==, 1312); + tt_u64_op(received_desc->plaintext_data.revision_counter, ==, 42); hs_descriptor_free(received_desc); received_desc = NULL; diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index 97fe1910b8..7d7ec7d9db 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,220 +15,17 @@ #include "test.h" #include "torcert.h" -static hs_desc_intro_point_t * -helper_build_intro_point(const ed25519_keypair_t *blinded_kp, time_t now, - const char *addr, int legacy) -{ - int ret; - ed25519_keypair_t auth_kp; - hs_desc_intro_point_t *intro_point = NULL; - hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); - ip->link_specifiers = smartlist_new(); - - { - hs_desc_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls)); - if (legacy) { - ls->type = LS_LEGACY_ID; - memcpy(ls->u.legacy_id, "0299F268FCA9D55CD157976D39AE92B4B455B3A8", - DIGEST_LEN); - } else { - ls->u.ap.port = 9001; - int family = tor_addr_parse(&ls->u.ap.addr, addr); - switch (family) { - case AF_INET: - ls->type = LS_IPV4; - break; - case AF_INET6: - ls->type = LS_IPV6; - break; - default: - /* Stop the test, not suppose to have an error. */ - tt_int_op(family, OP_EQ, AF_INET); - } - } - smartlist_add(ip->link_specifiers, ls); - } - - ret = ed25519_keypair_generate(&auth_kp, 0); - tt_int_op(ret, ==, 0); - ip->auth_key_cert = tor_cert_create(blinded_kp, CERT_TYPE_AUTH_HS_IP_KEY, - &auth_kp.pubkey, now, - HS_DESC_CERT_LIFETIME, - CERT_FLAG_INCLUDE_SIGNING_KEY); - tt_assert(ip->auth_key_cert); - - if (legacy) { - ip->enc_key.legacy = crypto_pk_new(); - ip->enc_key_type = HS_DESC_KEY_TYPE_LEGACY; - tt_assert(ip->enc_key.legacy); - ret = crypto_pk_generate_key(ip->enc_key.legacy); - tt_int_op(ret, ==, 0); - } else { - ret = curve25519_keypair_generate(&ip->enc_key.curve25519, 0); - tt_int_op(ret, ==, 0); - ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519; - } - - intro_point = ip; - done: - return intro_point; -} - -/* Return a valid hs_descriptor_t object. If no_ip is set, no introduction - * points are added. */ -static hs_descriptor_t * -helper_build_hs_desc(unsigned int no_ip, ed25519_public_key_t *signing_pubkey) -{ - int ret; - time_t now = time(NULL); - ed25519_keypair_t blinded_kp; - hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc)); - - desc->plaintext_data.version = HS_DESC_SUPPORTED_FORMAT_VERSION_MAX; - - /* Copy only the public key into the descriptor. */ - memcpy(&desc->plaintext_data.signing_pubkey, signing_pubkey, - sizeof(ed25519_public_key_t)); - - ret = ed25519_keypair_generate(&blinded_kp, 0); - tt_int_op(ret, ==, 0); - /* Copy only the public key into the descriptor. */ - memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey, - sizeof(ed25519_public_key_t)); - - desc->plaintext_data.signing_key_cert = - tor_cert_create(&blinded_kp, CERT_TYPE_SIGNING_HS_DESC, signing_pubkey, - now, 3600, CERT_FLAG_INCLUDE_SIGNING_KEY); - tt_assert(desc->plaintext_data.signing_key_cert); - desc->plaintext_data.revision_counter = 42; - desc->plaintext_data.lifetime_sec = 3 * 60 * 60; - - /* Setup encrypted data section. */ - desc->encrypted_data.create2_ntor = 1; - desc->encrypted_data.auth_types = smartlist_new(); - desc->encrypted_data.single_onion_service = 1; - smartlist_add(desc->encrypted_data.auth_types, tor_strdup("ed25519")); - desc->encrypted_data.intro_points = smartlist_new(); - if (!no_ip) { - /* Add four intro points. */ - smartlist_add(desc->encrypted_data.intro_points, - helper_build_intro_point(&blinded_kp, now, "1.2.3.4", 0)); - smartlist_add(desc->encrypted_data.intro_points, - helper_build_intro_point(&blinded_kp, now, "[2600::1]", 0)); - smartlist_add(desc->encrypted_data.intro_points, - helper_build_intro_point(&blinded_kp, now, "3.2.1.4", 1)); - smartlist_add(desc->encrypted_data.intro_points, - helper_build_intro_point(&blinded_kp, now, "", 1)); - } - - descp = desc; - done: - return descp; -} +#include "hs_test_helpers.h" +#include "test_helpers.h" +#include "log_test_helpers.h" -static void -helper_compare_hs_desc(const hs_descriptor_t *desc1, - const hs_descriptor_t *desc2) -{ - char *addr1 = NULL, *addr2 = NULL; - /* Plaintext data section. */ - tt_int_op(desc1->plaintext_data.version, OP_EQ, - desc2->plaintext_data.version); - tt_uint_op(desc1->plaintext_data.lifetime_sec, OP_EQ, - desc2->plaintext_data.lifetime_sec); - tt_assert(tor_cert_eq(desc1->plaintext_data.signing_key_cert, - desc2->plaintext_data.signing_key_cert)); - tt_mem_op(desc1->plaintext_data.signing_pubkey.pubkey, OP_EQ, - desc2->plaintext_data.signing_pubkey.pubkey, - ED25519_PUBKEY_LEN); - tt_mem_op(desc1->plaintext_data.blinded_pubkey.pubkey, OP_EQ, - desc2->plaintext_data.blinded_pubkey.pubkey, - ED25519_PUBKEY_LEN); - tt_u64_op(desc1->plaintext_data.revision_counter, ==, - desc2->plaintext_data.revision_counter); - - /* NOTE: We can't compare the encrypted blob because when encoding the - * descriptor, the object is immutable thus we don't update it with the - * encrypted blob. As contrast to the decoding process where we populate a - * descriptor object. */ - - /* Encrypted data section. */ - tt_uint_op(desc1->encrypted_data.create2_ntor, ==, - desc2->encrypted_data.create2_ntor); - - /* Authentication type. */ - tt_int_op(!!desc1->encrypted_data.auth_types, ==, - !!desc2->encrypted_data.auth_types); - if (desc1->encrypted_data.auth_types && desc2->encrypted_data.auth_types) { - tt_int_op(smartlist_len(desc1->encrypted_data.auth_types), ==, - smartlist_len(desc2->encrypted_data.auth_types)); - for (int i = 0; i < smartlist_len(desc1->encrypted_data.auth_types); i++) { - tt_str_op(smartlist_get(desc1->encrypted_data.auth_types, i), OP_EQ, - smartlist_get(desc2->encrypted_data.auth_types, i)); - } - } - - /* Introduction points. */ - { - tt_assert(desc1->encrypted_data.intro_points); - tt_assert(desc2->encrypted_data.intro_points); - tt_int_op(smartlist_len(desc1->encrypted_data.intro_points), ==, - smartlist_len(desc2->encrypted_data.intro_points)); - for (int i=0; i < smartlist_len(desc1->encrypted_data.intro_points); i++) { - hs_desc_intro_point_t *ip1 = smartlist_get(desc1->encrypted_data - .intro_points, i), - *ip2 = smartlist_get(desc2->encrypted_data - .intro_points, i); - tt_assert(tor_cert_eq(ip1->auth_key_cert, ip2->auth_key_cert)); - tt_int_op(ip1->enc_key_type, OP_EQ, ip2->enc_key_type); - tt_assert(ip1->enc_key_type == HS_DESC_KEY_TYPE_LEGACY || - ip1->enc_key_type == HS_DESC_KEY_TYPE_CURVE25519); - switch (ip1->enc_key_type) { - case HS_DESC_KEY_TYPE_LEGACY: - tt_int_op(crypto_pk_cmp_keys(ip1->enc_key.legacy, ip2->enc_key.legacy), - OP_EQ, 0); - break; - case HS_DESC_KEY_TYPE_CURVE25519: - tt_mem_op(ip1->enc_key.curve25519.pubkey.public_key, OP_EQ, - ip2->enc_key.curve25519.pubkey.public_key, - CURVE25519_PUBKEY_LEN); - break; - } - - tt_int_op(smartlist_len(ip1->link_specifiers), ==, - smartlist_len(ip2->link_specifiers)); - for (int j = 0; j < smartlist_len(ip1->link_specifiers); j++) { - hs_desc_link_specifier_t *ls1 = smartlist_get(ip1->link_specifiers, j), - *ls2 = smartlist_get(ip2->link_specifiers, j); - tt_int_op(ls1->type, ==, ls2->type); - switch (ls1->type) { - case LS_IPV4: - case LS_IPV6: - { - addr1 = tor_addr_to_str_dup(&ls1->u.ap.addr); - addr2 = tor_addr_to_str_dup(&ls2->u.ap.addr); - tt_str_op(addr1, OP_EQ, addr2); - tor_free(addr1); - tor_free(addr2); - tt_int_op(ls1->u.ap.port, ==, ls2->u.ap.port); - } - break; - case LS_LEGACY_ID: - tt_mem_op(ls1->u.legacy_id, OP_EQ, ls2->u.legacy_id, - sizeof(ls1->u.legacy_id)); - break; - default: - /* Unknown type, caught it and print its value. */ - tt_int_op(ls1->type, OP_EQ, -1); - } - } - } - } - - done: - tor_free(addr1); - tor_free(addr2); -} +#ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS +DISABLE_GCC_WARNING(overlength-strings) +/* We allow huge string constants in the unit tests, but not in the code + * at large. */ +#endif +#include "test_hs_descriptor.inc" +ENABLE_GCC_WARNING(overlength-strings) /* Test certificate encoding put in a descriptor. */ static void @@ -311,13 +108,13 @@ test_descriptor_padding(void *arg) /* Example: if l = 129, the ceiled division gives 2 and then multiplied by 128 * to give 256. With l = 127, ceiled division gives 1 then times 128. */ #define PADDING_EXPECTED_LEN(l) \ - CEIL_DIV(l, HS_DESC_PLAINTEXT_PADDING_MULTIPLE) * \ - HS_DESC_PLAINTEXT_PADDING_MULTIPLE + CEIL_DIV(l, HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE) * \ + HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE (void) arg; { /* test #1: no padding */ - plaintext_len = HS_DESC_PLAINTEXT_PADDING_MULTIPLE; + plaintext_len = HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE; plaintext = tor_malloc(plaintext_len); padded_len = build_plaintext_padding(plaintext, plaintext_len, &padded_plaintext); @@ -333,7 +130,7 @@ test_descriptor_padding(void *arg) } { /* test #2: one byte padding? */ - plaintext_len = HS_DESC_PLAINTEXT_PADDING_MULTIPLE - 1; + plaintext_len = HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE - 1; plaintext = tor_malloc(plaintext_len); padded_plaintext = NULL; padded_len = build_plaintext_padding(plaintext, plaintext_len, @@ -350,7 +147,7 @@ test_descriptor_padding(void *arg) } { /* test #3: Lots more bytes of padding? */ - plaintext_len = HS_DESC_PLAINTEXT_PADDING_MULTIPLE + 1; + plaintext_len = HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE + 1; plaintext = tor_malloc(plaintext_len); padded_plaintext = NULL; padded_len = build_plaintext_padding(plaintext, plaintext_len, @@ -488,7 +285,7 @@ test_encode_descriptor(void *arg) ret = ed25519_keypair_generate(&signing_kp, 0); tt_int_op(ret, ==, 0); - desc = helper_build_hs_desc(0, &signing_kp.pubkey); + desc = hs_helper_build_hs_desc_with_ip(&signing_kp); ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded); tt_int_op(ret, ==, 0); tt_assert(encoded); @@ -512,7 +309,7 @@ test_decode_descriptor(void *arg) ret = ed25519_keypair_generate(&signing_kp, 0); tt_int_op(ret, ==, 0); - desc = helper_build_hs_desc(0, &signing_kp.pubkey); + desc = hs_helper_build_hs_desc_with_ip(&signing_kp); /* Give some bad stuff to the decoding function. */ ret = hs_desc_decode_descriptor("hladfjlkjadf", NULL, &decoded); @@ -526,14 +323,14 @@ test_decode_descriptor(void *arg) tt_int_op(ret, ==, 0); tt_assert(decoded); - helper_compare_hs_desc(desc, decoded); + hs_helper_desc_equal(desc, decoded); /* Decode a descriptor with _no_ introduction points. */ { ed25519_keypair_t signing_kp_no_ip; ret = ed25519_keypair_generate(&signing_kp_no_ip, 0); tt_int_op(ret, ==, 0); - desc_no_ip = helper_build_hs_desc(1, &signing_kp_no_ip.pubkey); + desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip); tt_assert(desc_no_ip); tor_free(encoded); ret = hs_desc_encode_descriptor(desc_no_ip, &signing_kp_no_ip, &encoded); @@ -587,24 +384,17 @@ test_encrypted_data_len(void *arg) /* No length, error. */ ret = encrypted_data_length_is_valid(0); tt_int_op(ret, OP_EQ, 0); - /* This value is missing data. */ - value = HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN; - ret = encrypted_data_length_is_valid(value); - tt_int_op(ret, OP_EQ, 0); /* Valid value. */ - value = HS_DESC_PADDED_PLAINTEXT_MAX_LEN + HS_DESC_ENCRYPTED_SALT_LEN + - DIGEST256_LEN; + value = HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN + 1; ret = encrypted_data_length_is_valid(value); tt_int_op(ret, OP_EQ, 1); - /* XXX: Test maximum possible size. */ - done: ; } static void -test_decode_intro_point(void *arg) +test_decode_invalid_intro_point(void *arg) { int ret; char *encoded_ip = NULL; @@ -615,9 +405,6 @@ test_decode_intro_point(void *arg) (void) arg; - /* The following certificate expires in 2036. After that, one of the test - * will fail because of the expiry time. */ - /* Seperate pieces of a valid encoded introduction point. */ const char *intro_point = "introduction-point AQIUMDI5OUYyNjhGQ0E5RDU1Q0QxNTc="; @@ -630,60 +417,13 @@ test_decode_intro_point(void *arg) "-----END ED25519 CERT-----"; const char *enc_key = "enc-key ntor bpZKLsuhxP6woDQ3yVyjm5gUKSk7RjfAijT2qrzbQk0="; - const char *enc_key_legacy = - "enc-key legacy\n" - "-----BEGIN RSA PUBLIC KEY-----\n" - "MIGJAoGBAO4bATcW8kW4h6RQQAKEgg+aXCpF4JwbcO6vGZtzXTDB+HdPVQzwqkbh\n" - "XzFM6VGArhYw4m31wcP1Z7IwULir7UMnAFd7Zi62aYfU6l+Y1yAoZ1wzu1XBaAMK\n" - "ejpwQinW9nzJn7c2f69fVke3pkhxpNdUZ+vplSA/l9iY+y+v+415AgMBAAE=\n" - "-----END RSA PUBLIC KEY-----"; const char *enc_key_cert = - "enc-key-certification\n" + "enc-key-cert\n" "-----BEGIN ED25519 CERT-----\n" "AQsACOhZAUpNvCZ1aJaaR49lS6MCdsVkhVGVrRqoj0Y2T4SzroAtAQAgBABFOcGg\n" "lbTt1DF5nKTE/gU3Fr8ZtlCIOhu1A+F5LM7fqCUupfesg0KTHwyIZOYQbJuM5/he\n" "/jDNyLy9woPJdjkxywaY2RPUxGjLYtMQV0E8PUxWyICV+7y52fTCYaKpYQw=\n" "-----END ED25519 CERT-----"; - const char *enc_key_cert_legacy = - "enc-key-certification\n" - "-----BEGIN CROSSCERT-----\n" - "Sk28JnVolppHj2VLowJ2xWSFUZWtGqiPRjZPhLOugC0ACOhZgFPA5egeRDUXMM1U\n" - "Fn3c7Je0gJS6mVma5FzwlgwggeriF13UZcaT71vEAN/ZJXbxOfQVGMZ0rXuFpjUq\n" - "C8CvqmZIwEUaPE1nDFtmnTcucvNS1YQl9nsjH3ejbxc+4yqps/cXh46FmXsm5yz7\n" - "NZjBM9U1fbJhlNtOvrkf70K8bLk6\n" - "-----END CROSSCERT-----"; - - (void) enc_key_legacy; - (void) enc_key_cert_legacy; - - /* Start by testing the "decode all intro points" function. */ - { - char *line; - ret = ed25519_keypair_generate(&signing_kp, 0); - tt_int_op(ret, ==, 0); - desc = helper_build_hs_desc(0, &signing_kp.pubkey); - tt_assert(desc); - /* Only try to decode an incomplete introduction point section. */ - tor_asprintf(&line, "\n%s", intro_point); - ret = decode_intro_points(desc, &desc->encrypted_data, line); - tor_free(line); - tt_int_op(ret, ==, -1); - - /* Decode one complete intro point. */ - smartlist_t *lines = smartlist_new(); - smartlist_add(lines, (char *) intro_point); - smartlist_add(lines, (char *) auth_key); - smartlist_add(lines, (char *) enc_key); - smartlist_add(lines, (char *) enc_key_cert); - encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); - tt_assert(encoded_ip); - tor_asprintf(&line, "\n%s", encoded_ip); - tor_free(encoded_ip); - ret = decode_intro_points(desc, &desc->encrypted_data, line); - tor_free(line); - smartlist_free(lines); - tt_int_op(ret, ==, 0); - } /* Try to decode a junk string. */ { @@ -691,7 +431,7 @@ test_decode_intro_point(void *arg) desc = NULL; ret = ed25519_keypair_generate(&signing_kp, 0); tt_int_op(ret, ==, 0); - desc = helper_build_hs_desc(0, &signing_kp.pubkey); + desc = hs_helper_build_hs_desc_with_ip(&signing_kp); const char *junk = "this is not a descriptor"; ip = decode_introduction_point(desc, junk); tt_assert(!ip); @@ -797,7 +537,7 @@ test_decode_intro_point(void *arg) /* Invalid enc-key invalid legacy. */ { smartlist_t *lines = smartlist_new(); - const char *bad_line = "enc-key legacy blah==="; + const char *bad_line = "legacy-key blah==="; /* Build intro point text. */ smartlist_add(lines, (char *) intro_point); smartlist_add(lines, (char *) auth_key); @@ -811,27 +551,33 @@ test_decode_intro_point(void *arg) smartlist_free(lines); } - /* Valid object. */ - { - smartlist_t *lines = smartlist_new(); - /* Build intro point text. */ - smartlist_add(lines, (char *) intro_point); - smartlist_add(lines, (char *) auth_key); - smartlist_add(lines, (char *) enc_key); - smartlist_add(lines, (char *) enc_key_cert); - encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); - tt_assert(encoded_ip); - ip = decode_introduction_point(desc, encoded_ip); - tt_assert(ip); - tor_free(encoded_ip); - smartlist_free(lines); - } - done: hs_descriptor_free(desc); desc_intro_point_free(ip); } +/** Make sure we fail gracefully when decoding the bad desc from #23233. */ +static void +test_decode_bad_signature(void *arg) +{ + hs_desc_plaintext_data_t desc_plaintext; + int ret; + + (void) arg; + + /* Update approx time to dodge cert expiration */ + update_approx_time(1502661599); + + setup_full_capture_of_logs(LOG_WARN); + ret = hs_desc_decode_plaintext(HS_DESC_BAD_SIG, &desc_plaintext); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Malformed signature line. Rejecting."); + teardown_capture_of_logs(); + + done: + desc_plaintext_data_free_contents(&desc_plaintext); +} + static void test_decode_plaintext(void *arg) { @@ -1005,6 +751,103 @@ test_desc_signature(void *arg) tor_free(data); } +/* bad desc auth type */ +static const char bad_superencrypted_text1[] = "desc-auth-type scoobysnack\n" + "desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n" + "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" + "encrypted\n" + "-----BEGIN MESSAGE-----\n" + "YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC" + "BiYWQgYXQgYWxs\n" + "-----END MESSAGE-----\n"; + +/* bad ephemeral key */ +static const char bad_superencrypted_text2[] = "desc-auth-type x25519\n" + "desc-auth-ephemeral-key differentalphabet\n" + "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" + "encrypted\n" + "-----BEGIN MESSAGE-----\n" + "YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC" + "BiYWQgYXQgYWxs\n" + "-----END MESSAGE-----\n"; + +/* bad encrypted msg */ +static const char bad_superencrypted_text3[] = "desc-auth-type x25519\n" + "desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n" + "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" + "encrypted\n" + "-----BEGIN MESSAGE-----\n" + "SO SMALL NOT GOOD\n" + "-----END MESSAGE-----\n"; + +static const char correct_superencrypted_text[] = "desc-auth-type x25519\n" + "desc-auth-ephemeral-key A/O8DVtnUheb3r1JqoB8uJB7wxXL1XJX3eny4yB+eFA=\n" + "auth-client oiNrQB8WwKo S5D02W7vKgiWIMygrBl8RQ FB//SfOBmLEx1kViEWWL1g\n" + "auth-client Od09Qu636Qo /PKLzqewAdS/+0+vZC+MvQ dpw4NFo13zDnuPz45rxrOg\n" + "auth-client JRr840iGYN0 8s8cxYqF7Lx23+NducC4Qg zAafl4wPLURkuEjJreZq1g\n" + "encrypted\n" + "-----BEGIN MESSAGE-----\n" + "YmVpbmcgb24gbW91bnRhaW5zLCB0aGlua2luZyBhYm91dCBjb21wdXRlcnMsIGlzIG5vdC" + "BiYWQgYXQgYWxs\n" + "-----END MESSAGE-----\n"; + +static const char correct_encrypted_plaintext[] = "being on mountains, " + "thinking about computers, is not bad at all"; + +static void +test_parse_hs_desc_superencrypted(void *arg) +{ + (void) arg; + size_t retval; + uint8_t *encrypted_out = NULL; + + { + setup_full_capture_of_logs(LOG_WARN); + retval = decode_superencrypted(bad_superencrypted_text1, + strlen(bad_superencrypted_text1), + &encrypted_out); + tt_u64_op(retval, ==, 0); + tt_assert(!encrypted_out); + expect_log_msg_containing("Unrecognized desc auth type"); + teardown_capture_of_logs(); + } + + { + setup_full_capture_of_logs(LOG_WARN); + retval = decode_superencrypted(bad_superencrypted_text2, + strlen(bad_superencrypted_text2), + &encrypted_out); + tt_u64_op(retval, ==, 0); + tt_assert(!encrypted_out); + expect_log_msg_containing("Bogus desc auth key in HS desc"); + teardown_capture_of_logs(); + } + + { + setup_full_capture_of_logs(LOG_WARN); + retval = decode_superencrypted(bad_superencrypted_text3, + strlen(bad_superencrypted_text3), + &encrypted_out); + tt_u64_op(retval, ==, 0); + tt_assert(!encrypted_out); + expect_log_msg_containing("Length of descriptor\'s encrypted data " + "is too small."); + teardown_capture_of_logs(); + } + + /* Now finally the good one */ + retval = decode_superencrypted(correct_superencrypted_text, + strlen(correct_superencrypted_text), + &encrypted_out); + + tt_u64_op(retval, ==, strlen(correct_encrypted_plaintext)); + tt_mem_op(encrypted_out, OP_EQ, correct_encrypted_plaintext, + strlen(correct_encrypted_plaintext)); + + done: + tor_free(encrypted_out); +} + struct testcase_t hs_descriptor[] = { /* Encoding tests. */ { "cert_encoding", test_cert_encoding, TT_FORK, @@ -1021,10 +864,12 @@ struct testcase_t hs_descriptor[] = { NULL, NULL }, { "encrypted_data_len", test_encrypted_data_len, TT_FORK, NULL, NULL }, - { "decode_intro_point", test_decode_intro_point, TT_FORK, + { "decode_invalid_intro_point", test_decode_invalid_intro_point, TT_FORK, NULL, NULL }, { "decode_plaintext", test_decode_plaintext, TT_FORK, NULL, NULL }, + { "decode_bad_signature", test_decode_bad_signature, TT_FORK, + NULL, NULL }, /* Misc. */ { "version", test_supported_version, TT_FORK, @@ -1034,6 +879,9 @@ struct testcase_t hs_descriptor[] = { { "desc_signature", test_desc_signature, TT_FORK, NULL, NULL }, + { "parse_hs_desc_superencrypted", test_parse_hs_desc_superencrypted, + TT_FORK, NULL, NULL }, + END_OF_TESTCASES }; diff --git a/src/test/test_hs_descriptor.inc b/src/test/test_hs_descriptor.inc new file mode 100644 index 0000000000..70a2c7c2f7 --- /dev/null +++ b/src/test/test_hs_descriptor.inc @@ -0,0 +1,224 @@ +static const char* HS_DESC_BAD_SIG = +"hs-descriptor 3\n" +"descriptor-lifetime 180\n" +"descriptor-signing-key-cert\n" +"-----BEGIN ED25519 CERT-----\n" +"AQgABl5+AQoPXRnCGEOxIup3AcjQXb8npNiUFm2Qv7A6JKk/K+EuAQAgBAD18iUD\n" +"nbkUblnUvTHzipq4bcr6aPyFVB42Ptobg4xr8s3VjHiJtjs9MDEdr6nXS7UlyhEl\n" +"78vsuFEvLp7cvAgGxYY1xGXdn5RdHMCdi8W9yZLKMQX9OuJckmp1C6q+cA4=\n" +"-----END ED25519 CERT-----\n" +"revision-counter 42\n" +"superencrypted\n" +"-----BEGIN MESSAGE-----\n" +"BxzghAOjM4De6Z6eGTvBrTP2SJDdQOYV/u9qtvlFsa2FRQWk20Adv3zJ/AI10CQO\n" +"mUP4DNXM8FWQYGTvmD7wGz2/cXGjKwBXg1qO7zF5eP/D/My1sXsIfCcb41mkheNt\n" +"xn1I5eKXcnghtd4lw7OkPVjSb/Z+VARUMmf+0qSNgmHLgEVnAoGJsn8W8B4qtIay\n" +"4h4PuV0jPPlqJx6jMFOOEW72uqnfmqeNvClENXXW60xhnaxsf0up62fuW8ktu6Wf\n" +"lnX/lvTstBFZZQ8/XI1+G+BPf8TZf7mxu0WYVg1s/KWYasYMSw46as59nkqdq2Ii\n" +"qJnqHX/R20mWBhgpLse6wO0aNpky/rozEnikaPqyO1DShf6a6jXY8ADBg7spnK2/\n" +"h7sf1+F1xfi2dy2WGxc1EUMP1kTVUmbft7kOo2nA7+3YZwQuSJHaN/66HrzU2x5z\n" +"ayRUJ8+qDtfpEf17xthc/Uh253blFK96IoJJiqBfI6xt3IqOdHJq0OOC9zBbF6Rj\n" +"vKMsaxmc/nc6uOB2WePYSgkZ0qs/dRKBJs6+Ahn1KdGkadyd8mDKL86Oe8lncHdB\n" +"m/6sQjhKqFgngkCDOIlEJyWizqfN84AGqD5Zyxq0rbsN+9KLsHFfEbCRjgqjO5nS\n" +"FYSFtuKgCZl2gaYEslL1pIEYE6BD2Whjn/HWTRyWiULJr6SuavgcbxeNEQDuVCC+\n" +"fm0X7Z+qERaMAMR0vTMJK/NzT4GifrDpgmgbxc+34CtNBF5TriM8aXTNZZlsW00k\n" +"d0XRxFbbbtiT5VOaEHbny7R3MdTVutEc9E/BhLBvjSSrGX7vrryh6Oj++nthIIzm\n" +"F4M7I11S0TyA+UE06qF1C8rKmhcqU9MWy1SiccJ9KOWhJ5xwlsXBIID7wVygUhVl\n" +"ovzfKkDDPfRoBch6NdVkxNJx3gb63CUmC2TzfwOMh973nntMVzqqw9A7jYkro9ln\n" +"217kHUwMk3e83UgFL4nn7NCf3Kj0zhJ4jSfAsuQpV6e7dhzrlNya0lqrUsY2zFXP\n" +"xv8wUtg6Vo1KewgVQas4oElkgFjDN8RJ7uBAwfuE/b9NnYJoQd76G8DHei/1PHbu\n" +"tbtwN9I5RHaTvEOfetsJFnIAkCG6O4CQpzwHu1DdvEP4s6/el10b/4awBJ4VwOVZ\n" +"YHSe4X0DStTV4Cu6aLh5OvrOmGbieRj6HdGQ6syYCaEBTuxbBUUpjIAfVlReAIph\n" +"6aOrY6HNcCmeVmL5qm4dKr2XXOREsnUFuMqmfQuQd9pN3zlmS+RqCgSJuFrguFpd\n" +"mjo6UxZvbjE7yJjtCih38HRe5BaigP5RDRkXmiXjqJ4koLJpyjQh19k3BYGcdxUC\n" +"RCcYXydbGF7qHlnoaX9HnX7y6ZRsyKQpt91PMTGOUsB4fS8NhsqPpl2gdp4poLNs\n" +"+hqjWZJ3uuLotXBcgM39Dtq9tqqu9vM12T80UAfWnVEHrBphmukh49EhEr2sx/la\n" +"kAzRoTbLyTdlGVei8hI7/RtZIaIcOkzlhcFI5zmBlydyrv6/79vzt6WI/w9GVGpM\n" +"OuSM0NS2CDJ7Iw412nz3CV1pEXB551ZBmbme6NHUe4EtEsDbgkP1Z201H4j51yVz\n" +"wNoIksE5Bh5XRKuu4We5f9KZb+AEG9kxKJ5DbJk2YGJEQFTyfv0H68pl9urstPXD\n" +"aMQF806COe2uhGm5gV/skvPVTeEvStE3K8DxZgcWNcTMVk8ZjrUHNfguVVToP8hT\n" +"Fl4Iqo3r+JZEAGXnAbTpxUVC2Xxspf3jsT5xhUfB/NOexZxrXWnQZ+pscsbow0ba\n" +"GATtakD3TF2WBqq5WscmOex+lrJcBCWVIzVWdwi5ngAtm1S7efkJlFUvmi4OuYnN\n" +"RyZfxVIpoer8f2/xPXvxkOWFminDy5sFEvlh2/pnymfKOUV+CKih9ZApt+izlRJn\n" +"+sMIOW6Jhf/WYyjeN6KQpwi6CDpclQJXA1SVoOVVL5A3lotLjs0x7ThIcBoxCZBq\n" +"rFBhBu1gJgJ8guMySAHssIvhHHwXJsYEwzWCVAg/zIUXy4PLwIkgHApl+vGcldGv\n" +"Br5HNCuqQ2pD9z2RvzNneB/LrYB214i+BP2piO5HbmeJBhby93blGXVfQewQT6aF\n" +"dBlK8/jQM0rvb+LkmvQm2ypOttRpX2kyQXooJHYTTusaUr4jVmgngCvGtgqAQVqD\n" +"HULXfHWvugZbAh6dXF7gKnnsyDOWwAgy4OJRi8i0jCaZ8aWSFRUjeGKT26dg/ayB\n" +"U4QfMb8vL8tMdXVBfQLGcBgvrzQYrY69//pV6bX3SbLfUfWXV9eqUVWVPqVyPEwa\n" +"Tz/aGVnGv/dY8h2cVnrgSXJGlOO+mCwSl+k9nk7VcEaKYuNlaOP3ZlKJvVj1LefM\n" +"FODh4qTDBo5NkyfKu5fcZcOqDMBeGWXZzltE7CmvY7fOpDNMsuAoXYWI7q9gK82F\n" +"w+nS0tVFCIWYa9DgGMv9GKTOk4Ia9elkbWypdRE/4oz4QxmHsArEsK4gDI+wmcp7\n" +"/NsAZeuy96r2YDIUam4uASKOiAqrEfCv6B6cYctdYwZbAEXdo4fkGrCIjNRZmZGv\n" +"kcZzHzIymnAmKRTkPt/LQ7Rx27Qd/Vt++B3zt2ORFuopqowOP0ocGZtkm0daK3Fc\n" +"YDXMwIpf6Z8PwvvsG1bQHcSR+cUZi7vK7+hj/LGhMPafHM7HmFUbAxpJYr5CvR6y\n" +"V1pZQYltT8xWayCeMHlLAAg10RgDkqCnY4dHnrY4GdwI2O7Wpxomni7qVHMjn+cN\n" +"UTrd7EeVw+dxAIYosuqG7ua7ee3VGoOs+XMLrscAqHahfGbyYC+j+6Tow4qwWBdU\n" +"/W3NJXnRWaHTXFHllpClnxggPRQx4yPtgTOmBBVl/O0T6i4Bv0ygsJeZAqC3VmAJ\n" +"QodQTzGf2jwqsZf4uHKQa0EKGQvTGjFVgAFNpHmAuzyqh0b1pq5JeXiFERGsKC3j\n" +"xcJilq1XeIx4SL38YNuCxi4pnyJyLnGGHpNjdjeFO5lvgCaKPegsPo4hpNpTvBJ1\n" +"D7+o3E5CqxzjRt9kQmtwBbuH/SQX2T0x8aQ6vhwjj8ftDfw+FbjpMR9zfU0Lf8V7\n" +"UjVGIl2yiVBGScBZu1nSD83PxjFy3XdFtBYoU5OrlXwBEYQs91jwK7UCiGtjI2Ao\n" +"ZGkJaBd4AqP6voyJiGnC3LWFcmeMyzfExgiclQwfhFqqf762TX5JwG6xGqtdcNKS\n" +"k54LlcI/RfvJw3ncSs9YsodZr6Jz5irpRTHX5WwCrX9mLukP96SXo29bIXEZAqEr\n" +"ZxEcF0zlYE+km5bRfRCRcVVrScugCshSNLOdQp6fOAtHCl7rdQ/8Rz7oHuqieLVi\n" +"UldRsAmpk9fIfRLphXj4j24jRP0VtL/LoJwakWTa0xO8K7eBAMVITI+HgFfN4wSO\n" +"Yh1B+bGD5WKxFsWSgBMmW+YLF5ZtxVmmbg7wK2dIpJs4pjg2YO/MTO7SifJ9kjcb\n" +"bCc74Tjs5mLLGGjGCIoXfda6WXbt2it40XhFk2zUAcPPsgjbctftkaWph7JSZpmZ\n" +"fVcPqKdhmA1U0LA2XEOMTxGyCAeseH6pJXZm9LdBozc1CwyWP8XEDHHJf35vfPKY\n" +"JDe2PanFepIOHaoRTgE7ZkGWKzOIKlS0Ucr1ezVfcxiFgQUNM+MYXXbUz51BVVq1\n" +"Dulg4VvX104nt/ULijcfa/TsE+uklEnkyk1mhavH337NQg38XF4cAngNlUF4nSW/\n" +"j0jizbAtaSx1f7q6xqPm3zPRlHrGQizHXLyl+SLzDUVPOXbPwcoeev97YeeyB6h5\n" +"NBbIK9hmekNDmYIwI0bmlrg6IXhC5pyvRe8sQlV+9wBY2liF0M1mq5onW3a55afp\n" +"+ynxXfQucb1HxZLXvRIGMBgWSQ7HfIPASqSE90Vu6qQCfkOW5PDqONr4BM65V4+g\n" +"AYsVEgaosgHw9CF7yKgkvmZpToOtGpCHVcdUeeY2/rrQnAQeSy19gj/baJ+OKl6Q\n" +"i1EGU8Yqo2r0d4XDFp/eKgC4sv57qp1PwkYQ/HKqoelJ09IAZL2sQWc05BGwt1A0\n" +"11qDIEdkZBjzK3qUnY3QlOuoZtALZrnPg56SlF1RGDOPqbcF+3opqsvzBoiikh4V\n" +"WV5OUYjRDMUDLQqf/OkuktdYf5N3RcbYP0XsAvY0ZWG3Gp068b3p8peCpkDzrF9p\n" +"bQ2ZvS304tN7+p0hif3+JyZy5/sxl17RxTeg5I3mo2+J0ptQDYwF/WadONO8r7uU\n" +"YlRltFtQfyMzyVzHON4NHGjZh7dDGtWp0MGeHRBHQsC8bEChhvWme19VXhgZoWpl\n" +"dUIZkSuvRwiURXjhKbZrEdJbVmr9FX6zoyOahv3VnmcEARoR+umxzvo3hGQPbHyH\n" +"jTsQtSBjs75/9fCxcYmBWkh3JHVDVsCbV+z+5KZpk3m50J4Y1hC8hvepC9CaBqOM\n" +"DjfyXh58x1yKiueEbcjSWsRuF7CjcrYnFUBHOs9U1j9WytCI3fhOWPMgR4UZpGuU\n" +"WlcR1BXg1wYxX273xOS/jYn9MLAVlbRpPTUMIH9VRP+sc8+XaxKpJSCl4C+vcwNY\n" +"1YdKD2QiuoBJ3fXGtqMVRtn9eZvatSJuY9CnRKRbf0hWmFD4D5RkiwE0WkdtwoHR\n" +"uEXJ47RlF0/JDU1fY1mXBkq3usvB4Absy78qL06vh45xkk9bHbdf+7Ao1RQKmqiB\n" +"NL5XnjBu+YX535WG7t7Su3mTCJXYHvn72ATxry8yhSLgWqt81STkRwc14HmrOGG8\n" +"Gw7bz7y5vikj/rnPyr7ry+QRgNNDDayAqenAu2vEAzWir0RQC/iZ9rc/r7YQWGgL\n" +"Xrd4TQ6rTZePARhwB3VomnLDDvLvi2oq/jPzLKSYM2a7qj/vBSbJ/NnNaDW5Ccew\n" +"RjMI1lIHeedqYTVAW/CKoSEPcFSAzi/Ija0gcWLgX5xsFDGIYBepAX0KS9426kMu\n" +"0r/V66zmPMusMilqRTx7KW+jZMVxXVc2zClcdmohMmtjsbqLkczprfSbdGswMv9Q\n" +"I1ktHJHIRD0vPeZXnvKZsRKZw3sKb0ltZi33ZxCJFQPeGGtM5aAFthj6awcXy6Tt\n" +"DPUQdCU/vh1zmGRAX17/Xb0irfvN+GhQLEl42pzhigJXc/rCG3a4Na8wT+xAIZVf\n" +"WUI7hMslx5wA+iB4lrAjCq0YIrjINI/lHYpotXUZGmz5wz0jOciTmXMSx9du4cpk\n" +"fIQJfR+fr5tG3fjHMgSP+p+RewHkd/7RUAmHC2k3cuk5pCJvUVJrhUIqsi1fa0LG\n" +"GA0UU6Nr9tpYdNr1WkbKQjxTg0D//AXe61jmUS5XUU4AQf6zQVfN0TMtmuYeacbK\n" +"4r6Z1CSIRbsgcnL1BN8GSd4KddkCqSk941aJUCoX+77ou4t0btVSB9FnLKipigtE\n" +"E/Rpmv+81lA4fLiIag62/pcJ3uppsZ9aaHdR10SMmuCjAVLYHqhJfrHHn32dyqLK\n" +"UI8kEZJ6GQzHLUXcGbbdnk1Qm6JwO8TeF/oQvh9y9py+oAyFy0qzP2UeUMUI2yRQ\n" +"mlWSy+wX1DbVDQ3UHwJjWp65CgyYXuW8eCB0AbyF0kF4KGf7/7Ae7tEGbmYSm5MA\n" +"71z+Azxtv5gRyRb787V2dyo0wcmbRlL7iUBVXNM/czQo31tAZIwLc+lKNp0SPH6g\n" +"gJ2yX/GeDSFNAeEVUZ/f4KZIa7QQsnGWrUr+agSnQFkySmIjWYjwC/abJwah0v0d\n" +"ulwr3tECaaXtoWVdYXa3utEclBz9umBwMJ9MQCm4Kx7dTYUWFT3bMM/ESTkGPcfm\n" +"m+C4FsqFBs80WY0ududu50vTDSdJt1RqZ7Sg6DNH6acBvWyXOpT5mPJKUjnSFwyG\n" +"oVLgv0aDDx7lLZdCkhyz/Ff5LNmBgQsjGllPszJ2gTZxZ5LD68S4kUirQG/qtzlS\n" +"PGfDOC79SMZGgsoAnr4wV3RUTxsTVFlxVHsBMB+EXOFHAr3wHTVxUGBbGzxBlQ9w\n" +"I/jlu8LIIexXAU75HS5KCGGfg0Z7BLqEzqpMKqcBQC7BD7GnCXrDSQ2DCXnl7bLN\n" +"lIrQ/z2Y8AgSdED46R40MqyyN6CPPNiOCjONHZ30fLEXuEgCp4R/+x0WWsWpjGk2\n" +"Ydkc03cx/X6moUYxB5HTqTodBmAQuWMX0rxFDrnR0SWghWjdWth9gjd+dvZ82tt1\n" +"UMUywDPhcYchtUi2lnqnYJm5p00GN9Mk14MC5ZC5qP57IJVqxu0ktOMpks+CLPnz\n" +"qp9OBpI4sIzd0y0aUJC2Gd+E9aAhlREIiicyBDmxLdk1i37QeeCralI3eubLNmE+\n" +"CjDjD8t8FUGPpKglSD3lfLTqbp2TUvyWfvJC6ulFPNsAbeLHTnPnpyPQmWxhMNGt\n" +"h67B9tbYww2TvNwqIgmB4+YIR4/pSs15TpAqvuUvjpmRwGklqgiSmrQrlIxCxux/\n" +"mfsaL3KE97wm8BsaMpMkjUL7ByTIFhFZ/gHPTxaFpbqTZ4G+lABLgp3bIsB9Dl/P\n" +"ovoqX+qL2Mq9T0GrVJGfRBuA5hISw63hx5zdsj2Cj3A3khHPqR+GRN/rVYUuOpLm\n" +"z3v5pU/74vZRmNMAIhyhmweSEPNtyVkgSdgbFErqvhxN0om2Cd/7cWh2g5BXHyUL\n" +"PBr7ZkgfsE9TnuDH7Z0JoBqXJki+MO6nqz73oH2Mm86yxcXp6O/ieKTollrUJ3yQ\n" +"P6hLcEbYPzUV99del7Va5Wi0nn0wbRXCGVQdwY+iWc7pT+VVlncyg0TvLXi0OtOt\n" +"O8xbT2DAzVXxMwOsKV9ZgS/0dtwzwICpnTzBI/47V8GYhHbOUNTBPZ52GaXMeWlX\n" +"cuRGb0+7OkKWuriyOQ5z5xaASCVfqgnOwSZYiAk0gcDoK+JHdr64/sMoJhH87R4i\n" +"2TO90whkScgiGR7A06Ba42bT1nJtI6pxvzdB2b4BDAs2Lr2OdcB3BY1dtzKjFkw/\n" +"qfIw3F55UQwcs84ZEFQDAB/tmfNHajblDFpXR4N5QvU/PdWVWJUub7oNyhIX6ruu\n" +"ln4H7lpTUHJZ7jkr1qpnvkztZtHGlpJ0QdUHgyMYER1xU58Hg77yzIW3EdAa2PyK\n" +"1t4udKbQKChShlShIMzwzj57ss/69QobrpYAHYi6IRMaMUGBfipGBACK3yeXsXz0\n" +"c3Q2J5vI6QbxNsiJ5t7Ry1IqotbJcU7HND/yVUAUbEg5CpEDOSeSOW/ulyLuFxEV\n" +"lRTwIO/68BoIoR7umlP23/1N5OYzaBHhH2nThILBovHeJRXnGXSgeFfwSj7LIYEV\n" +"c1MdDSg/HzoADPXyEPLzqFzHRHeNiqEolmOPnFh0hRzbMZ0W5TQPDGWJdF21g816\n" +"vA0WW4UQjLM+vnX9kKKLA1ut+9JWk1dGKsmWtdWUDfJjUP/L6dS4OYEl6O6+SjM9\n" +"GcyGvHTiC5OpJllYpvELP/NjtTf9or8Bmruuga/axeOuS5ocYLK/sGRlmO6Z96da\n" +"QSlyGWEQAnM2D1cDmdd4CetPslOVIcQ41+coWCi2xg3UjO/bFK1CA4R1rb4ekXfs\n" +"s5U2XChyHhUPgl57y1r0ILXRXWJTJ0/F9hhu4aYQVFeIV/IuzJbmTKKkAcCOH6ys\n" +"qnu2BXz8Pm2tU10JFfRcuZ8rHuUyUErA40ESsLijON98GMwL4Rat9ZSCNS5hlK7y\n" +"yRJdr0ITp8oTbduAoulgWOvtcw1L87QBVojWz3cbhXra+WITirYuGNbzfmZn1WQM\n" +"kukEZUEHSypGOrHr1XiuY4Rw/DBaJSLyZ+VybEOfXqXkDBh5s1ayypBvzrzFZCIn\n" +"PJxIVsvrkhrpEbTJ9d7zLWjhOa9ZWw8lAubllbGm+7qCfdHmGsfBtvJdzx6zhB1Y\n" +"otL/PCis2XVTBEDJeB8pGqKFOZjNz8PC5qP+ymtAfy2ktl/u4HsFlxV7CsEKGYPm\n" +"p3LqnhPUy5M5gin4E4uPPyzzD2kcM3way49FKWUKlblQU0SyWtHRmMB3vcVmyT85\n" +"BRULXF7jgog7XR/EMltwQyJI6GcUCrnWZu+G0BEwXG+CsgCzE7assDavc1NSGLZM\n" +"rmzXiFFyfk7CE6lW2Lm+oWaFwKdvpmNZJFGGX8ZHRE9ZvkFMnfw9MYf2W7xa0jf7\n" +"k3c6X5wMuk9mznVtq5itNFVXh1mT1ujeWOiiqyH5UhQQjj6O+ZXt4gqt/jT6dd1i\n" +"jRuhhxaUGOlhpVBW/ySXhZ+HgOy9aCJ/bgjRGaqGixogk4f4rcgigHruwTpOQuDn\n" +"xDZ3Xns70S40WtHSYN+Gbl9nIh4yl78aNnA4FVtTAuLlVKEKlMJi9OBFuP5TEczG\n" +"+0HTwL/VPSCI+8FUZBhlz3YwecYq6dY5mS46+luPW+5Wl+5jtzb8V9oxVnRx2hQq\n" +"B5HJsM5FOOhHDHMXoCsevj7N/ufK7cU7Wbr0DkgYRwvb0ZJB5WYgcaQ0W7aduhGb\n" +"MQsandhP8Ajb2cmLobi3mHHPbcEkvjT8JP9Sim5xtfF+oCMMB5ByA5bI2aIFybZm\n" +"jX9e/V8wNgtpDKDVKPjB3+9dj5gU1N5JsrjQwQDB0kVRMWdpJCtD4hZ2+T/QE3SI\n" +"f8Rdk8pj8qBzRPbnhW6qsoWZdjMRC8qixZqHw4jol09UF7Ab9hjEF5ZDTfNGXwy8\n" +"/hz8su+mr8hhrlCrOF2vBYUayAA96zhbDWfg3Pdxo9bTn3/DmyAngL4J5Gu679xK\n" +"rWN4j7uQG4bzTa8WJb09/lW49UzWvmrz0c6/yexk3T//xDD067FafdnP5pYs4Cvp\n" +"rCoHpXbKjxx99DJmb5iXW0JRLSpFSCbf1HPHbmzST3minSXap5FCWDJcSgExKIJp\n" +"DXZ9rk0LMnQA74MWC5gjjM+5t0AHKuNRhJbQSwYWTKqeApXho53T/COlfDlSs2tb\n" +"Vz1Ia5z7IOfu1QheE93huNAHT3Ob+mSmUq782SqFPr6uwud/l5uP3HpcuwugdlFm\n" +"Jw8uBBOQ53W4lLbYfQYTVgieClVhmYMu7Ye0xYZ5B2jf714sjZRMa0LCbsyj58xH\n" +"uzs8ddNN1fLMzb0JRBE8JWj5PbxhA/sTwMkD7SnEMBUTtP0obmuQ982aTfyvQCH/\n" +"ve8OUPtYf5XWNv18mpR+h+riMt1Y8Eb6BJzTMFNWagMJAe3JV6A6upHroNFo2FxY\n" +"1XPRM1Rt0zKo7GD+oXnixfpl1aG8yqZhYo1ZC9buaHwH6zvM+xoiGD0iujeDtpVy\n" +"Vp6cAqqaGmrNwcPVBLc7hNKrJnbFKyhjL5/xp9j6jQov1aWQ8HsaNvh0p2ljmlwb\n" +"daTYZcwLgSgPna7HhiqnOSAmXZ7St/qe/b9TqBtIVzwzmtevgMyG98QV0syFP5X6\n" +"2Jc1g9733sTZp7njq4Cu07JhpICpinhLWR3nkODJbjk/mpLcQZgtV6W749AUo8oT\n" +"jRVEJ8MpCo1h0bVDxsRnA3DrMneD88L8/b10aHs+bPm1HKbCmT+kJAFaUQNa8JvJ\n" +"pReN37qTWvZCte7vaPAIP5cboATMu/J4t3izpm+YJoJlWcIegGx3kQ+17P4MbgDl\n" +"S93U4sOLvTk9+MoyPo9yGWU/zHgzcQ6wCFdzWMDRswuh+/4TJ2+yg6maq3iBtj39\n" +"gNLMR+sRgGGvYisqE9bfvNQy5IWrABBKcSBTXeTM1DmW6jv3TI8DoCzCbpjqcIwT\n" +"u2J+7k8wJEHPcAwnBjlyWphVvwNwM0cXqOnlJZ/4z7OGgjiNEem7TMuvxk+YkiXK\n" +"OzftdTjeIpzBwsGRP8/teMBpjS95M7GloKtxO+muBVxXbmsq8GBRC9vtNJ2Ma/xP\n" +"bXvd+7caytD3ob6ZfOzCpi4ZS8uByEfIMxlgZ5Sn3jhgEkcIU+YW9b3teMZOuWdA\n" +"QpDCoMpXaHVyRqwVV59JjmftiBnNBEo1/QzRj2UxRi7fHMfmNxL5LRM4CHSLUSCq\n" +"Y3A3pkxvBHUzemhynSFvtCPa8GHiUpe9so0V/2hlgaENAVELPjMlWytaYufRllgy\n" +"tUnCd32C5PrrmYzMKnxKRPXLcxLgziruJGSks9vIspoPk0pWgkZm+M9fRpJKlWHF\n" +"yT9OOGBW2yynw/yvXssxJmdUDxVcWL4uS2bZc4s0Zc6RSL9uQPjZVX0JLj+cXfx4\n" +"93Gn5bDhMgm+CGM6j3RiAAD7tT5V0sytNFjXd1A4U1u8yj3wzhKqOtZpDmuGUlMn\n" +"EODu7I5KtWxOTPThy7TecI5r+F/6KL+2MOtRhj2PmlT/Xed6PaAmDkQeiXGps08x\n" +"u0JIpuB61axvT4PAsKZNUd4ExbzNxRDAARUMgY8krpmyKZyHVFIQ19uHM2lGl9/i\n" +"h3PKlLHYI8RsHutHElzq+F5tWd5AA99LVRZX4axAVIQNiqRg8IMSoCwUaCCbjUMz\n" +"sJCo2t36GYk5S2BRnfrCqYoZRHw+ENYN0tDEMhXq1OqjvNHW3TzL3DsUhM6EZU5n\n" +"cRR4ynUvPqqWFphLefRW10vCtaW9roJQZyFYf9kd8xgW/BhcDNbTTaQ1U6xCHgX+\n" +"78DKee/NvY1WIEBR8X0iVk5XlSJb14eRtxNawXFyebVdmC/DiMNgnTBncMbePnZi\n" +"KCl1r5xqo7tSIoJ6Z0l6qINd89T9fcg9mujTVwsfQ+5/kdEy0Iw7CQcTOGvMaoPX\n" +"IAJlWSVeZ8eu8kmsD1Z8ewoPufMKiY4cPRAK5bCDgsrK6bAExOlCwPnNNM8Ym1Hz\n" +"aYFeGs5sW468Qww+Nbl5xcNFKtwUKZ6EebRHjwttiyTgCdAhv9wL1u2WFydWWgkG\n" +"rwUbNpSLKls+pijCeJAscvxzbZz96iOaYrY8IyzGBFwfgFAESfnzBc8SQjZzMzoO\n" +"vmYIRon2m/5w5AZA2IjQ4VxXJDK6XExD/ZLsxNXzMnROD++hE+s8DvPlRPmN4egF\n" +"gAzJs/9t7IyE/dDf7gSSBqzEBbwduD8ozzYHwELUc4ERdRzjEdBM0azT61g7Yilr\n" +"iT5Hy+2iw/pNwiqVOYiAbj2lwcoMlFZmdxviD4IMXdsNVWsCAVJL0PqIh1UDDb3z\n" +"Urv3idBJeSBuuFr6AFS6kAgvrwV/pEGoBoHuyii/rZxVugGKeuMynKEvSHuFNuQU\n" +"qIHcNgqQR34v2Ut5pQ1R8s7K3Rae/AhE5GncJa6FJmB9TF8MYMu9PlSZV/eGv8UL\n" +"IDWQ7sY3NdhZini//xtwPqIw29yOeZ0X6Aqsek9tfh21UwKSpHb7T+PwXYmoB+23\n" +"p3FXkP/rv4AGRq1xJqFYzKJvwsXqTFuNFWP74yhTg6rC90w2p5TeH1rJMAnv4u0L\n" +"hGtG/NL+D1Tzdf00TYAjno5Ia5dQJDd/eO+Ygqnhl6hAqGtS6r9JhIEXw1nQD7SC\n" +"lj96ZuKdUWO8rpIiAtvHAsn++xvMVPm/S1SwA8oE049iVwS8/eNNiMKoSlTlYc7o\n" +"pusBZQrVF4We4HHYFjysBbcXlvoXDd8LkZ8Nh63VQPnoIGNKH2U6aXCnQcJ8dZqO\n" +"DNxL4uyM4A578FUUR6vxqt2asnLHQ0Z7pPE4uqtz/WgbiHI/i2oHS8oe1clsifCw\n" +"3ZY33kflqLftkTNka1oiftDb0OqFLjkS7/AUorqHazw53gM3gqJY5EXA3Px9+nhu\n" +"NzxSK/t41JoCfgQJHMkIWb3yUcO4OFZeGCeAxIJY95hv/brt6/WNielXjNaohYvc\n" +"lsSUHEJRHwVxQmWK0LS+g13HAgOI7cNt3MA8sSkzTneHGFgEvmrSyb0wCEmushC9\n" +"mjQThvaxfQk9douA/cR2bHr7axXqv9vjztmxUr0a30a7lvLMBQbJmFtJJylW+tJe\n" +"v/vKNOB+9mK793cttr2JFnMhwUKFKWiFDQJtxw/eLQWY4BJ19Rs2x4BJgmV+u1jB\n" +"zR8uvxuArG/cqVEJsoC6uuSzhAWSwdvumijO6yuyWF6nHY6aAcy8dyFQlDFHAd+/\n" +"J05Lrbzj4N9lcI7hPalh0uMdERGvtUdT8QRm5ebP1zogYEkZk/1GOU29dMawkAt/\n" +"SWhp2yWdjLt8f5HQKu72vUF/yyTfzfdqQqJwfthP7+vp+sHDO85AMF45uU9g3pxW\n" +"IbXSbZ4fFGC1/41db/2GOHFgaheMXj0SIWHqQE1jtihr3BBBO4b3Ccz5QCnrn48J\n" +"8L+QRdh4a/cAx4ty/oHEiXwpSBBSFRl5+y2NijC8GITA5dRjCRWP+Y0zuTrJ7j1a\n" +"h+3kGs1kxqskhaEuhXnXyknGLjXrU+ewRGhHzP23o5betVhX+c1XjVqmJNZ5OPn/\n" +"wrqx/XwoIl/3F5lMmGDG9mPtyg0E227nKl9Sy0Vbwx2tu1unjOlzSCa7lpoD4TIX\n" +"PBJ5+Zb0CE6HEt3V0ec1m4uUe/xObAnzyr4UbzdqLaMy8vTcF/qsncXyPBjwqdjR\n" +"ReDAtt99bAPY4roPKGt8dgKUPE0t/XoY+SlmUp75TkZDXrOIJXpEW0GpLPf53T+W\n" +"Ex3KtfLAnZzrw8+dIageY7IgoQ85h3sYE7uEI8QlcO/o4udqUzTp4Sn4sWvdTLrx\n" +"W7ImvK2rsU5ubVdsEaFKM7+7nxGn2JyMpIWFz0SbP34CkXHhrXxyRD+GhMIDHFxV\n" +"uBnZnjJsw+ooIm1rL4I7/VMWEwmVegreT6w9Gsmb5igw+zu9v2YBgTOhysA9XZd4\n" +"7O3VjqKkhTXcBqdpRWuz8gPQ+4rfwij28Gg2alG04Eh3G3868NOCFJhhaHVmwYR2\n" +"ygRm6N9eDW1bHhYSN75HSEb6aIebk+1AT4S1QtJaPSH0EduIXO++JYAs+jIFKy2c\n" +"jCVFlO/LbXl7iCdXurJHpSbMNmZFNUri6zEolENODLwke836jBOKiVrWzLnEMxHI\n" +"WDDTpLTYhR3C7sEprpEQm9SX2Eik3WxVb4ZTb7SZFU1y1d4tWnjGu3U1D+vO9wVq\n" +"Sss9lDipbkhQ9k4j1/Pqozaxvi8lYLbh3WEjK3Iwpr66Bk6Ai2oRg4b+7vzV4o+6\n" +"L47JPJhajdHac0CIlmupyA4eejECS6OpoLDf5Wr/616k3dxM//3kAWGUnXVw9GSo\n" +"UF5W8AaKlaGZ6EZk09NyGSFRjEs18z+g5ckviGF0EhZI7ZPWQQmlqWUsL9O0S4GO\n" +"ZZ9f0UhNmHEspcugbs7e1yfjwGVyxIBkrmxpkmfHE4Gb47UGlJevg2OvZOPT3wMH\n" +"vOds2BtqdT3tuss9k+7hsISGse7isEOb7TN5MHb6yyzqnCUZhp5m3Iag7TUkiyfU\n" +"jKH5R13tHqKUoJ2rofWoLO2H5xSfp/lqF9sLd4rJ+Pbjhiuvfwz5copYsuTNL4kB\n" +"SPUikHlTxSOgTBYNV77qxpsqOI3+iziCrSqHsxNdlaA1T3fiq6SeZBNdD822AYm9\n" +"L5hbcgpDPEEwT/n5kWNbRNueerJkJwboaOnT1ZX1601Pwj5QDi+YM1NYy5PsdWxb\n" +"bPGpQyZ+uf917q9gV7Ykr5cic10YD11khAghr0n6fYfb8Ijc22uP6m47KItDqQc1\n" +"eFym149F56B0yg5FR85Arg==\n" +"-----END MESSAGE-----\n" +" signature Of+jvQKzH9ot2NV5twlDO2CFbzLSB4absWTwG58TCHb+TWgQi3z6SZIoTnGGY/uicJgEkCN++bZZR49GiyHyCQ\n"; diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c index b207cd4ce3..c6197875b5 100644 --- a/src/test/test_hs_intropoint.c +++ b/src/test/test_hs_intropoint.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -69,10 +69,10 @@ helper_create_intro_circuit(void) return circ; } -static hs_cell_introduce1_t * +static trn_cell_introduce1_t * helper_create_introduce1_cell(void) { - hs_cell_introduce1_t *cell = NULL; + trn_cell_introduce1_t *cell = NULL; ed25519_keypair_t auth_key_kp; /* Generate the auth_key of the cell. */ @@ -80,39 +80,39 @@ helper_create_introduce1_cell(void) goto err; } - cell = hs_cell_introduce1_new(); + cell = trn_cell_introduce1_new(); tt_assert(cell); /* Set the auth key. */ { size_t auth_key_len = sizeof(auth_key_kp.pubkey); - hs_cell_introduce1_set_auth_key_type(cell, + trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519); - hs_cell_introduce1_set_auth_key_len(cell, auth_key_len); - hs_cell_introduce1_setlen_auth_key(cell, auth_key_len); - uint8_t *auth_key_ptr = hs_cell_introduce1_getarray_auth_key(cell); + trn_cell_introduce1_set_auth_key_len(cell, auth_key_len); + trn_cell_introduce1_setlen_auth_key(cell, auth_key_len); + uint8_t *auth_key_ptr = trn_cell_introduce1_getarray_auth_key(cell); memcpy(auth_key_ptr, auth_key_kp.pubkey.pubkey, auth_key_len); } /* Set the cell extentions to none. */ { - cell_extension_t *ext = cell_extension_new(); - cell_extension_set_num(ext, 0); - hs_cell_introduce1_set_extensions(cell, ext); + trn_cell_extension_t *ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce1_set_extensions(cell, ext); } /* Set the encrypted section to some data. */ { size_t enc_len = 128; - hs_cell_introduce1_setlen_encrypted(cell, enc_len); - uint8_t *enc_ptr = hs_cell_introduce1_getarray_encrypted(cell); + trn_cell_introduce1_setlen_encrypted(cell, enc_len); + uint8_t *enc_ptr = trn_cell_introduce1_getarray_encrypted(cell); memset(enc_ptr, 'a', enc_len); } return cell; err: done: - hs_cell_introduce1_free(cell); + trn_cell_introduce1_free(cell); return NULL; } @@ -122,7 +122,7 @@ static void test_establish_intro_wrong_purpose(void *arg) { int retval; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; or_circuit_t *intro_circ = or_circuit_new(0,NULL);; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; @@ -154,7 +154,7 @@ test_establish_intro_wrong_purpose(void *arg) tt_int_op(retval, ==, -1); done: - hs_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -198,7 +198,7 @@ static void test_establish_intro_wrong_keytype2(void *arg) { int retval; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; or_circuit_t *intro_circ = or_circuit_new(0,NULL);; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; @@ -230,7 +230,7 @@ test_establish_intro_wrong_keytype2(void *arg) tt_int_op(retval, ==, -1); done: - hs_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -239,7 +239,7 @@ static void test_establish_intro_wrong_mac(void *arg) { int retval; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; or_circuit_t *intro_circ = or_circuit_new(0,NULL);; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; @@ -258,7 +258,7 @@ test_establish_intro_wrong_mac(void *arg) tt_assert(establish_intro_cell); /* Mangle one byte of the MAC. */ uint8_t *handshake_ptr = - hs_cell_establish_intro_getarray_handshake_mac(establish_intro_cell); + trn_cell_establish_intro_getarray_handshake_mac(establish_intro_cell); handshake_ptr[TRUNNEL_SHA3_256_LEN - 1]++; /* We need to resign the payload with that change. */ { @@ -269,7 +269,7 @@ test_establish_intro_wrong_mac(void *arg) retval = ed25519_keypair_generate(&key_struct, 0); tt_int_op(retval, OP_EQ, 0); uint8_t *auth_key_ptr = - hs_cell_establish_intro_getarray_auth_key(establish_intro_cell); + trn_cell_establish_intro_getarray_auth_key(establish_intro_cell); memcpy(auth_key_ptr, key_struct.pubkey.pubkey, ED25519_PUBKEY_LEN); /* Encode payload so we can sign it. */ cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), @@ -284,7 +284,7 @@ test_establish_intro_wrong_mac(void *arg) tt_int_op(retval, OP_EQ, 0); /* And write the signature to the cell */ uint8_t *sig_ptr = - hs_cell_establish_intro_getarray_sig(establish_intro_cell); + trn_cell_establish_intro_getarray_sig(establish_intro_cell); memcpy(sig_ptr, sig.sig, establish_intro_cell->sig_len); /* Re-encode with the new signature. */ cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), @@ -299,7 +299,7 @@ test_establish_intro_wrong_mac(void *arg) tt_int_op(retval, ==, -1); done: - hs_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -309,7 +309,7 @@ static void test_establish_intro_wrong_auth_key_len(void *arg) { int retval; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; or_circuit_t *intro_circ = or_circuit_new(0,NULL);; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; @@ -328,9 +328,9 @@ test_establish_intro_wrong_auth_key_len(void *arg) sizeof(circuit_key_material)); tt_assert(establish_intro_cell); /* Mangle the auth key length. */ - hs_cell_establish_intro_set_auth_key_len(establish_intro_cell, + trn_cell_establish_intro_set_auth_key_len(establish_intro_cell, bad_auth_key_len); - hs_cell_establish_intro_setlen_auth_key(establish_intro_cell, + trn_cell_establish_intro_setlen_auth_key(establish_intro_cell, bad_auth_key_len); cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), establish_intro_cell); @@ -344,7 +344,7 @@ test_establish_intro_wrong_auth_key_len(void *arg) tt_int_op(retval, ==, -1); done: - hs_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -354,7 +354,7 @@ static void test_establish_intro_wrong_sig_len(void *arg) { int retval; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; or_circuit_t *intro_circ = or_circuit_new(0,NULL);; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; @@ -373,8 +373,8 @@ test_establish_intro_wrong_sig_len(void *arg) sizeof(circuit_key_material)); tt_assert(establish_intro_cell); /* Mangle the signature length. */ - hs_cell_establish_intro_set_sig_len(establish_intro_cell, bad_sig_len); - hs_cell_establish_intro_setlen_sig(establish_intro_cell, bad_sig_len); + trn_cell_establish_intro_set_sig_len(establish_intro_cell, bad_sig_len); + trn_cell_establish_intro_setlen_sig(establish_intro_cell, bad_sig_len); cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), establish_intro_cell); tt_int_op(cell_len, >, 0); @@ -387,7 +387,7 @@ test_establish_intro_wrong_sig_len(void *arg) tt_int_op(retval, ==, -1); done: - hs_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -397,7 +397,7 @@ static void test_establish_intro_wrong_sig(void *arg) { int retval; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; or_circuit_t *intro_circ = or_circuit_new(0,NULL);; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; @@ -429,17 +429,17 @@ test_establish_intro_wrong_sig(void *arg) tt_int_op(retval, ==, -1); done: - hs_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } /* Helper function: Send a well-formed v3 ESTABLISH_INTRO cell to * <b>intro_circ</b>. Return the cell. */ -static hs_cell_establish_intro_t * +static trn_cell_establish_intro_t * helper_establish_intro_v3(or_circuit_t *intro_circ) { int retval; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; uint8_t circuit_key_material[DIGEST_LEN] = {0}; @@ -503,6 +503,24 @@ helper_establish_intro_v2(or_circuit_t *intro_circ) return key1; } +/* Helper function: test circuitmap free_all function outside of + * test_intro_point_registration to prevent Coverity from seeing a + * double free if the assertion hypothetically fails. + */ +static void +test_circuitmap_free_all(void) +{ + hs_circuitmap_ht *the_hs_circuitmap = NULL; + + the_hs_circuitmap = get_hs_circuitmap(); + tt_assert(the_hs_circuitmap); + hs_circuitmap_free_all(); + the_hs_circuitmap = get_hs_circuitmap(); + tt_assert(!the_hs_circuitmap); + done: + ; +} + /** Successfuly register a v2 intro point and a v3 intro point. Ensure that HS * circuitmap is maintained properly. */ static void @@ -512,7 +530,7 @@ test_intro_point_registration(void *arg) hs_circuitmap_ht *the_hs_circuitmap = NULL; or_circuit_t *intro_circ = NULL; - hs_cell_establish_intro_t *establish_intro_cell = NULL; + trn_cell_establish_intro_t *establish_intro_cell = NULL; ed25519_public_key_t auth_key; crypto_pk_t *legacy_auth_key = NULL; @@ -532,7 +550,7 @@ test_intro_point_registration(void *arg) tt_assert(the_hs_circuitmap); tt_int_op(0, ==, HT_SIZE(the_hs_circuitmap)); /* Do a circuitmap query in any case */ - returned_intro_circ = hs_circuitmap_get_intro_circ_v3(&auth_key); + returned_intro_circ =hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key); tt_ptr_op(returned_intro_circ, ==, NULL); } @@ -548,7 +566,8 @@ test_intro_point_registration(void *arg) tt_int_op(1, ==, HT_SIZE(the_hs_circuitmap)); get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, establish_intro_cell); - returned_intro_circ = hs_circuitmap_get_intro_circ_v3(&auth_key); + returned_intro_circ = + hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key); tt_ptr_op(intro_circ, ==, returned_intro_circ); } @@ -569,7 +588,8 @@ test_intro_point_registration(void *arg) /* Check that the new element is our legacy intro circuit. */ retval = crypto_pk_get_digest(legacy_auth_key, key_digest); tt_int_op(retval, ==, 0); - returned_intro_circ= hs_circuitmap_get_intro_circ_v2((uint8_t*)key_digest); + returned_intro_circ = + hs_circuitmap_get_intro_circ_v2_relay_side((uint8_t*)key_digest); tt_ptr_op(legacy_intro_circ, ==, returned_intro_circ); } @@ -580,15 +600,8 @@ test_intro_point_registration(void *arg) crypto_pk_free(legacy_auth_key); circuit_free(TO_CIRCUIT(intro_circ)); circuit_free(TO_CIRCUIT(legacy_intro_circ)); - hs_cell_establish_intro_free(establish_intro_cell); - - { /* Test circuitmap free_all function. */ - the_hs_circuitmap = get_hs_circuitmap(); - tt_assert(the_hs_circuitmap); - hs_circuitmap_free_all(); - the_hs_circuitmap = get_hs_circuitmap(); - tt_assert(!the_hs_circuitmap); - } + trn_cell_establish_intro_free(establish_intro_cell); + test_circuitmap_free_all(); UNMOCK(hs_intro_send_intro_established_cell); } @@ -674,7 +687,7 @@ static void test_introduce1_validation(void *arg) { int ret; - hs_cell_introduce1_t *cell = NULL; + trn_cell_introduce1_t *cell = NULL; (void) arg; @@ -714,25 +727,25 @@ test_introduce1_validation(void *arg) ret = validate_introduce1_parsed_cell(cell); tt_int_op(ret, OP_EQ, 0); /* Set an invalid size of the auth key buffer. */ - hs_cell_introduce1_setlen_auth_key(cell, 3); + trn_cell_introduce1_setlen_auth_key(cell, 3); ret = validate_introduce1_parsed_cell(cell); tt_int_op(ret, OP_EQ, -1); /* Reset auth key buffer and make sure it works. */ - hs_cell_introduce1_setlen_auth_key(cell, sizeof(ed25519_public_key_t)); + trn_cell_introduce1_setlen_auth_key(cell, sizeof(ed25519_public_key_t)); ret = validate_introduce1_parsed_cell(cell); tt_int_op(ret, OP_EQ, 0); /* Empty encrypted section. */ - hs_cell_introduce1_setlen_encrypted(cell, 0); + trn_cell_introduce1_setlen_encrypted(cell, 0); ret = validate_introduce1_parsed_cell(cell); tt_int_op(ret, OP_EQ, -1); /* Reset it to some non zero bytes and validate. */ - hs_cell_introduce1_setlen_encrypted(cell, 1); + trn_cell_introduce1_setlen_encrypted(cell, 1); ret = validate_introduce1_parsed_cell(cell); tt_int_op(ret, OP_EQ, 0); done: - hs_cell_introduce1_free(cell); + trn_cell_introduce1_free(cell); } static void @@ -740,7 +753,7 @@ test_received_introduce1_handling(void *arg) { int ret; uint8_t *request = NULL, buf[128]; - hs_cell_introduce1_t *cell = NULL; + trn_cell_introduce1_t *cell = NULL; or_circuit_t *circ = NULL; (void) arg; @@ -774,12 +787,12 @@ test_received_introduce1_handling(void *arg) /* Valid case. */ { cell = helper_create_introduce1_cell(); - ssize_t request_len = hs_cell_introduce1_encoded_len(cell); - tt_size_op(request_len, OP_GT, 0); + ssize_t request_len = trn_cell_introduce1_encoded_len(cell); + tt_int_op((int)request_len, OP_GT, 0); request = tor_malloc_zero(request_len); ssize_t encoded_len = - hs_cell_introduce1_encode(request, request_len, cell); - tt_size_op(encoded_len, OP_GT, 0); + trn_cell_introduce1_encode(request, request_len, cell); + tt_int_op((int)encoded_len, OP_GT, 0); circ = helper_create_intro_circuit(); or_circuit_t *service_circ = helper_create_intro_circuit(); @@ -788,9 +801,9 @@ test_received_introduce1_handling(void *arg) /* Register the circuit in the map for the auth key of the cell. */ ed25519_public_key_t auth_key; const uint8_t *cell_auth_key = - hs_cell_introduce1_getconstarray_auth_key(cell); + trn_cell_introduce1_getconstarray_auth_key(cell); memcpy(auth_key.pubkey, cell_auth_key, ED25519_PUBKEY_LEN); - hs_circuitmap_register_intro_circ_v3(service_circ, &auth_key); + hs_circuitmap_register_intro_circ_v3_relay_side(service_circ, &auth_key); ret = hs_intro_received_introduce1(circ, request, request_len); circuit_free(TO_CIRCUIT(circ)); circuit_free(TO_CIRCUIT(service_circ)); @@ -800,17 +813,17 @@ test_received_introduce1_handling(void *arg) /* Valid legacy cell. */ { tor_free(request); - hs_cell_introduce1_free(cell); + trn_cell_introduce1_free(cell); cell = helper_create_introduce1_cell(); - uint8_t *legacy_key_id = hs_cell_introduce1_getarray_legacy_key_id(cell); + uint8_t *legacy_key_id = trn_cell_introduce1_getarray_legacy_key_id(cell); memset(legacy_key_id, 'a', DIGEST_LEN); /* Add an arbitrary amount of data for the payload of a v2 cell. */ - size_t request_len = hs_cell_introduce1_encoded_len(cell) + 256; + size_t request_len = trn_cell_introduce1_encoded_len(cell) + 256; tt_size_op(request_len, OP_GT, 0); request = tor_malloc_zero(request_len + 256); ssize_t encoded_len = - hs_cell_introduce1_encode(request, request_len, cell); - tt_size_op(encoded_len, OP_GT, 0); + trn_cell_introduce1_encode(request, request_len, cell); + tt_int_op((int)encoded_len, OP_GT, 0); circ = helper_create_intro_circuit(); or_circuit_t *service_circ = helper_create_intro_circuit(); @@ -819,7 +832,7 @@ test_received_introduce1_handling(void *arg) /* Register the circuit in the map for the auth key of the cell. */ uint8_t token[REND_TOKEN_LEN]; memcpy(token, legacy_key_id, sizeof(token)); - hs_circuitmap_register_intro_circ_v2(service_circ, token); + hs_circuitmap_register_intro_circ_v2_relay_side(service_circ, token); ret = hs_intro_received_introduce1(circ, request, request_len); circuit_free(TO_CIRCUIT(circ)); circuit_free(TO_CIRCUIT(service_circ)); @@ -827,7 +840,7 @@ test_received_introduce1_handling(void *arg) } done: - hs_cell_introduce1_free(cell); + trn_cell_introduce1_free(cell); tor_free(request); hs_circuitmap_free_all(); UNMOCK(relay_send_command_from_edge_); diff --git a/src/test/test_hs_ntor.sh b/src/test/test_hs_ntor.sh new file mode 100755 index 0000000000..8a0003d44a --- /dev/null +++ b/src/test/test_hs_ntor.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Validate Tor's ntor implementation. + +exitcode=0 + +# Run the python integration test sand return the exitcode of the python +# script. The python script might ask the testsuite to skip it if not all +# python dependencies are covered. +"${PYTHON:-python}" "${abs_top_srcdir:-.}/src/test/hs_ntor_ref.py" || exitcode=$? + +exit ${exitcode} diff --git a/src/test/test_hs_ntor_cl.c b/src/test/test_hs_ntor_cl.c new file mode 100644 index 0000000000..ed1eda58ea --- /dev/null +++ b/src/test/test_hs_ntor_cl.c @@ -0,0 +1,255 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** This is a wrapper over the little-t-tor HS ntor functions. The wrapper is + * used by src/test/hs_ntor_ref.py to conduct the HS ntor integration + * tests. + * + * The logic of this wrapper is basically copied from src/test/test_ntor_cl.c + */ + +#include "orconfig.h" +#include <stdio.h> +#include <stdlib.h> + +#define ONION_NTOR_PRIVATE +#include "or.h" +#include "util.h" +#include "compat.h" +#include "crypto.h" +#include "crypto_curve25519.h" +#include "hs_ntor.h" +#include "onion_ntor.h" + +#define N_ARGS(n) STMT_BEGIN { \ + if (argc < (n)) { \ + fprintf(stderr, "%s needs %d arguments.\n",argv[1],n); \ + return 1; \ + } \ + } STMT_END +#define BASE16(idx, var, n) STMT_BEGIN { \ + const char *s = argv[(idx)]; \ + if (base16_decode((char*)var, n, s, strlen(s)) < (int)n ) { \ + fprintf(stderr, "couldn't decode argument %d (%s)\n",idx,s); \ + return 1; \ + } \ + } STMT_END +#define INT(idx, var) STMT_BEGIN { \ + var = atoi(argv[(idx)]); \ + if (var <= 0) { \ + fprintf(stderr, "bad integer argument %d (%s)\n",idx,argv[(idx)]); \ + } \ + } STMT_END + +/** The first part of the HS ntor protocol. The client-side computes all + necessary key material and sends the appropriate message to the service. */ +static int +client1(int argc, char **argv) +{ + int retval; + + /* Inputs */ + curve25519_public_key_t intro_enc_pubkey; + ed25519_public_key_t intro_auth_pubkey; + curve25519_keypair_t client_ephemeral_enc_keypair; + uint8_t subcredential[DIGEST256_LEN]; + + /* Output */ + hs_ntor_intro_cell_keys_t hs_ntor_intro_cell_keys; + + char buf[256]; + + N_ARGS(6); + BASE16(2, intro_auth_pubkey.pubkey, ED25519_PUBKEY_LEN); + BASE16(3, intro_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN); + BASE16(4, client_ephemeral_enc_keypair.seckey.secret_key, + CURVE25519_SECKEY_LEN); + BASE16(5, subcredential, DIGEST256_LEN); + + /* Generate keypair */ + curve25519_public_key_generate(&client_ephemeral_enc_keypair.pubkey, + &client_ephemeral_enc_keypair.seckey); + + retval = hs_ntor_client_get_introduce1_keys(&intro_auth_pubkey, + &intro_enc_pubkey, + &client_ephemeral_enc_keypair, + subcredential, + &hs_ntor_intro_cell_keys); + if (retval < 0) { + goto done; + } + + /* Send ENC_KEY */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_intro_cell_keys.enc_key, + sizeof(hs_ntor_intro_cell_keys.enc_key)); + printf("%s\n", buf); + /* Send MAC_KEY */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_intro_cell_keys.mac_key, + sizeof(hs_ntor_intro_cell_keys.mac_key)); + printf("%s\n", buf); + + done: + return retval; +} + +/** The second part of the HS ntor protocol. The service-side computes all + necessary key material and sends the appropriate message to the client */ +static int +server1(int argc, char **argv) +{ + int retval; + + /* Inputs */ + curve25519_keypair_t intro_enc_keypair; + ed25519_public_key_t intro_auth_pubkey; + curve25519_public_key_t client_ephemeral_enc_pubkey; + uint8_t subcredential[DIGEST256_LEN]; + + /* Output */ + hs_ntor_intro_cell_keys_t hs_ntor_intro_cell_keys; + hs_ntor_rend_cell_keys_t hs_ntor_rend_cell_keys; + curve25519_keypair_t service_ephemeral_rend_keypair; + + char buf[256]; + + N_ARGS(6); + BASE16(2, intro_auth_pubkey.pubkey, ED25519_PUBKEY_LEN); + BASE16(3, intro_enc_keypair.seckey.secret_key, CURVE25519_SECKEY_LEN); + BASE16(4, client_ephemeral_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN); + BASE16(5, subcredential, DIGEST256_LEN); + + /* Generate keypair */ + curve25519_public_key_generate(&intro_enc_keypair.pubkey, + &intro_enc_keypair.seckey); + curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0); + + /* Get INTRODUCE1 keys */ + retval = hs_ntor_service_get_introduce1_keys(&intro_auth_pubkey, + &intro_enc_keypair, + &client_ephemeral_enc_pubkey, + subcredential, + &hs_ntor_intro_cell_keys); + if (retval < 0) { + goto done; + } + + /* Get RENDEZVOUS1 keys */ + retval = hs_ntor_service_get_rendezvous1_keys(&intro_auth_pubkey, + &intro_enc_keypair, + &service_ephemeral_rend_keypair, + &client_ephemeral_enc_pubkey, + &hs_ntor_rend_cell_keys); + if (retval < 0) { + goto done; + } + + /* Send ENC_KEY */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_intro_cell_keys.enc_key, + sizeof(hs_ntor_intro_cell_keys.enc_key)); + printf("%s\n", buf); + /* Send MAC_KEY */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_intro_cell_keys.mac_key, + sizeof(hs_ntor_intro_cell_keys.mac_key)); + printf("%s\n", buf); + /* Send AUTH_MAC */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_rend_cell_keys.rend_cell_auth_mac, + sizeof(hs_ntor_rend_cell_keys.rend_cell_auth_mac)); + printf("%s\n", buf); + /* Send NTOR_KEY_SEED */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_rend_cell_keys.ntor_key_seed, + sizeof(hs_ntor_rend_cell_keys.ntor_key_seed)); + printf("%s\n", buf); + /* Send service ephemeral pubkey (Y) */ + base16_encode(buf, sizeof(buf), + (const char*)service_ephemeral_rend_keypair.pubkey.public_key, + sizeof(service_ephemeral_rend_keypair.pubkey.public_key)); + printf("%s\n", buf); + + done: + return retval; +} + +/** The final step of the ntor protocol, the client computes and returns the + * rendezvous key material. */ +static int +client2(int argc, char **argv) +{ + int retval; + + /* Inputs */ + curve25519_public_key_t intro_enc_pubkey; + ed25519_public_key_t intro_auth_pubkey; + curve25519_keypair_t client_ephemeral_enc_keypair; + curve25519_public_key_t service_ephemeral_rend_pubkey; + uint8_t subcredential[DIGEST256_LEN]; + + /* Output */ + hs_ntor_rend_cell_keys_t hs_ntor_rend_cell_keys; + + char buf[256]; + + N_ARGS(7); + BASE16(2, intro_auth_pubkey.pubkey, ED25519_PUBKEY_LEN); + BASE16(3, client_ephemeral_enc_keypair.seckey.secret_key, + CURVE25519_SECKEY_LEN); + BASE16(4, intro_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN); + BASE16(5, service_ephemeral_rend_pubkey.public_key, CURVE25519_PUBKEY_LEN); + BASE16(6, subcredential, DIGEST256_LEN); + + /* Generate keypair */ + curve25519_public_key_generate(&client_ephemeral_enc_keypair.pubkey, + &client_ephemeral_enc_keypair.seckey); + + /* Get RENDEZVOUS1 keys */ + retval = hs_ntor_client_get_rendezvous1_keys(&intro_auth_pubkey, + &client_ephemeral_enc_keypair, + &intro_enc_pubkey, + &service_ephemeral_rend_pubkey, + &hs_ntor_rend_cell_keys); + if (retval < 0) { + goto done; + } + + /* Send AUTH_MAC */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_rend_cell_keys.rend_cell_auth_mac, + sizeof(hs_ntor_rend_cell_keys.rend_cell_auth_mac)); + printf("%s\n", buf); + /* Send NTOR_KEY_SEED */ + base16_encode(buf, sizeof(buf), + (const char*)hs_ntor_rend_cell_keys.ntor_key_seed, + sizeof(hs_ntor_rend_cell_keys.ntor_key_seed)); + printf("%s\n", buf); + + done: + return 1; +} + +/** Perform a different part of the protocol depdning on the argv used. */ +int +main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "I need arguments. Read source for more info.\n"); + return 1; + } + + curve25519_init(); + if (!strcmp(argv[1], "client1")) { + return client1(argc, argv); + } else if (!strcmp(argv[1], "server1")) { + return server1(argc, argv); + } else if (!strcmp(argv[1], "client2")) { + return client2(argc, argv); + } else { + fprintf(stderr, "What's a %s?\n", argv[1]); + return 1; + } +} + diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 039d727cea..fcfb3b992d 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -6,6 +6,7 @@ * \brief Test hidden service functionality. */ +#define HS_COMMON_PRIVATE #define HS_SERVICE_PRIVATE #define HS_INTROPOINT_PRIVATE @@ -14,9 +15,12 @@ #include "crypto.h" #include "hs/cell_establish_intro.h" +#include "hs_common.h" #include "hs_service.h" #include "hs_intropoint.h" +#include "hs_ntor.h" + /** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we * parse it from the receiver side. */ static void @@ -26,8 +30,8 @@ test_gen_establish_intro_cell(void *arg) ssize_t retval; uint8_t circuit_key_material[DIGEST_LEN] = {0}; uint8_t buf[RELAY_PAYLOAD_SIZE]; - hs_cell_establish_intro_t *cell_out = NULL; - hs_cell_establish_intro_t *cell_in = NULL; + trn_cell_establish_intro_t *cell_out = NULL; + trn_cell_establish_intro_t *cell_in = NULL; crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); @@ -44,7 +48,7 @@ test_gen_establish_intro_cell(void *arg) /* Parse it as the receiver */ { - ssize_t parse_result = hs_cell_establish_intro_parse(&cell_in, + ssize_t parse_result = trn_cell_establish_intro_parse(&cell_in, buf, sizeof(buf)); tt_int_op(parse_result, >=, 0); @@ -55,8 +59,8 @@ test_gen_establish_intro_cell(void *arg) } done: - hs_cell_establish_intro_free(cell_out); - hs_cell_establish_intro_free(cell_in); + trn_cell_establish_intro_free(cell_out); + trn_cell_establish_intro_free(cell_in); } /* Mocked ed25519_sign_prefixed() function that always fails :) */ @@ -78,7 +82,7 @@ static void test_gen_establish_intro_cell_bad(void *arg) { (void) arg; - hs_cell_establish_intro_t *cell = NULL; + trn_cell_establish_intro_t *cell = NULL; uint8_t circuit_key_material[DIGEST_LEN] = {0}; MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed); @@ -96,15 +100,150 @@ test_gen_establish_intro_cell_bad(void *arg) tt_assert(!cell); done: - hs_cell_establish_intro_free(cell); + trn_cell_establish_intro_free(cell); UNMOCK(ed25519_sign_prefixed); } +/** Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1 + * cell, and verify the proper derivation of decryption keys on the other end. + * Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify + * the proper verification on the other end. */ +static void +test_hs_ntor(void *arg) +{ + int retval; + + uint8_t subcredential[DIGEST256_LEN]; + + ed25519_keypair_t service_intro_auth_keypair; + curve25519_keypair_t service_intro_enc_keypair; + curve25519_keypair_t service_ephemeral_rend_keypair; + + curve25519_keypair_t client_ephemeral_enc_keypair; + + hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys; + hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys; + + hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys; + hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys; + + (void) arg; + + /* Generate fake data for this unittest */ + { + /* Generate fake subcredential */ + memset(subcredential, 'Z', DIGEST256_LEN); + + /* service */ + curve25519_keypair_generate(&service_intro_enc_keypair, 0); + ed25519_keypair_generate(&service_intro_auth_keypair, 0); + curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0); + /* client */ + curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0); + } + + /* Client: Simulate the sending of an encrypted INTRODUCE1 cell */ + retval = + hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair.pubkey, + &client_ephemeral_enc_keypair, + subcredential, + &client_hs_ntor_intro_cell_keys); + tt_int_op(retval, ==, 0); + + /* Service: Simulate the decryption of the received INTRODUCE1 */ + retval = + hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair, + &client_ephemeral_enc_keypair.pubkey, + subcredential, + &service_hs_ntor_intro_cell_keys); + tt_int_op(retval, ==, 0); + + /* Test that the INTRODUCE1 encryption/mac keys match! */ + tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ, + service_hs_ntor_intro_cell_keys.enc_key, + CIPHER256_KEY_LEN); + tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ, + service_hs_ntor_intro_cell_keys.mac_key, + DIGEST256_LEN); + + /* Service: Simulate creation of RENDEZVOUS1 key material. */ + retval = + hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair, + &service_ephemeral_rend_keypair, + &client_ephemeral_enc_keypair.pubkey, + &service_hs_ntor_rend_cell_keys); + tt_int_op(retval, ==, 0); + + /* Client: Simulate the verification of a received RENDEZVOUS1 cell */ + retval = + hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, + &client_ephemeral_enc_keypair, + &service_intro_enc_keypair.pubkey, + &service_ephemeral_rend_keypair.pubkey, + &client_hs_ntor_rend_cell_keys); + tt_int_op(retval, ==, 0); + + /* Test that the RENDEZVOUS1 key material match! */ + tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ, + service_hs_ntor_rend_cell_keys.rend_cell_auth_mac, + DIGEST256_LEN); + tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ, + service_hs_ntor_rend_cell_keys.ntor_key_seed, + DIGEST256_LEN); + + done: + ; +} + +/** Test that our HS time period calculation functions work properly */ +static void +test_time_period(void *arg) +{ + (void) arg; + uint64_t tn; + int retval; + time_t fake_time; + + /* Let's do the example in prop224 section [TIME-PERIODS] */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", + &fake_time); + tt_int_op(retval, ==, 0); + + /* Check that the time period number is right */ + tn = get_time_period_num(fake_time); + tt_u64_op(tn, ==, 16903); + + /* Increase current time to 11:59:59 UTC and check that the time period + number is still the same */ + fake_time += 3599; + tn = get_time_period_num(fake_time); + tt_u64_op(tn, ==, 16903); + + /* Now take time to 12:00:00 UTC and check that the time period rotated */ + fake_time += 1; + tn = get_time_period_num(fake_time); + tt_u64_op(tn, ==, 16904); + + /* Now also check our hs_get_next_time_period_num() function */ + tn = hs_get_next_time_period_num(fake_time); + tt_u64_op(tn, ==, 16905); + + done: + ; +} + struct testcase_t hs_service_tests[] = { { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, NULL, NULL }, { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK, NULL, NULL }, + { "hs_ntor", test_hs_ntor, TT_FORK, + NULL, NULL }, + { "time_period", test_time_period, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_introduce.c b/src/test/test_introduce.c index 810b03c93d..cfb8d83b1d 100644 --- a/src/test/test_introduce.c +++ b/src/test/test_introduce.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_keypin.c b/src/test/test_keypin.c index 95657349c6..d2ec8e9ca7 100644 --- a/src/test/test_keypin.c +++ b/src/test/test_keypin.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_link_handshake.c b/src/test/test_link_handshake.c index 467bba3a64..c5508b0f04 100644 --- a/src/test/test_link_handshake.c +++ b/src/test/test_link_handshake.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -10,13 +10,6 @@ #include "compat.h" -/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in - * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */ -DISABLE_GCC_WARNING(redundant-decls) -#include <openssl/x509.h> -#include <openssl/ssl.h> -ENABLE_GCC_WARNING(redundant-decls) - #include "or.h" #include "config.h" #include "connection.h" @@ -808,19 +801,14 @@ CERTS_FAIL(expired_rsa_id, /* both */ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 1); const tor_x509_cert_t *idc; tor_tls_get_my_certs(1, NULL, &idc); - X509 *newc = X509_dup(idc->cert); + tor_x509_cert_t *newc; time_t new_end = time(NULL) - 86400 * 10; - X509_time_adj(X509_get_notAfter(newc), 0, &new_end); - EVP_PKEY *pk = crypto_pk_get_evp_pkey_(d->key2, 1); - tt_assert(X509_sign(newc, pk, EVP_sha1())); - int len = i2d_X509(newc, NULL); - certs_cell_cert_setlen_body(cert, len); - uint8_t *body = certs_cell_cert_getarray_body(cert); - int len2 = i2d_X509(newc, &body); - tt_int_op(len, ==, len2); + newc = tor_x509_cert_replace_expiration(idc, new_end, d->key2); + certs_cell_cert_setlen_body(cert, newc->encoded_len); + memcpy(certs_cell_cert_getarray_body(cert), + newc->encoded, newc->encoded_len); REENCODE(); - X509_free(newc); - EVP_PKEY_free(pk); + tor_x509_cert_free(newc); }) CERTS_FAIL(expired_ed_id, /* ed25519 */ { diff --git a/src/test/test_logging.c b/src/test/test_logging.c index 15471e46d0..94b3e4ea68 100644 --- a/src/test/test_logging.c +++ b/src/test/test_logging.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_microdesc.c b/src/test/test_microdesc.c index 2ae605b8db..c78fda3b69 100644 --- a/src/test/test_microdesc.c +++ b/src/test/test_microdesc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -14,12 +14,6 @@ #include "test.h" -DISABLE_GCC_WARNING(redundant-decls) -#include <openssl/rsa.h> -#include <openssl/bn.h> -#include <openssl/pem.h> -ENABLE_GCC_WARNING(redundant-decls) - #ifdef _WIN32 /* For mkdir() */ #include <direct.h> diff --git a/src/test/test_nodelist.c b/src/test/test_nodelist.c index d58f8a7fca..256354415c 100644 --- a/src/test/test_nodelist.c +++ b/src/test/test_nodelist.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/test/test_ntor_cl.c b/src/test/test_ntor_cl.c index a560e5fc5e..d0eea85d6f 100644 --- a/src/test/test_ntor_cl.c +++ b/src/test/test_ntor_cl.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_oom.c b/src/test/test_oom.c index 0f97972032..f03a504d1d 100644 --- a/src/test/test_oom.c +++ b/src/test/test_oom.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /* Unit tests for OOM handling logic */ diff --git a/src/test/test_oos.c b/src/test/test_oos.c index db06625116..9fd6bce5ae 100644 --- a/src/test/test_oos.c +++ b/src/test/test_oos.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /* Unit tests for OOS handler */ diff --git a/src/test/test_options.c b/src/test/test_options.c index d5782e9ec0..ad735b72a6 100644 --- a/src/test/test_options.c +++ b/src/test/test_options.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CONFIG_PRIVATE @@ -105,11 +105,71 @@ clear_log_messages(void) "EDE6D711294FADF8E7951F4DE6CA56B58 194.109.206.212:80 7EA6 EAD6 FD83" \ " 083C 538F 4403 8BBF A077 587D D755\n" +static int +test_options_checklog(const char *configuration, int expect_log_severity, + const char *expect_log) +{ + int found = 0, ret = -1; + char *actual_log = NULL; + + if (messages) { + SMARTLIST_FOREACH_BEGIN(messages, logmsg_t *, m) { + if (m->severity == expect_log_severity && + strstr(m->msg, expect_log)) { + found = 1; + break; + } + } SMARTLIST_FOREACH_END(m); + } + if (!found) { + actual_log = dump_logs(); + TT_DIE(("Expected log message [%s] %s from <%s>, but got <%s>.", + log_level_to_string(expect_log_severity), expect_log, + configuration, actual_log)); + } + ret = 0; + + done: + tor_free(actual_log); + return ret; +} + +static int +test_options_checkmsgs(const char *configuration, + const char *expect_errmsg, + int expect_log_severity, + const char *expect_log, + char *msg) +{ + if (expect_errmsg && !msg) { + TT_DIE(("Expected error message <%s> from <%s>, but got none.", + expect_errmsg, configuration)); + } else if (expect_errmsg && !strstr(msg, expect_errmsg)) { + TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.", + expect_errmsg, configuration, msg)); + } else if (!expect_errmsg && msg) { + TT_DIE(("Expected no error message from <%s> but got <%s>.", + configuration, msg)); + } + if (expect_log) { + return test_options_checklog(configuration, expect_log_severity, + expect_log); + } + return 0; + + done: + return -1; +} + +/* Which phases of config parsing/validation to check for messages/logs */ +enum { PH_GETLINES, PH_ASSIGN, PH_VALIDATE }; + static void test_options_validate_impl(const char *configuration, const char *expect_errmsg, int expect_log_severity, - const char *expect_log) + const char *expect_log, + int phase) { or_options_t *opt=NULL; or_options_t *dflt; @@ -120,43 +180,34 @@ test_options_validate_impl(const char *configuration, setup_options(opt, dflt); r = config_get_lines(configuration, &cl, 1); - tt_int_op(r, OP_EQ, 0); + if (phase == PH_GETLINES) { + if (test_options_checkmsgs(configuration, expect_errmsg, + expect_log_severity, + expect_log, msg)) + goto done; + } + if (r) + goto done; r = config_assign(&options_format, opt, cl, 0, &msg); - tt_int_op(r, OP_EQ, 0); - - r = options_validate(NULL, opt, dflt, 0, &msg); - if (expect_errmsg && !msg) { - TT_DIE(("Expected error message <%s> from <%s>, but got none.", - expect_errmsg, configuration)); - } else if (expect_errmsg && !strstr(msg, expect_errmsg)) { - TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.", - expect_errmsg, configuration, msg)); - } else if (!expect_errmsg && msg) { - TT_DIE(("Expected no error message from <%s> but got <%s>.", - configuration, msg)); + if (phase == PH_ASSIGN) { + if (test_options_checkmsgs(configuration, expect_errmsg, + expect_log_severity, + expect_log, msg)) + goto done; } tt_int_op((r == 0), OP_EQ, (msg == NULL)); + if (r) + goto done; - if (expect_log) { - int found = 0; - if (messages) { - SMARTLIST_FOREACH_BEGIN(messages, logmsg_t *, m) { - if (m->severity == expect_log_severity && - strstr(m->msg, expect_log)) { - found = 1; - break; - } - } SMARTLIST_FOREACH_END(m); - } - if (!found) { - tor_free(msg); - msg = dump_logs(); - TT_DIE(("Expected log message [%s] %s from <%s>, but got <%s>.", - log_level_to_string(expect_log_severity), expect_log, - configuration, msg)); - } + r = options_validate(NULL, opt, dflt, 0, &msg); + if (phase == PH_VALIDATE) { + if (test_options_checkmsgs(configuration, expect_errmsg, + expect_log_severity, + expect_log, msg)) + goto done; } + tt_int_op((r == 0), OP_EQ, (msg == NULL)); done: escaped(NULL); @@ -168,14 +219,14 @@ test_options_validate_impl(const char *configuration, clear_log_messages(); } -#define WANT_ERR(config, msg) \ - test_options_validate_impl((config), (msg), 0, NULL) -#define WANT_LOG(config, severity, msg) \ - test_options_validate_impl((config), NULL, (severity), (msg)) -#define WANT_ERR_LOG(config, msg, severity, logmsg) \ - test_options_validate_impl((config), (msg), (severity), (logmsg)) -#define OK(config) \ - test_options_validate_impl((config), NULL, 0, NULL) +#define WANT_ERR(config, msg, ph) \ + test_options_validate_impl((config), (msg), 0, NULL, (ph)) +#define WANT_LOG(config, severity, msg, ph) \ + test_options_validate_impl((config), NULL, (severity), (msg), (ph)) +#define WANT_ERR_LOG(config, msg, severity, logmsg, ph) \ + test_options_validate_impl((config), (msg), (severity), (logmsg), (ph)) +#define OK(config, ph) \ + test_options_validate_impl((config), NULL, 0, NULL, (ph)) static void test_options_validate(void *arg) @@ -184,21 +235,39 @@ test_options_validate(void *arg) setup_log_callback(); sandbox_disable_getaddrinfo_cache(); - WANT_ERR("ExtORPort 500000", "Invalid ExtORPort"); + WANT_ERR("ExtORPort 500000", "Invalid ExtORPort", PH_VALIDATE); WANT_ERR_LOG("ServerTransportOptions trebuchet", "ServerTransportOptions did not parse", - LOG_WARN, "Too few arguments"); - OK("ServerTransportOptions trebuchet sling=snappy"); - OK("ServerTransportOptions trebuchet sling="); + LOG_WARN, "Too few arguments", PH_VALIDATE); + OK("ServerTransportOptions trebuchet sling=snappy", PH_VALIDATE); + OK("ServerTransportOptions trebuchet sling=", PH_VALIDATE); WANT_ERR_LOG("ServerTransportOptions trebuchet slingsnappy", "ServerTransportOptions did not parse", - LOG_WARN, "\"slingsnappy\" is not a k=v"); + LOG_WARN, "\"slingsnappy\" is not a k=v", PH_VALIDATE); WANT_ERR("DirPort 8080\nDirCache 0", - "DirPort configured but DirCache disabled."); + "DirPort configured but DirCache disabled.", PH_VALIDATE); WANT_ERR("BridgeRelay 1\nDirCache 0", - "We're a bridge but DirCache is disabled."); + "We're a bridge but DirCache is disabled.", PH_VALIDATE); + + WANT_ERR_LOG("HeartbeatPeriod 21 snarks", + "Interval 'HeartbeatPeriod 21 snarks' is malformed or" + " out of bounds.", LOG_WARN, "Unknown unit 'snarks'.", + PH_ASSIGN); + WANT_ERR_LOG("LogTimeGranularity 21 snarks", + "Msec interval 'LogTimeGranularity 21 snarks' is malformed or" + " out of bounds.", LOG_WARN, "Unknown unit 'snarks'.", + PH_ASSIGN); + OK("HeartbeatPeriod 1 hour", PH_VALIDATE); + OK("LogTimeGranularity 100 milliseconds", PH_VALIDATE); + + WANT_LOG("ControlSocket \"string with trailing garbage\" bogus", LOG_WARN, + "Error while parsing configuration: " + "Excess data after quoted string", PH_GETLINES); + WANT_LOG("ControlSocket \"bogus escape \\@\"", LOG_WARN, + "Error while parsing configuration: " + "Invalid escape sequence in quoted string", PH_GETLINES); close_temp_logs(); clear_log_messages(); @@ -354,6 +423,12 @@ get_options_test_data(const char *conf) result->opt = options_new(); result->old_opt = options_new(); result->def_opt = options_new(); + + // XXX: Really, all of these options should be set to defaults + // with options_init(), but about a dozen tests break when I do that. + // Being kinda lame and just fixing the immedate breakage for now.. + result->opt->ConnectionPadding = -1; // default must be "auto" + rv = config_get_lines(conf, &cl, 1); tt_assert(rv == 0); rv = config_assign(&options_format, result->opt, cl, 0, &msg); @@ -402,7 +477,7 @@ test_options_validate__uname_for_server(void *ignored) (void)ignored; char *msg; options_test_data_t *tdata = get_options_test_data( - "ORListenAddress 127.0.0.1:5555"); + "ORPort 127.0.0.1:5555"); setup_capture_of_logs(LOG_WARN); MOCK(get_uname, fixed_get_uname); @@ -536,7 +611,7 @@ test_options_validate__contactinfo(void *ignored) int ret; char *msg; options_test_data_t *tdata = get_options_test_data( - "ORListenAddress 127.0.0.1:5555\nORPort 955"); + "ORPort 127.0.0.1:5555"); setup_capture_of_logs(LOG_DEBUG); tdata->opt->ContactInfo = NULL; @@ -549,7 +624,7 @@ test_options_validate__contactinfo(void *ignored) tor_free(msg); free_options_test_data(tdata); - tdata = get_options_test_data("ORListenAddress 127.0.0.1:5555\nORPort 955\n" + tdata = get_options_test_data("ORPort 127.0.0.1:5555\n" "ContactInfo hella@example.org"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); @@ -957,8 +1032,7 @@ test_options_validate__relay_with_hidden_services(void *ignored) char *msg; setup_capture_of_logs(LOG_DEBUG); options_test_data_t *tdata = get_options_test_data( - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "HiddenServiceDir " "/Library/Tor/var/lib/tor/hidden_service/\n" "HiddenServicePort 80 127.0.0.1:8080\n" @@ -1028,7 +1102,7 @@ test_options_validate__transproxy(void *ignored) #else tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_PF_DIVERT); tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without " - "any valid TransPort or TransListenAddress."); + "any valid TransPort."); #endif tor_free(msg); @@ -1043,7 +1117,7 @@ test_options_validate__transproxy(void *ignored) #else tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_TPROXY); tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without any valid " - "TransPort or TransListenAddress."); + "TransPort."); #endif tor_free(msg); @@ -1059,7 +1133,7 @@ test_options_validate__transproxy(void *ignored) #else tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_IPFW); tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without any valid " - "TransPort or TransListenAddress."); + "TransPort."); #endif tor_free(msg); @@ -1117,8 +1191,7 @@ test_options_validate__transproxy(void *ignored) ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - tt_str_op(msg, OP_EQ, "TransPort and TransListenAddress are disabled in " - "this build."); + tt_str_op(msg, OP_EQ, "TransPort is disabled in this build."); tor_free(msg); #endif @@ -1313,54 +1386,6 @@ test_options_validate__node_families(void *ignored) } static void -test_options_validate__tlsec(void *ignored) -{ - (void)ignored; - int ret; - char *msg; - setup_capture_of_logs(LOG_DEBUG); - options_test_data_t *tdata = get_options_test_data( - "TLSECGroup ed25519\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); - - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - expect_log_msg("Unrecognized TLSECGroup: Falling back to the default.\n"); - tt_assert(!tdata->opt->TLSECGroup); - tor_free(msg); - - free_options_test_data(tdata); - tdata = get_options_test_data("TLSECGroup P224\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); - mock_clean_saved_logs(); - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - expect_no_log_msg( - "Unrecognized TLSECGroup: Falling back to the default.\n"); - tt_assert(tdata->opt->TLSECGroup); - tor_free(msg); - - free_options_test_data(tdata); - tdata = get_options_test_data("TLSECGroup P256\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); - mock_clean_saved_logs(); - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - expect_no_log_msg( - "Unrecognized TLSECGroup: Falling back to the default.\n"); - tt_assert(tdata->opt->TLSECGroup); - tor_free(msg); - - done: - teardown_capture_of_logs(); - free_options_test_data(tdata); - tor_free(msg); -} - -static void test_options_validate__token_bucket(void *ignored) { (void)ignored; @@ -1742,8 +1767,7 @@ test_options_validate__reachable_addresses(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("ReachableAddresses *:82\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" "ConnLimit 1\n" "SchedulerHighWaterMark__ 42\n" @@ -1756,8 +1780,7 @@ test_options_validate__reachable_addresses(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("ReachableORAddresses *:82\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" "ConnLimit 1\n" "SchedulerHighWaterMark__ 42\n" @@ -1770,8 +1793,7 @@ test_options_validate__reachable_addresses(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("ReachableDirAddresses *:82\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" "ConnLimit 1\n" "SchedulerHighWaterMark__ 42\n" @@ -1784,8 +1806,7 @@ test_options_validate__reachable_addresses(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("ClientUseIPv4 0\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" "ConnLimit 1\n" "SchedulerHighWaterMark__ 42\n" @@ -1885,8 +1906,7 @@ test_options_validate__use_bridges(void *ignored) options_test_data_t *tdata = get_options_test_data( "UseBridges 1\n" "ClientUseIPv4 1\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" "ConnLimit 1\n" "SchedulerHighWaterMark__ 42\n" @@ -2008,56 +2028,6 @@ test_options_validate__entry_nodes(void *ignored) } static void -test_options_validate__invalid_nodes(void *ignored) -{ - (void)ignored; - int ret; - char *msg; - options_test_data_t *tdata = get_options_test_data( - "AllowInvalidNodes something_stupid\n" - "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); - - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - tt_str_op(msg, OP_EQ, - "Unrecognized value 'something_stupid' in AllowInvalidNodes"); - tor_free(msg); - - free_options_test_data(tdata); - tdata = get_options_test_data("AllowInvalidNodes entry, middle, exit\n" - "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); - - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - tt_int_op(tdata->opt->AllowInvalid_, OP_EQ, ALLOW_INVALID_ENTRY | - ALLOW_INVALID_EXIT | ALLOW_INVALID_MIDDLE); - tor_free(msg); - - free_options_test_data(tdata); - tdata = get_options_test_data("AllowInvalidNodes introduction, rendezvous\n" - "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); - - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - tt_int_op(tdata->opt->AllowInvalid_, OP_EQ, ALLOW_INVALID_INTRODUCTION | - ALLOW_INVALID_RENDEZVOUS); - tor_free(msg); - - done: - free_options_test_data(tdata); - tor_free(msg); -} - -static void test_options_validate__safe_logging(void *ignored) { (void)ignored; @@ -2327,30 +2297,6 @@ test_options_validate__hidserv(void *ignored) } static void -test_options_validate__predicted_ports(void *ignored) -{ - (void)ignored; - int ret; - char *msg; - setup_capture_of_logs(LOG_WARN); - - options_test_data_t *tdata = get_options_test_data( - "PredictedPortsRelevanceTime 100000000\n" - TEST_OPTIONS_DEFAULT_VALUES); - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, 0); - expect_log_msg("PredictedPortsRelevanceTime is too " - "large; clipping to 3600s.\n"); - tt_int_op(tdata->opt->PredictedPortsRelevanceTime, OP_EQ, 3600); - - done: - teardown_capture_of_logs(); - policies_free_all(); - free_options_test_data(tdata); - tor_free(msg); -} - -static void test_options_validate__path_bias(void *ignored) { (void)ignored; @@ -2496,8 +2442,7 @@ test_options_validate__bandwidth(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 1\n" ); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); @@ -2508,8 +2453,7 @@ test_options_validate__bandwidth(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 76800\n" "MaxAdvertisedBandwidth 30000\n" ); @@ -2521,8 +2465,7 @@ test_options_validate__bandwidth(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 76800\n" "RelayBandwidthRate 1\n" "MaxAdvertisedBandwidth 38400\n" @@ -2535,8 +2478,7 @@ test_options_validate__bandwidth(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 76800\n" "BandwidthBurst 76800\n" "RelayBandwidthRate 76800\n" @@ -2974,8 +2916,7 @@ test_options_validate__accounting(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data( TEST_OPTIONS_DEFAULT_VALUES - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 76800\n" "BandwidthBurst 76800\n" "MaxAdvertisedBandwidth 38400\n" @@ -3609,8 +3550,7 @@ test_options_validate__families(void *ignored) tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES "MyFamily home\n" "BridgeRelay 1\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 51300\n" "BandwidthBurst 51300\n" "MaxAdvertisedBandwidth 25700\n" @@ -3839,8 +3779,7 @@ test_options_validate__transport(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES "ServerTransportPlugin foo exec bar\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 76900\n" "BandwidthBurst 76900\n" "MaxAdvertisedBandwidth 38500\n" @@ -3882,8 +3821,7 @@ test_options_validate__transport(void *ignored) tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES "ServerTransportListenAddr foo 127.0.0.42:55\n" "ServerTransportPlugin foo exec bar\n" - "ORListenAddress 127.0.0.1:5555\n" - "ORPort 955\n" + "ORPort 127.0.0.1:5555\n" "BandwidthRate 76900\n" "BandwidthBurst 76900\n" "MaxAdvertisedBandwidth 38500\n" @@ -4240,48 +4178,6 @@ test_options_validate__virtual_addr(void *ignored) } static void -test_options_validate__exits(void *ignored) -{ - (void)ignored; - int ret; - char *msg; - options_test_data_t *tdata = NULL; - setup_capture_of_logs(LOG_WARN); - - free_options_test_data(tdata); - tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES - "AllowSingleHopExits 1" - ); - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, 0); - expect_log_msg("You have set AllowSingleHopExits; " - "now your relay will allow others to make one-hop exits. However," - " since by default most clients avoid relays that set this option," - " most clients will ignore you.\n"); - tor_free(msg); - - free_options_test_data(tdata); - tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES - "AllowSingleHopExits 1\n" - VALID_DIR_AUTH - ); - mock_clean_saved_logs(); - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, 0); - expect_no_log_msg("You have set AllowSingleHopExits; " - "now your relay will allow others to make one-hop exits. However," - " since by default most clients avoid relays that set this option," - " most clients will ignore you.\n"); - tor_free(msg); - - done: - policies_free_all(); - teardown_capture_of_logs(); - free_options_test_data(tdata); - tor_free(msg); -} - -static void test_options_validate__testing_options(void *ignored) { (void)ignored; @@ -4519,7 +4415,6 @@ struct testcase_t options_tests[] = { LOCAL_VALIDATE_TEST(exclude_nodes), LOCAL_VALIDATE_TEST(scheduler), LOCAL_VALIDATE_TEST(node_families), - LOCAL_VALIDATE_TEST(tlsec), LOCAL_VALIDATE_TEST(token_bucket), LOCAL_VALIDATE_TEST(recommended_packages), LOCAL_VALIDATE_TEST(fetch_dir), @@ -4530,12 +4425,10 @@ struct testcase_t options_tests[] = { LOCAL_VALIDATE_TEST(reachable_addresses), LOCAL_VALIDATE_TEST(use_bridges), LOCAL_VALIDATE_TEST(entry_nodes), - LOCAL_VALIDATE_TEST(invalid_nodes), LOCAL_VALIDATE_TEST(safe_logging), LOCAL_VALIDATE_TEST(publish_server_descriptor), LOCAL_VALIDATE_TEST(testing), LOCAL_VALIDATE_TEST(hidserv), - LOCAL_VALIDATE_TEST(predicted_ports), LOCAL_VALIDATE_TEST(path_bias), LOCAL_VALIDATE_TEST(bandwidth), LOCAL_VALIDATE_TEST(circuits), @@ -4553,7 +4446,6 @@ struct testcase_t options_tests[] = { LOCAL_VALIDATE_TEST(constrained_sockets), LOCAL_VALIDATE_TEST(v3_auth), LOCAL_VALIDATE_TEST(virtual_addr), - LOCAL_VALIDATE_TEST(exits), LOCAL_VALIDATE_TEST(testing_options), LOCAL_VALIDATE_TEST(accel), END_OF_TESTCASES /* */ diff --git a/src/test/test_policy.c b/src/test/test_policy.c index e86d0f0274..1b2fac4325 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/test/test_procmon.c b/src/test/test_procmon.c index 9e63fc006d..5c52af8693 100644 --- a/src/test/test_procmon.c +++ b/src/test/test_procmon.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define PROCMON_PRIVATE diff --git a/src/test/test_protover.c b/src/test/test_protover.c index f00955d1b4..5626816024 100644 --- a/src/test/test_protover.c +++ b/src/test/test_protover.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define PROTOVER_PRIVATE diff --git a/src/test/test_pt.c b/src/test/test_pt.c index f93019f1c4..79b03171bc 100644 --- a/src/test/test_pt.c +++ b/src/test/test_pt.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -284,13 +284,13 @@ test_pt_get_extrainfo_string(void *arg) } #ifdef _WIN32 -#define STDIN_HANDLE HANDLE +#define STDIN_HANDLE HANDLE* #else -#define STDIN_HANDLE FILE +#define STDIN_HANDLE int #endif static smartlist_t * -tor_get_lines_from_handle_replacement(STDIN_HANDLE *handle, +tor_get_lines_from_handle_replacement(STDIN_HANDLE handle, enum stream_status *stream_status_out) { static int times_called = 0; diff --git a/src/test/test_pubsub.c b/src/test/test_pubsub.c index 547d6c6b32..2f047d9f2c 100644 --- a/src/test/test_pubsub.c +++ b/src/test/test_pubsub.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/test/test_relay.c b/src/test/test_relay.c index 4713c79ea5..238d4c5baf 100644 --- a/src/test/test_relay.c +++ b/src/test/test_relay.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/test/test_relaycell.c b/src/test/test_relaycell.c index fb6748965a..eea1f5dc80 100644 --- a/src/test/test_relaycell.c +++ b/src/test/test_relaycell.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /* Unit tests for handling different kinds of relay cell */ diff --git a/src/test/test_rendcache.c b/src/test/test_rendcache.c index 0d53c78817..feba8f664e 100644 --- a/src/test/test_rendcache.c +++ b/src/test/test_rendcache.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -11,7 +11,6 @@ #include "routerlist.h" #include "config.h" #include "hs_common.h" -#include <openssl/rsa.h> #include "rend_test_helpers.h" #include "log_test_helpers.h" diff --git a/src/test/test_replay.c b/src/test/test_replay.c index e882bc6164..80e7203716 100644 --- a/src/test/test_replay.c +++ b/src/test/test_replay.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Tor Project, Inc. */ +/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define REPLAYCACHE_PRIVATE diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c index 13059267ac..db6b9b3872 100644 --- a/src/test/test_routerkeys.c +++ b/src/test/test_routerkeys.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index 78f1cf16b7..0b4b6c5c44 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_rust.c b/src/test/test_rust.c new file mode 100644 index 0000000000..6ad57d6fcb --- /dev/null +++ b/src/test/test_rust.c @@ -0,0 +1,31 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "compat_rust.h" +#include "test.h" +#include "util.h" + +static void +test_welcome_string(void *arg) +{ + (void)arg; + rust_str_t s = rust_welcome_string(); + const char *c_str = rust_str_get(s); + tt_assert(c_str); + size_t len = strlen(c_str); +#ifdef HAVE_RUST + tt_assert(len > 0); +#else + tt_assert(len == 0); +#endif + + done: + rust_str_free(s); +} + +struct testcase_t rust_tests[] = { + { "welcome_string", test_welcome_string, 0, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_rust.sh b/src/test/test_rust.sh new file mode 100755 index 0000000000..d559f94ce0 --- /dev/null +++ b/src/test/test_rust.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Test all the Rust crates we're using + +crates=tor_util + +exitcode=0 + +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 +done + +exit $exitcode diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index 05ea8e86e8..4c536b0905 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Tor Project, Inc. */ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_slow.c b/src/test/test_slow.c index 7c9f0b1cc2..e640702499 100644 --- a/src/test/test_slow.c +++ b/src/test/test_slow.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/test/test_socks.c b/src/test/test_socks.c index 62ff12fe15..bb1be11f2b 100644 --- a/src/test/test_socks.c +++ b/src/test/test_socks.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/test/test_storagedir.c b/src/test/test_storagedir.c new file mode 100644 index 0000000000..19e5de4ea3 --- /dev/null +++ b/src/test/test_storagedir.c @@ -0,0 +1,375 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "storagedir.h" +#include "test.h" + +#ifdef HAVE_UTIME_H +#include <utime.h> +#endif + +static void +test_storagedir_empty(void *arg) +{ + char *dirname = tor_strdup(get_fname_rnd("store_dir")); + storage_dir_t *d = NULL; + (void)arg; + + tt_int_op(FN_NOENT, OP_EQ, file_status(dirname)); + + d = storage_dir_new(dirname, 10); + tt_assert(d); + + tt_int_op(FN_DIR, OP_EQ, file_status(dirname)); + + tt_int_op(0, OP_EQ, smartlist_len(storage_dir_list(d))); + tt_u64_op(0, OP_EQ, storage_dir_get_usage(d)); + + storage_dir_free(d); + d = storage_dir_new(dirname, 10); + tt_assert(d); + + tt_int_op(FN_DIR, OP_EQ, file_status(dirname)); + + tt_int_op(0, OP_EQ, smartlist_len(storage_dir_list(d))); + tt_u64_op(0, OP_EQ, storage_dir_get_usage(d)); + + done: + storage_dir_free(d); + tor_free(dirname); +} + +static void +test_storagedir_basic(void *arg) +{ + char *dirname = tor_strdup(get_fname_rnd("store_dir")); + storage_dir_t *d = NULL; + uint8_t *junk = NULL, *bytes = NULL; + const size_t junklen = 1024; + char *fname1 = NULL, *fname2 = NULL; + const char hello_str[] = "then what are we but cold, alone ... ?"; + tor_mmap_t *mapping = NULL; + (void)arg; + + junk = tor_malloc(junklen); + crypto_rand((void*)junk, junklen); + + d = storage_dir_new(dirname, 10); + tt_assert(d); + tt_u64_op(0, OP_EQ, storage_dir_get_usage(d)); + + int r; + r = storage_dir_save_string_to_file(d, hello_str, 1, &fname1); + tt_int_op(r, OP_EQ, 0); + tt_ptr_op(fname1, OP_NE, NULL); + tt_u64_op(strlen(hello_str), OP_EQ, storage_dir_get_usage(d)); + + r = storage_dir_save_bytes_to_file(d, junk, junklen, 1, &fname2); + tt_int_op(r, OP_EQ, 0); + tt_ptr_op(fname2, OP_NE, NULL); + + tt_str_op(fname1, OP_NE, fname2); + + tt_int_op(2, OP_EQ, smartlist_len(storage_dir_list(d))); + tt_u64_op(junklen + strlen(hello_str), OP_EQ, storage_dir_get_usage(d)); + tt_assert(smartlist_contains_string(storage_dir_list(d), fname1)); + tt_assert(smartlist_contains_string(storage_dir_list(d), fname2)); + + storage_dir_free(d); + d = storage_dir_new(dirname, 10); + tt_assert(d); + tt_int_op(2, OP_EQ, smartlist_len(storage_dir_list(d))); + tt_u64_op(junklen + strlen(hello_str), OP_EQ, storage_dir_get_usage(d)); + tt_assert(smartlist_contains_string(storage_dir_list(d), fname1)); + tt_assert(smartlist_contains_string(storage_dir_list(d), fname2)); + + size_t n; + bytes = storage_dir_read(d, fname2, 1, &n); + tt_assert(bytes); + tt_u64_op(n, OP_EQ, junklen); + tt_mem_op(bytes, OP_EQ, junk, junklen); + + mapping = storage_dir_map(d, fname1); + tt_assert(mapping); + tt_u64_op(mapping->size, OP_EQ, strlen(hello_str)); + tt_mem_op(mapping->data, OP_EQ, hello_str, strlen(hello_str)); + + done: + tor_free(dirname); + tor_free(junk); + tor_free(bytes); + tor_munmap_file(mapping); + storage_dir_free(d); + tor_free(fname1); + tor_free(fname2); +} + +static void +test_storagedir_deletion(void *arg) +{ + (void)arg; + char *dirname = tor_strdup(get_fname_rnd("store_dir")); + storage_dir_t *d = NULL; + char *fn1 = NULL, *fn2 = NULL; + char *bytes = NULL; + int r; + const char str1[] = "There are nine and sixty ways to disguise communiques"; + const char str2[] = "And rather more than one of them is right"; + + // Make sure the directory is there. */ + d = storage_dir_new(dirname, 10); + storage_dir_free(d); + d = NULL; + + tor_asprintf(&fn1, "%s/1007", dirname); + r = write_str_to_file(fn1, str1, 0); + tt_int_op(r, OP_EQ, 0); + + tor_asprintf(&fn2, "%s/1003.tmp", dirname); + r = write_str_to_file(fn2, str2, 0); + tt_int_op(r, OP_EQ, 0); + + // The tempfile should be deleted the next time we list the directory. + d = storage_dir_new(dirname, 10); + tt_int_op(1, OP_EQ, smartlist_len(storage_dir_list(d))); + tt_u64_op(strlen(str1), OP_EQ, storage_dir_get_usage(d)); + tt_int_op(FN_FILE, OP_EQ, file_status(fn1)); + tt_int_op(FN_NOENT, OP_EQ, file_status(fn2)); + + bytes = (char*) storage_dir_read(d, "1007", 1, NULL); + tt_str_op(bytes, OP_EQ, str1); + + // Should have no effect; file already gone. + storage_dir_remove_file(d, "1003.tmp"); + tt_int_op(1, OP_EQ, smartlist_len(storage_dir_list(d))); + tt_u64_op(strlen(str1), OP_EQ, storage_dir_get_usage(d)); + + // Actually remove a file. + storage_dir_remove_file(d, "1007"); + tt_int_op(FN_NOENT, OP_EQ, file_status(fn1)); + tt_int_op(0, OP_EQ, smartlist_len(storage_dir_list(d))); + tt_u64_op(0, OP_EQ, storage_dir_get_usage(d)); + + done: + tor_free(dirname); + tor_free(fn1); + tor_free(fn2); + storage_dir_free(d); + tor_free(bytes); +} + +static void +test_storagedir_full(void *arg) +{ + (void)arg; + + char *dirname = tor_strdup(get_fname_rnd("store_dir")); + storage_dir_t *d = NULL; + const char str[] = "enemies of the peephole"; + int r; + + d = storage_dir_new(dirname, 3); + tt_assert(d); + + r = storage_dir_save_string_to_file(d, str, 1, NULL); + tt_int_op(r, OP_EQ, 0); + r = storage_dir_save_string_to_file(d, str, 1, NULL); + tt_int_op(r, OP_EQ, 0); + r = storage_dir_save_string_to_file(d, str, 1, NULL); + tt_int_op(r, OP_EQ, 0); + + // These should fail! + r = storage_dir_save_string_to_file(d, str, 1, NULL); + tt_int_op(r, OP_EQ, -1); + r = storage_dir_save_string_to_file(d, str, 1, NULL); + tt_int_op(r, OP_EQ, -1); + + tt_u64_op(strlen(str) * 3, OP_EQ, storage_dir_get_usage(d)); + + done: + tor_free(dirname); + storage_dir_free(d); +} + +static void +test_storagedir_cleaning(void *arg) +{ + (void)arg; + + char *dirname = tor_strdup(get_fname_rnd("store_dir")); + storage_dir_t *d = NULL; + const char str[] = + "On a mountain halfway between Reno and Rome / " + "We have a machine in a plexiglass dome / " + "Which listens and looks into everyone's home." + " -- Dr. Seuss"; + char *fns[8]; + int r, i; + + memset(fns, 0, sizeof(fns)); + d = storage_dir_new(dirname, 10); + tt_assert(d); + + for (i = 0; i < 8; ++i) { + r = storage_dir_save_string_to_file(d, str+i*2, 1, &fns[i]); + tt_int_op(r, OP_EQ, 0); + } + + /* Now we're going to make sure all the files have distinct mtimes. */ + time_t now = time(NULL); + struct utimbuf ub; + ub.actime = now; + ub.modtime = now - 1000; + for (i = 0; i < 8; ++i) { + char *f = NULL; + tor_asprintf(&f, "%s/%s", dirname, fns[i]); + r = utime(f, &ub); + tor_free(f); + tt_int_op(r, OP_EQ, 0); + ub.modtime += 5; + } + + const uint64_t usage_orig = storage_dir_get_usage(d); + /* No changes needed if we are already under target. */ + storage_dir_shrink(d, 1024*1024, 0); + tt_u64_op(usage_orig, OP_EQ, storage_dir_get_usage(d)); + + /* Get rid of at least one byte. This will delete fns[0]. */ + storage_dir_shrink(d, usage_orig - 1, 0); + tt_u64_op(usage_orig, OP_GT, storage_dir_get_usage(d)); + tt_u64_op(usage_orig - strlen(str), OP_EQ, storage_dir_get_usage(d)); + + /* Get rid of at least two files. This will delete fns[1] and fns[2]. */ + storage_dir_shrink(d, 1024*1024, 2); + tt_u64_op(usage_orig - strlen(str)*3 + 6, OP_EQ, storage_dir_get_usage(d)); + + /* Get rid of everything. */ + storage_dir_remove_all(d); + tt_u64_op(0, OP_EQ, storage_dir_get_usage(d)); + + done: + tor_free(dirname); + storage_dir_free(d); + for (i = 0; i < 8; ++i) { + tor_free(fns[i]); + } +} + +static void +test_storagedir_save_labeled(void *arg) +{ + (void)arg; + char *dirname = tor_strdup(get_fname_rnd("store_dir")); + storage_dir_t *d = NULL; + uint8_t *inp = tor_malloc_zero(8192); + config_line_t *labels = NULL; + char *fname = NULL; + uint8_t *saved = NULL; + + d = storage_dir_new(dirname, 10); + tt_assert(d); + + crypto_rand((char *)inp, 8192); + + config_line_append(&labels, "Foo", "bar baz"); + config_line_append(&labels, "quux", "quuzXxz"); + const char expected[] = + "Foo bar baz\n" + "quux quuzXxz\n"; + + int r = storage_dir_save_labeled_to_file(d, labels, inp, 8192, &fname); + tt_int_op(r, OP_EQ, 0); + + size_t n; + saved = storage_dir_read(d, fname, 1, &n); + tt_assert(memchr(saved, '\0', n)); + tt_str_op((char*)saved, OP_EQ, expected); /* NUL guarantees strcmp works */ + tt_mem_op(saved+strlen(expected)+1, OP_EQ, inp, 8192); + + done: + storage_dir_free(d); + tor_free(dirname); + tor_free(inp); + tor_free(fname); + config_free_lines(labels); + tor_free(saved); +} + +static void +test_storagedir_read_labeled(void *arg) +{ + (void)arg; + char *dirname = tor_strdup(get_fname_rnd("store_dir")); + storage_dir_t *d = NULL; + uint8_t *inp = tor_malloc_zero(8192); + config_line_t *labels = NULL, *labels2 = NULL; + char *fname = NULL; + tor_mmap_t *map = NULL; + uint8_t *as_read = NULL; + + d = storage_dir_new(dirname, 10); + tt_assert(d); + + tor_snprintf((char*)inp, 8192, + "Hello world\n" + "This is a test\n" + "Yadda yadda.\n"); + size_t bodylen = 8192 - strlen((char*)inp) - 1; + crypto_rand((char *)inp+strlen((char*)inp)+1, bodylen); + + int r = storage_dir_save_bytes_to_file(d, inp, 8192, 1, &fname); + tt_int_op(r, OP_EQ, 0); + + /* Try mapping */ + const uint8_t *datap = NULL; + size_t sz = 0; + map = storage_dir_map_labeled(d, fname, &labels, &datap, &sz); + tt_assert(map); + tt_assert(datap); + tt_u64_op(sz, OP_EQ, bodylen); + tt_mem_op(datap, OP_EQ, inp+strlen((char*)inp)+1, bodylen); + tt_assert(labels); + tt_str_op(labels->key, OP_EQ, "Hello"); + tt_str_op(labels->value, OP_EQ, "world"); + tt_assert(labels->next); + tt_str_op(labels->next->key, OP_EQ, "This"); + tt_str_op(labels->next->value, OP_EQ, "is a test"); + tt_assert(labels->next->next); + tt_str_op(labels->next->next->key, OP_EQ, "Yadda"); + tt_str_op(labels->next->next->value, OP_EQ, "yadda."); + tt_assert(labels->next->next->next == NULL); + + /* Try reading this time. */ + sz = 0; + as_read = storage_dir_read_labeled(d, fname, &labels2, &sz); + tt_assert(as_read); + tt_u64_op(sz, OP_EQ, bodylen); + tt_mem_op(as_read, OP_EQ, inp+strlen((char*)inp)+1, bodylen); + tt_assert(config_lines_eq(labels, labels2)); + + done: + storage_dir_free(d); + tor_free(dirname); + tor_free(inp); + tor_free(fname); + config_free_lines(labels); + config_free_lines(labels2); + tor_munmap_file(map); + tor_free(as_read); +} + +#define ENT(name) \ + { #name, test_storagedir_ ## name, TT_FORK, NULL, NULL } + +struct testcase_t storagedir_tests[] = { + ENT(empty), + ENT(basic), + ENT(deletion), + ENT(full), + ENT(cleaning), + ENT(save_labeled), + ENT(read_labeled), + END_OF_TESTCASES +}; + diff --git a/src/test/test_switch_id.c b/src/test/test_switch_id.c index e12205bb2e..53de793fe8 100644 --- a/src/test/test_switch_id.c +++ b/src/test/test_switch_id.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Tor Project, Inc. */ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" diff --git a/src/test/test_threads.c b/src/test/test_threads.c index ebbc95c7ca..18a9407ff7 100644 --- a/src/test/test_threads.c +++ b/src/test/test_threads.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/test/test_tortls.c b/src/test/test_tortls.c index 4bfcea211d..7aa3051464 100644 --- a/src/test/test_tortls.c +++ b/src/test/test_tortls.c @@ -1,7 +1,8 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define TORTLS_PRIVATE +#define TORTLS_OPENSSL_PRIVATE #define LOG_PRIVATE #include "orconfig.h" diff --git a/src/test/test_util.c b/src/test/test_util.c index ab6fe8ded9..fa6ce1dc85 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -2242,114 +2242,220 @@ test_util_pow2(void *arg) ; } -/** Run unit tests for compression functions */ static void -test_util_gzip(void *arg) +test_util_compress_impl(compress_method_t method) { - char *buf1=NULL, *buf2=NULL, *buf3=NULL, *cp1, *cp2; - const char *ccp2; + char *buf1=NULL, *buf2=NULL, *buf3=NULL; size_t len1, len2; - tor_zlib_state_t *state = NULL; - (void)arg; - buf1 = tor_strdup("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAAAAAZ"); - tt_assert(detect_compression_method(buf1, strlen(buf1)) == UNKNOWN_METHOD); - - tt_assert(!tor_gzip_compress(&buf2, &len1, buf1, strlen(buf1)+1, - GZIP_METHOD)); - tt_assert(buf2); - tt_assert(len1 < strlen(buf1)); - tt_assert(detect_compression_method(buf2, len1) == GZIP_METHOD); + tt_assert(tor_compress_supports_method(method)); - tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1, - GZIP_METHOD, 1, LOG_INFO)); - tt_assert(buf3); - tt_int_op(strlen(buf1) + 1,OP_EQ, len2); - tt_str_op(buf1,OP_EQ, buf3); + if (method != NO_METHOD) { + tt_assert(tor_compress_version_str(method) != NULL); + tt_assert(tor_compress_header_version_str(method) != NULL); + } - tor_free(buf2); - tor_free(buf3); + buf1 = tor_strdup("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAAAAAZ"); + tt_assert(detect_compression_method(buf1, strlen(buf1)) == UNKNOWN_METHOD); - tt_assert(!tor_gzip_compress(&buf2, &len1, buf1, strlen(buf1)+1, - ZLIB_METHOD)); - tt_assert(buf2); - tt_assert(detect_compression_method(buf2, len1) == ZLIB_METHOD); + tt_assert(!tor_compress(&buf2, &len1, buf1, strlen(buf1)+1, method)); + tt_assert(buf2 != NULL); + if (method == NO_METHOD) { + // The identity transform doesn't actually compress, and it isn't + // detectable as "the identity transform." + tt_int_op(len1, OP_EQ, strlen(buf1)+1); + tt_int_op(detect_compression_method(buf2, len1), OP_EQ, UNKNOWN_METHOD); + } else { + tt_int_op(len1, OP_LT, strlen(buf1)); + tt_int_op(detect_compression_method(buf2, len1), OP_EQ, method); + } - tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1, - ZLIB_METHOD, 1, LOG_INFO)); - tt_assert(buf3); - tt_int_op(strlen(buf1) + 1,OP_EQ, len2); - tt_str_op(buf1,OP_EQ, buf3); + tt_assert(!tor_uncompress(&buf3, &len2, buf2, len1, method, 1, LOG_INFO)); + tt_assert(buf3 != NULL); + tt_int_op(strlen(buf1) + 1, OP_EQ, len2); + tt_str_op(buf1, OP_EQ, buf3); + tt_int_op(buf3[len2], OP_EQ, 0); /* Check whether we can uncompress concatenated, compressed strings. */ tor_free(buf3); buf2 = tor_reallocarray(buf2, len1, 2); memcpy(buf2+len1, buf2, len1); - tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1*2, - ZLIB_METHOD, 1, LOG_INFO)); - tt_int_op((strlen(buf1)+1)*2,OP_EQ, len2); - tt_mem_op(buf3,OP_EQ, + tt_assert(!tor_uncompress(&buf3, &len2, buf2, len1*2, method, 1, LOG_INFO)); + tt_int_op((strlen(buf1)+1)*2, OP_EQ, len2); + tt_mem_op(buf3, OP_EQ, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAAAAAZ\0" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAAAAAZ\0", (strlen(buf1)+1)*2); + tt_int_op(buf3[len2], OP_EQ, 0); + + /* Check whether we can uncompress partial strings */ tor_free(buf1); tor_free(buf2); tor_free(buf3); - /* Check whether we can uncompress partial strings. */ - buf1 = - tor_strdup("String with low redundancy that won't be compressed much."); - tt_assert(!tor_gzip_compress(&buf2, &len1, buf1, strlen(buf1)+1, - ZLIB_METHOD)); - tt_assert(len1>16); - /* when we allow an incomplete string, we should succeed.*/ - tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1-16, - ZLIB_METHOD, 0, LOG_INFO)); - tt_assert(len2 > 5); - buf3[len2]='\0'; - tt_assert(!strcmpstart(buf1, buf3)); - - /* when we demand a complete string, this must fail. */ + size_t b1len = 1<<10; + if (method == ZSTD_METHOD) { + // zstd needs a big input before it starts generating output that it + // can partially decompress. + b1len = 1<<18; + } + buf1 = tor_malloc(b1len); + crypto_rand(buf1, b1len); + tt_assert(!tor_compress(&buf2, &len1, buf1, b1len, method)); + tt_int_op(len1, OP_GT, 16); + /* when we allow an incomplete output we should succeed.*/ + tt_assert(!tor_uncompress(&buf3, &len2, buf2, len1-16, + method, 0, LOG_INFO)); + tt_int_op(len2, OP_GT, 5); + tt_int_op(len2, OP_LE, len1); + tt_assert(fast_memeq(buf1, buf3, len2)); + tt_int_op(buf3[len2], OP_EQ, 0); + + /* when we demand a complete output from a real compression method, this + * must fail. */ tor_free(buf3); - tt_assert(tor_gzip_uncompress(&buf3, &len2, buf2, len1-16, - ZLIB_METHOD, 1, LOG_INFO)); - tt_assert(!buf3); + if (method != NO_METHOD) { + tt_assert(tor_uncompress(&buf3, &len2, buf2, len1-16, + method, 1, LOG_INFO)); + tt_assert(buf3 == NULL); + } - /* Now, try streaming compression. */ + done: tor_free(buf1); tor_free(buf2); tor_free(buf3); - state = tor_zlib_new(1, ZLIB_METHOD, HIGH_COMPRESSION); +} + +static void +test_util_compress_stream_impl(compress_method_t method, + compression_level_t level) +{ + char *buf1=NULL, *buf2=NULL, *buf3=NULL, *cp1, *cp2; + const char *ccp2; + size_t len1, len2; + + tor_compress_state_t *state = NULL; + state = tor_compress_new(1, method, level); tt_assert(state); cp1 = buf1 = tor_malloc(1024); len1 = 1024; ccp2 = "ABCDEFGHIJABCDEFGHIJ"; len2 = 21; - tt_assert(tor_zlib_process(state, &cp1, &len1, &ccp2, &len2, 0) - == TOR_ZLIB_OK); - tt_int_op(0,OP_EQ, len2); /* Make sure we compressed it all. */ + tt_int_op(tor_compress_process(state, &cp1, &len1, &ccp2, &len2, 0), + OP_EQ, TOR_COMPRESS_OK); + tt_int_op(0, OP_EQ, len2); /* Make sure we compressed it all. */ tt_assert(cp1 > buf1); len2 = 0; cp2 = cp1; - tt_assert(tor_zlib_process(state, &cp1, &len1, &ccp2, &len2, 1) - == TOR_ZLIB_DONE); - tt_int_op(0,OP_EQ, len2); - tt_assert(cp1 > cp2); /* Make sure we really added something. */ + tt_int_op(tor_compress_process(state, &cp1, &len1, &ccp2, &len2, 1), + OP_EQ, TOR_COMPRESS_DONE); + tt_int_op(0, OP_EQ, len2); + if (method == NO_METHOD) { + tt_ptr_op(cp1, OP_EQ, cp2); + } else { + tt_assert(cp1 > cp2); /* Make sure we really added something. */ + } + + tt_int_op(tor_compress_state_size(state), OP_GT, 0); - tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf1, 1024-len1, - ZLIB_METHOD, 1, LOG_WARN)); + tt_assert(!tor_uncompress(&buf3, &len2, buf1, 1024-len1, + method, 1, LOG_WARN)); /* Make sure it compressed right. */ tt_str_op(buf3, OP_EQ, "ABCDEFGHIJABCDEFGHIJ"); - tt_int_op(21,OP_EQ, len2); + tt_int_op(21, OP_EQ, len2); done: if (state) - tor_zlib_free(state); + tor_compress_free(state); + tor_free(buf1); tor_free(buf2); tor_free(buf3); - tor_free(buf1); +} + +/** Run unit tests for compression functions */ +static void +test_util_compress(void *arg) +{ + const char *methodname = arg; + tt_assert(methodname); + + compress_method_t method = compression_method_get_by_name(methodname); + tt_int_op(method, OP_NE, UNKNOWN_METHOD); + + if (! tor_compress_supports_method(method)) { + tt_skip(); + } + + compression_level_t levels[] = { + BEST_COMPRESSION, + HIGH_COMPRESSION, + MEDIUM_COMPRESSION, + LOW_COMPRESSION + }; + + test_util_compress_impl(method); + + for (unsigned l = 0; l < ARRAY_LENGTH(levels); ++l) { + compression_level_t level = levels[l]; + test_util_compress_stream_impl(method, level); + } + done: + ; +} + +static void +test_util_decompress_concatenated_impl(compress_method_t method) +{ + char input[4096]; + char *c1 = NULL, *c2 = NULL, *c3 = NULL; + char *result = NULL; + size_t sz1, sz2, sz3, szr; + int r; + + crypto_rand(input, sizeof(input)); + + /* Compress the input in two chunks. */ + r = tor_compress(&c1, &sz1, input, 2048, method); + tt_int_op(r, OP_EQ, 0); + r = tor_compress(&c2, &sz2, input+2048, 2048, method); + tt_int_op(r, OP_EQ, 0); + + /* concatenate the chunks. */ + sz3 = sz1 + sz2; + c3 = tor_malloc(sz3); + memcpy(c3, c1, sz1); + memcpy(c3+sz1, c2, sz2); + + /* decompress the concatenated result */ + r = tor_uncompress(&result, &szr, c3, sz3, method, 0, LOG_WARN); + tt_int_op(r, OP_EQ, 0); + tt_int_op(szr, OP_EQ, sizeof(input)); + tt_mem_op(result, OP_EQ, input, sizeof(input)); + + done: + tor_free(c1); + tor_free(c2); + tor_free(c3); + tor_free(result); +} + +static void +test_util_decompress_concatenated(void *arg) +{ + const char *methodname = arg; + tt_assert(methodname); + + compress_method_t method = compression_method_get_by_name(methodname); + tt_int_op(method, OP_NE, UNKNOWN_METHOD); + if (! tor_compress_supports_method(method)) { + tt_skip(); + } + + test_util_decompress_concatenated_impl(method); + done: + ; } static void @@ -2364,44 +2470,44 @@ test_util_gzip_compression_bomb(void *arg) char *one_mb = tor_malloc_zero(one_million); char *result = NULL; size_t result_len = 0; - tor_zlib_state_t *state = NULL; + tor_compress_state_t *state = NULL; /* Make sure we can't produce a compression bomb */ setup_full_capture_of_logs(LOG_WARN); - tt_int_op(-1, OP_EQ, tor_gzip_compress(&result, &result_len, - one_mb, one_million, - ZLIB_METHOD)); + tt_int_op(-1, OP_EQ, tor_compress(&result, &result_len, + one_mb, one_million, + ZLIB_METHOD)); expect_single_log_msg_containing( "We compressed something and got an insanely high " "compression factor; other Tors would think this " - "was a zlib bomb."); + "was a compression bomb."); teardown_capture_of_logs(); /* Here's a compression bomb that we made manually. */ const char compression_bomb[1039] = { 0x78, 0xDA, 0xED, 0xC1, 0x31, 0x01, 0x00, 0x00, 0x00, 0xC2, 0xA0, 0xF5, 0x4F, 0x6D, 0x08, 0x5F, 0xA0 /* .... */ }; - tt_int_op(-1, OP_EQ, tor_gzip_uncompress(&result, &result_len, - compression_bomb, 1039, - ZLIB_METHOD, 0, LOG_WARN)); + tt_int_op(-1, OP_EQ, tor_uncompress(&result, &result_len, + compression_bomb, 1039, + ZLIB_METHOD, 0, LOG_WARN)); /* Now try streaming that. */ - state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION); - tor_zlib_output_t r; + state = tor_compress_new(0, ZLIB_METHOD, HIGH_COMPRESSION); + tor_compress_output_t r; const char *inp = compression_bomb; size_t inlen = 1039; do { char *outp = one_mb; size_t outleft = 4096; /* small on purpose */ - r = tor_zlib_process(state, &outp, &outleft, &inp, &inlen, 0); + r = tor_compress_process(state, &outp, &outleft, &inp, &inlen, 0); tt_int_op(inlen, OP_NE, 0); - } while (r == TOR_ZLIB_BUF_FULL); + } while (r == TOR_COMPRESS_BUFFER_FULL); - tt_int_op(r, OP_EQ, TOR_ZLIB_ERR); + tt_int_op(r, OP_EQ, TOR_COMPRESS_ERROR); done: tor_free(one_mb); - tor_zlib_free(state); + tor_compress_free(state); } /** Run unit tests for mmap() wrapper functionality. */ @@ -3351,6 +3457,13 @@ test_util_memarea(void *arg) void *malloced_ptr = NULL; int i; +#ifdef DISABLE_MEMORY_SENTINELS + /* If memory sentinels are disabled, this whole module is just an alias for + malloc(), which is free to lay out memory most any way it wants. */ + if (1) + tt_skip(); +#endif + (void)arg; tt_assert(area); @@ -3944,17 +4057,13 @@ test_util_exit_status(void *ptr) #endif #ifndef _WIN32 -/* Check that fgets with a non-blocking pipe returns partial lines and sets - * EAGAIN, returns full lines and sets no error, and returns NULL on EOF and - * sets no error */ static void -test_util_fgets_eagain(void *ptr) +test_util_string_from_pipe(void *ptr) { int test_pipe[2] = {-1, -1}; - int retval; + int retval = 0; + enum stream_status status = IO_STREAM_TERM; ssize_t retlen; - char *retptr; - FILE *test_stream = NULL; char buf[4] = { 0 }; (void)ptr; @@ -3965,91 +4074,115 @@ test_util_fgets_eagain(void *ptr) retval = pipe(test_pipe); tt_int_op(retval, OP_EQ, 0); - /* Set up the read-end to be non-blocking */ - retval = fcntl(test_pipe[0], F_SETFL, O_NONBLOCK); - tt_int_op(retval, OP_EQ, 0); + /* Send in a string. */ + retlen = write(test_pipe[1], "ABC", 3); + tt_int_op(retlen, OP_EQ, 3); + + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); + tt_int_op(errno, OP_EQ, 0); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, "ABC"); + errno = 0; - /* Open it as a stdio stream */ - test_stream = fdopen(test_pipe[0], "r"); - tt_ptr_op(test_stream, OP_NE, NULL); + /* Send in a string that contains a nul. */ + retlen = write(test_pipe[1], "AB\0", 3); + tt_int_op(retlen, OP_EQ, 3); - /* Send in a partial line */ - retlen = write(test_pipe[1], "A", 1); + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); + tt_int_op(errno, OP_EQ, 0); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, "AB"); + errno = 0; + + /* Send in a string that contains a nul only. */ + retlen = write(test_pipe[1], "\0", 1); tt_int_op(retlen, OP_EQ, 1); - retptr = fgets(buf, sizeof(buf), test_stream); - tt_int_op(errno, OP_EQ, EAGAIN); - tt_ptr_op(retptr, OP_EQ, buf); - tt_str_op(buf, OP_EQ, "A"); + + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); + tt_int_op(errno, OP_EQ, 0); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, ""); errno = 0; - /* Send in the rest */ - retlen = write(test_pipe[1], "B\n", 2); - tt_int_op(retlen, OP_EQ, 2); - retptr = fgets(buf, sizeof(buf), test_stream); + /* Send in a string that contains a trailing newline. */ + retlen = write(test_pipe[1], "AB\n", 3); + tt_int_op(retlen, OP_EQ, 3); + + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); + tt_int_op(errno, OP_EQ, 0); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, "AB"); + errno = 0; + + /* Send in a string that contains a newline only. */ + retlen = write(test_pipe[1], "\n", 1); + tt_int_op(retlen, OP_EQ, 1); + + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); tt_int_op(errno, OP_EQ, 0); - tt_ptr_op(retptr, OP_EQ, buf); - tt_str_op(buf, OP_EQ, "B\n"); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, ""); errno = 0; - /* Send in a full line */ - retlen = write(test_pipe[1], "CD\n", 3); + /* Send in a string and check that we nul terminate return values. */ + retlen = write(test_pipe[1], "AAA", 3); tt_int_op(retlen, OP_EQ, 3); - retptr = fgets(buf, sizeof(buf), test_stream); + + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); tt_int_op(errno, OP_EQ, 0); - tt_ptr_op(retptr, OP_EQ, buf); - tt_str_op(buf, OP_EQ, "CD\n"); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, "AAA"); + tt_mem_op(buf, OP_EQ, "AAA\0", sizeof(buf)); errno = 0; - /* Send in a partial line */ - retlen = write(test_pipe[1], "E", 1); + retlen = write(test_pipe[1], "B", 1); tt_int_op(retlen, OP_EQ, 1); - retptr = fgets(buf, sizeof(buf), test_stream); - tt_int_op(errno, OP_EQ, EAGAIN); - tt_ptr_op(retptr, OP_EQ, buf); - tt_str_op(buf, OP_EQ, "E"); + + memset(buf, '\xff', sizeof(buf)); + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); + tt_int_op(errno, OP_EQ, 0); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, "B"); + tt_mem_op(buf, OP_EQ, "B\0\xff\xff", sizeof(buf)); errno = 0; - /* Send in the rest */ - retlen = write(test_pipe[1], "F\n", 2); - tt_int_op(retlen, OP_EQ, 2); - retptr = fgets(buf, sizeof(buf), test_stream); + /* Send in multiple lines. */ + retlen = write(test_pipe[1], "A\nB", 3); + tt_int_op(retlen, OP_EQ, 3); + + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); tt_int_op(errno, OP_EQ, 0); - tt_ptr_op(retptr, OP_EQ, buf); - tt_str_op(buf, OP_EQ, "F\n"); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, "A\nB"); errno = 0; - /* Send in a full line and close */ - retlen = write(test_pipe[1], "GH", 2); + /* Send in a line and close */ + retlen = write(test_pipe[1], "AB", 2); tt_int_op(retlen, OP_EQ, 2); retval = close(test_pipe[1]); tt_int_op(retval, OP_EQ, 0); test_pipe[1] = -1; - retptr = fgets(buf, sizeof(buf), test_stream); + + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); tt_int_op(errno, OP_EQ, 0); - tt_ptr_op(retptr, OP_EQ, buf); - tt_str_op(buf, OP_EQ, "GH"); + tt_int_op(status, OP_EQ, IO_STREAM_OKAY); + tt_str_op(buf, OP_EQ, "AB"); errno = 0; /* Check for EOF */ - retptr = fgets(buf, sizeof(buf), test_stream); + status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1); tt_int_op(errno, OP_EQ, 0); - tt_ptr_op(retptr, OP_EQ, NULL); - retval = feof(test_stream); - tt_int_op(retval, OP_NE, 0); + tt_int_op(status, OP_EQ, IO_STREAM_CLOSED); errno = 0; - /* Check that buf is unchanged according to C99 and C11 */ - tt_str_op(buf, OP_EQ, "GH"); - done: - if (test_stream != NULL) - fclose(test_stream); if (test_pipe[0] != -1) close(test_pipe[0]); if (test_pipe[1] != -1) close(test_pipe[1]); } -#endif + +#endif // _WIN32 /** * Test for format_hex_number_sigsafe() @@ -5669,12 +5802,99 @@ test_util_htonll(void *arg) ; } +static void +test_util_get_unquoted_path(void *arg) +{ + (void)arg; + + char *r = NULL; + + r = get_unquoted_path("\""); // " + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path("\"\"\""); // """ + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path("\\\""); // \" + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path("\\\"\\\""); // \"\" + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path("A\\B\\C\""); // A\B\C" + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path("\"A\\B\\C"); // "A\B\C + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path("\"A\\B\"C\""); // "A\B"C" + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path("A\\B\"C"); // A\B"C + tt_ptr_op(r, OP_EQ, NULL); + tor_free(r); + + r = get_unquoted_path(""); + tt_str_op(r, OP_EQ, ""); + tor_free(r); + + r = get_unquoted_path("\"\""); // "" + tt_str_op(r, OP_EQ, ""); + tor_free(r); + + r = get_unquoted_path("A\\B\\C"); // A\B\C + tt_str_op(r, OP_EQ, "A\\B\\C"); // A\B\C + tor_free(r); + + r = get_unquoted_path("\"A\\B\\C\""); // "A\B\C" + tt_str_op(r, OP_EQ, "A\\B\\C"); // A\B\C + tor_free(r); + + r = get_unquoted_path("\"\\\""); // "\" + tt_str_op(r, OP_EQ, "\\"); // \ /* comment to prevent line continuation */ + tor_free(r); + + r = get_unquoted_path("\"\\\"\""); // "\"" + tt_str_op(r, OP_EQ, "\""); // " + tor_free(r); + + r = get_unquoted_path("\"A\\B\\C\\\"\""); // "A\B\C\"" + tt_str_op(r, OP_EQ, "A\\B\\C\""); // A\B\C" + tor_free(r); + + r = get_unquoted_path("A\\B\\\"C"); // A\B\"C + tt_str_op(r, OP_EQ, "A\\B\"C"); // A\B"C + tor_free(r); + + r = get_unquoted_path("\"A\\B\\\"C\""); // "A\B\"C" + tt_str_op(r, OP_EQ, "A\\B\"C"); // A\B"C + + done: + tor_free(r); +} + #define UTIL_LEGACY(name) \ { #name, test_util_ ## name , 0, NULL, NULL } #define UTIL_TEST(name, flags) \ { #name, test_util_ ## name, flags, NULL, NULL } +#define COMPRESS(name, identifier) \ + { "compress/" #name, test_util_compress, 0, &passthrough_setup, \ + (char*)(identifier) } + +#define COMPRESS_CONCAT(name, identifier) \ + { "compress_concat/" #name, test_util_decompress_concatenated, 0, \ + &passthrough_setup, \ + (char*)(identifier) } + #ifdef _WIN32 #define UTIL_TEST_NO_WIN(n, f) { #n, NULL, TT_SKIP, NULL, NULL } #define UTIL_TEST_WIN_ONLY(n, f) UTIL_TEST(n, (f)) @@ -5699,7 +5919,16 @@ struct testcase_t util_tests[] = { UTIL_LEGACY(strmisc), UTIL_TEST(parse_integer, 0), UTIL_LEGACY(pow2), - UTIL_LEGACY(gzip), + COMPRESS(zlib, "deflate"), + COMPRESS(gzip, "gzip"), + COMPRESS(lzma, "x-tor-lzma"), + COMPRESS(zstd, "x-zstd"), + COMPRESS(none, "identity"), + COMPRESS_CONCAT(zlib, "deflate"), + COMPRESS_CONCAT(gzip, "gzip"), + COMPRESS_CONCAT(lzma, "x-tor-lzma"), + COMPRESS_CONCAT(zstd, "x-zstd"), + COMPRESS_CONCAT(none, "identity"), UTIL_TEST(gzip_compression_bomb, TT_FORK), UTIL_LEGACY(datadir), UTIL_LEGACY(memarea), @@ -5723,7 +5952,7 @@ struct testcase_t util_tests[] = { UTIL_TEST(num_cpus, 0), UTIL_TEST_WIN_ONLY(load_win_lib, 0), UTIL_TEST_NO_WIN(exit_status, 0), - UTIL_TEST_NO_WIN(fgets_eagain, 0), + UTIL_TEST_NO_WIN(string_from_pipe, 0), UTIL_TEST(format_hex_number, 0), UTIL_TEST(format_dec_number, 0), UTIL_TEST(join_win_cmdline, 0), @@ -5763,6 +5992,7 @@ struct testcase_t util_tests[] = { UTIL_TEST(monotonic_time, 0), UTIL_TEST(monotonic_time_ratchet, TT_FORK), UTIL_TEST(htonll, 0), + UTIL_TEST(get_unquoted_path, 0), END_OF_TESTCASES }; diff --git a/src/test/test_util_format.c b/src/test/test_util_format.c index 21a6923c6d..ea0a86499f 100644 --- a/src/test/test_util_format.c +++ b/src/test/test_util_format.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -133,48 +133,54 @@ test_util_format_base64_encode(void *ignored) } static void -test_util_format_base64_decode_nopad(void *ignored) +test_util_format_base64_decode_oddsize(void *ignored) { (void)ignored; int res; int i; char *src; - uint8_t *dst, *real_dst; - uint8_t expected[] = {0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65}; + char *dst, real_dst[7]; + char expected[] = {0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65}; char real_src[] = "ZXhhbXBsZQ"; + char expected40[] = "testing40characteroddsizebase64encoding!"; + char src40[] = "dGVzdGluZzQwY2hhcmFjdGVyb2Rkc2l6ZWJhc2U2NGVuY29kaW5nIQ"; + char pad40[] = "dGVzdGluZzQwY2hhcmFjdGVyb2Rkc2l6ZWJhc2U2NGVuY29kaW5nIQ=="; src = tor_malloc_zero(256); dst = tor_malloc_zero(1000); - real_dst = tor_malloc_zero(10); for (i=0;i<256;i++) { src[i] = (char)i; } - res = base64_decode_nopad(dst, 1, src, SIZE_T_CEILING); - tt_int_op(res, OP_EQ, -1); - - res = base64_decode_nopad(dst, 1, src, 5); + res = base64_decode(dst, 1, src, 5); tt_int_op(res, OP_EQ, -1); const char *s = "SGVsbG8gd29ybGQ"; - res = base64_decode_nopad(dst, 1000, s, strlen(s)); + res = base64_decode(dst, 1000, s, strlen(s)); tt_int_op(res, OP_EQ, 11); tt_mem_op(dst, OP_EQ, "Hello world", 11); s = "T3BhIG11bmRv"; - res = base64_decode_nopad(dst, 9, s, strlen(s)); + res = base64_decode(dst, 9, s, strlen(s)); tt_int_op(res, OP_EQ, 9); tt_mem_op(dst, OP_EQ, "Opa mundo", 9); - res = base64_decode_nopad(real_dst, 10, real_src, 10); + res = base64_decode(real_dst, sizeof(real_dst), real_src, 10); tt_int_op(res, OP_EQ, 7); tt_mem_op(real_dst, OP_EQ, expected, 7); + res = base64_decode(dst, 40, src40, strlen(src40)); + tt_int_op(res, OP_EQ, 40); + tt_mem_op(dst, OP_EQ, expected40, 40); + + res = base64_decode(dst, 40, pad40, strlen(pad40)); + tt_int_op(res, OP_EQ, 40); + tt_mem_op(dst, OP_EQ, expected40, 40); + done: tor_free(src); tor_free(dst); - tor_free(real_dst); } static void @@ -196,13 +202,10 @@ test_util_format_base64_decode(void *ignored) src[i] = (char)i; } - res = base64_decode(dst, 1, src, SIZE_T_CEILING); + res = base64_decode(dst, 1, src, 100); tt_int_op(res, OP_EQ, -1); - res = base64_decode(dst, SIZE_T_CEILING+1, src, 10); - tt_int_op(res, OP_EQ, -1); - - res = base64_decode(dst, 1, real_src, SIZE_MAX/3+1); + res = base64_decode(dst, 1, real_src, 10); tt_int_op(res, OP_EQ, -1); const char *s = "T3BhIG11bmRv"; @@ -370,11 +373,39 @@ test_util_format_base32_decode(void *arg) tor_free(dst); } +static void +test_util_format_encoded_size(void *arg) +{ + (void)arg; + uint8_t inbuf[256]; + char outbuf[1024]; + unsigned i; + + crypto_rand((char *)inbuf, sizeof(inbuf)); + for (i = 0; i <= sizeof(inbuf); ++i) { + /* XXXX (Once the return values are consistent, check them too.) */ + + base32_encode(outbuf, sizeof(outbuf), (char *)inbuf, i); + /* The "+ 1" below is an API inconsistency. */ + tt_int_op(strlen(outbuf) + 1, OP_EQ, base32_encoded_size(i)); + + base64_encode(outbuf, sizeof(outbuf), (char *)inbuf, i, 0); + tt_int_op(strlen(outbuf), OP_EQ, base64_encode_size(i, 0)); + base64_encode(outbuf, sizeof(outbuf), (char *)inbuf, i, + BASE64_ENCODE_MULTILINE); + tt_int_op(strlen(outbuf), OP_EQ, + base64_encode_size(i, BASE64_ENCODE_MULTILINE)); + } + + done: + ; +} + struct testcase_t util_format_tests[] = { { "unaligned_accessors", test_util_format_unaligned_accessors, 0, NULL, NULL }, { "base64_encode", test_util_format_base64_encode, 0, NULL, NULL }, - { "base64_decode_nopad", test_util_format_base64_decode_nopad, 0, + { "base64_decode_oddsize", test_util_format_base64_decode_oddsize, 0, NULL, NULL }, { "base64_decode", test_util_format_base64_decode, 0, NULL, NULL }, { "base16_decode", test_util_format_base16_decode, 0, NULL, NULL }, @@ -382,6 +413,7 @@ struct testcase_t util_format_tests[] = { NULL, NULL }, { "base32_decode", test_util_format_base32_decode, 0, NULL, NULL }, + { "encoded_size", test_util_format_encoded_size, 0, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_util_process.c b/src/test/test_util_process.c index 4e75b97f3d..70292f2287 100644 --- a/src/test/test_util_process.c +++ b/src/test/test_util_process.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Tor Project, Inc. */ +/* Copyright (c) 2010-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define UTIL_PROCESS_PRIVATE diff --git a/src/test/test_util_slow.c b/src/test/test_util_slow.c index 1e7160598c..3e5d78948d 100644 --- a/src/test/test_util_slow.c +++ b/src/test/test_util_slow.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" @@ -242,7 +242,7 @@ test_util_spawn_background_partial_read_impl(int exit_early) #else /* Check that we didn't read the end of file last time */ tt_assert(!eof); - pos = tor_read_all_handle(process_handle->stdout_handle, stdout_buf, + pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf, sizeof(stdout_buf) - 1, NULL, &eof); #endif log_info(LD_GENERAL, "tor_read_all_handle() returned %d", (int)pos); @@ -273,7 +273,7 @@ test_util_spawn_background_partial_read_impl(int exit_early) #else if (!eof) { /* We should have got all the data, but maybe not the EOF flag */ - pos = tor_read_all_handle(process_handle->stdout_handle, stdout_buf, + pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf, sizeof(stdout_buf) - 1, process_handle, &eof); tt_int_op(0,OP_EQ, pos); diff --git a/src/test/test_workqueue.c b/src/test/test_workqueue.c index ccb8d0c8ca..6fa46f90d4 100644 --- a/src/test/test_workqueue.c +++ b/src/test/test_workqueue.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" @@ -200,7 +200,9 @@ add_work(threadpool_t *tp) crypto_rand((char*)w->msg, 20); w->msglen = 20; ++rsa_sent; - return threadpool_queue_work(tp, workqueue_do_rsa, handle_reply, w); + return threadpool_queue_work_priority(tp, + WQ_PRI_MED, + workqueue_do_rsa, handle_reply, w); } else { ecdh_work_t *w = tor_malloc_zero(sizeof(*w)); w->serial = n_sent++; diff --git a/src/test/testing_common.c b/src/test/testing_common.c index caeae13a38..d7e36edbc0 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ extern const char tor_git_revision[]; @@ -21,6 +21,7 @@ const char tor_git_revision[] = ""; #include "rephist.h" #include "backtrace.h" #include "test.h" +#include "channelpadding.h" #include <stdio.h> #ifdef HAVE_FCNTL_H @@ -38,7 +39,6 @@ const char tor_git_revision[] = ""; #ifdef USE_DMALLOC #include <dmalloc.h> -#include <openssl/crypto.h> #include "main.h" #endif @@ -238,14 +238,15 @@ main(int c, const char **v) #ifdef USE_DMALLOC { - int r = CRYPTO_set_mem_ex_functions(tor_malloc_, tor_realloc_, tor_free_); - tor_assert(r); + int r = crypto_use_tor_alloc_functions(); + tor_assert(r == 0); } #endif update_approx_time(time(NULL)); options = options_new(); tor_threads_init(); + tor_compress_init(); network_init(); @@ -304,10 +305,15 @@ main(int c, const char **v) tor_free(errmsg); return 1; } + tor_set_failed_assertion_callback(an_assertion_failed); init_pregenerated_keys(); + channelpadding_new_consensus_params(NULL); + + predicted_ports_init(); + atexit(remove_directory); int have_failed = (tinytest_main(c, v, testgroups) != 0); diff --git a/src/test/testing_rsakeys.c b/src/test/testing_rsakeys.c index 134770bb0d..5dff233a69 100644 --- a/src/test/testing_rsakeys.c +++ b/src/test/testing_rsakeys.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/tools/include.am b/src/tools/include.am index d0185b5887..717af9e2ae 100644 --- a/src/tools/include.am +++ b/src/tools/include.am @@ -1,5 +1,4 @@ bin_PROGRAMS+= src/tools/tor-resolve src/tools/tor-gencert -noinst_PROGRAMS+= src/tools/tor-checkkey if COVERAGE_ENABLED noinst_PROGRAMS+= src/tools/tor-cov-resolve src/tools/tor-cov-gencert @@ -9,7 +8,8 @@ src_tools_tor_resolve_SOURCES = src/tools/tor-resolve.c src_tools_tor_resolve_LDFLAGS = src_tools_tor_resolve_LDADD = src/common/libor.a \ src/common/libor-ctime.a \ - @TOR_LIB_MATH@ @TOR_LIB_WS32@ + @TOR_LIB_MATH@ @TOR_LIB_WS32@ \ + $(rust_ldadd) if COVERAGE_ENABLED src_tools_tor_cov_resolve_SOURCES = src/tools/tor-resolve.c @@ -23,11 +23,12 @@ endif src_tools_tor_gencert_SOURCES = src/tools/tor-gencert.c src_tools_tor_gencert_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ src_tools_tor_gencert_LDADD = src/common/libor.a src/common/libor-crypto.a \ - src/common/libor-ctime.a \ - $(LIBKECCAK_TINY) \ - $(LIBDONNA) \ - @TOR_LIB_MATH@ @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ + src/common/libor-ctime.a \ + $(LIBKECCAK_TINY) \ + $(LIBDONNA) \ + @TOR_LIB_MATH@ @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ + $(rust_ldadd) if COVERAGE_ENABLED src_tools_tor_cov_gencert_SOURCES = src/tools/tor-gencert.c @@ -43,14 +44,4 @@ src_tools_tor_cov_gencert_LDADD = src/common/libor-testing.a \ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ endif -src_tools_tor_checkkey_SOURCES = src/tools/tor-checkkey.c -src_tools_tor_checkkey_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ -src_tools_tor_checkkey_LDADD = src/common/libor.a \ - src/common/libor-ctime.a \ - src/common/libor-crypto.a \ - $(LIBKECCAK_TINY) \ - $(LIBDONNA) \ - @TOR_LIB_MATH@ @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ - EXTRA_DIST += src/tools/tor-fw-helper/README diff --git a/src/tools/tor-checkkey.c b/src/tools/tor-checkkey.c deleted file mode 100644 index 3e16fd0336..0000000000 --- a/src/tools/tor-checkkey.c +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright (c) 2008-2015, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -#include "orconfig.h" - -#include <stdio.h> -#include <stdlib.h> -#include "crypto.h" -#include "torlog.h" -#include "util.h" -#include "compat.h" -#include "compat_openssl.h" -#include <openssl/bn.h> -#include <openssl/rsa.h> - -int -main(int c, char **v) -{ - crypto_pk_t *env; - char *str; - RSA *rsa; - int wantdigest=0; - int fname_idx; - char *fname=NULL; - init_logging(1); - - if (c < 2) { - fprintf(stderr, "Hi. I'm tor-checkkey. Tell me a filename that " - "has a PEM-encoded RSA public key (like in a cert) and I'll " - "dump the modulus. Use the --digest option too and I'll " - "dump the digest.\n"); - return 1; - } - - if (crypto_global_init(0, NULL, NULL)) { - fprintf(stderr, "Couldn't initialize crypto library.\n"); - return 1; - } - - if (!strcmp(v[1], "--digest")) { - wantdigest = 1; - fname_idx = 2; - if (c<3) { - fprintf(stderr, "too few arguments"); - return 1; - } - } else { - wantdigest = 0; - fname_idx = 1; - } - - fname = expand_filename(v[fname_idx]); - str = read_file_to_str(fname, 0, NULL); - tor_free(fname); - if (!str) { - fprintf(stderr, "Couldn't read %s\n", v[fname_idx]); - return 1; - } - - env = crypto_pk_new(); - if (crypto_pk_read_public_key_from_string(env, str, strlen(str))<0) { - fprintf(stderr, "Couldn't parse key.\n"); - return 1; - } - tor_free(str); - - if (wantdigest) { - char digest[HEX_DIGEST_LEN+1]; - if (crypto_pk_get_fingerprint(env, digest, 0)<0) - return 1; - printf("%s\n",digest); - } else { - rsa = crypto_pk_get_rsa_(env); - - const BIGNUM *rsa_n; -#ifdef OPENSSL_1_1_API - const BIGNUM *rsa_e, *rsa_d; - RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d); -#else - rsa_n = rsa->n; -#endif - str = BN_bn2hex(rsa_n); - - printf("%s\n", str); - } - - return 0; -} - diff --git a/src/tools/tor-gencert.c b/src/tools/tor-gencert.c index db308485e6..395535697f 100644 --- a/src/tools/tor-gencert.c +++ b/src/tools/tor-gencert.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" diff --git a/src/tools/tor-resolve.c b/src/tools/tor-resolve.c index 6ac866d3c0..1e2409a131 100644 --- a/src/tools/tor-resolve.c +++ b/src/tools/tor-resolve.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson - * Copyright (c) 2007-2015, The Tor Project, Inc. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ diff --git a/src/trace/debug.h b/src/trace/debug.h new file mode 100644 index 0000000000..3a1652543a --- /dev/null +++ b/src/trace/debug.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_TRACE_LOG_DEBUG_H +#define TOR_TRACE_LOG_DEBUG_H + +#include "torlog.h" + +/* Stringify pre-processor trick. */ +#define XSTR(d) STR(d) +#define STR(s) #s + +/* Send every event to a debug log level. This is useful to debug new trace + * events without implementing them for a specific event tracing framework. + * Note that the arguments are ignored since at this step we do not know the + * types and amount there is. */ + +/* Example on how to map a tracepoint to log_debug(). */ +#undef tor_trace +#define tor_trace(subsystem, name, args...) \ + log_debug(LD_GENERAL, "Trace event \"" XSTR(name) "\" from " \ + "\"" XSTR(subsystem) "\" hit. " \ + "(line "XSTR(__LINE__) ")") + +#endif /* TOR_TRACE_LOG_DEBUG_H */ diff --git a/src/trace/events.h b/src/trace/events.h new file mode 100644 index 0000000000..1be1fd596e --- /dev/null +++ b/src/trace/events.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file events.h + * \brief Header file for Tor event tracing. + **/ + +#ifndef TOR_TRACE_EVENTS_H +#define TOR_TRACE_EVENTS_H + +/* + * The following defines a generic event tracing function name that has to be + * used to trace events in the code base. + * + * That generic function is then defined by a event tracing framework. For + * instance, the "log debug" framework sends all trace events to log_debug() + * which is defined in src/trace/debug.h which can only be enabled at compile + * time (--enable-event-tracing-debug). + * + * By default, every trace events in the code base are replaced by a NOP. See + * doc/HACKING/Tracing.md for more information on how to use event tracing or + * add events. + */ + +#ifdef TOR_EVENT_TRACING_ENABLED +/* Map every trace event to a per subsystem macro. */ +#define tor_trace(subsystem, name, ...) \ + tor_trace_##subsystem(name, __VA_ARGS__) + +/* Enable event tracing for the debug framework where all trace events are + * mapped to a log_debug(). */ +#ifdef USE_EVENT_TRACING_DEBUG +#include "trace/debug.h" +#endif + +#else /* TOR_EVENT_TRACING_ENABLED */ + +/* Reaching this point, we NOP every event declaration because event tracing + * is not been enabled at compile time. */ +#define tor_trace(subsystem, name, args...) + +#endif /* TOR_EVENT_TRACING_ENABLED */ + +#endif /* TOR_TRACE_EVENTS_H */ diff --git a/src/trace/include.am b/src/trace/include.am new file mode 100644 index 0000000000..3285b04de6 --- /dev/null +++ b/src/trace/include.am @@ -0,0 +1,22 @@ +# Include the src/ so we can use the trace/events.h statement when including +# any file in that directory. +AM_CPPFLAGS += -I$(srcdir)/src + +noinst_LIBRARIES += \ + src/trace/libor-trace.a +LIBOR_TRACE_A_SOURCES = \ + src/trace/trace.c + +TRACEHEADERS = \ + src/trace/trace.h \ + src/trace/events.h + +if USE_EVENT_TRACING_DEBUG +TRACEHEADERS += \ + src/trace/debug.h +endif + +# Library source files. +src_trace_libor_trace_a_SOURCES = $(LIBOR_TRACE_A_SOURCES) + +noinst_HEADERS+= $(TRACEHEADERS) diff --git a/src/trace/trace.c b/src/trace/trace.c new file mode 100644 index 0000000000..fcdb80091f --- /dev/null +++ b/src/trace/trace.c @@ -0,0 +1,11 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "trace.h" + +/** Initialize the tracing library. */ +void +tor_trace_init(void) +{ +} + diff --git a/src/trace/trace.h b/src/trace/trace.h new file mode 100644 index 0000000000..28fcd8eea8 --- /dev/null +++ b/src/trace/trace.h @@ -0,0 +1,10 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_TRACE_TRACE_H +#define TOR_TRACE_TRACE_H + +void tor_trace_init(void); + +#endif // TOR_TRACE_TRACE_H + diff --git a/src/trunnel/channelpadding_negotiation.c b/src/trunnel/channelpadding_negotiation.c new file mode 100644 index 0000000000..172d6f8a03 --- /dev/null +++ b/src/trunnel/channelpadding_negotiation.c @@ -0,0 +1,281 @@ +/* channelpadding_negotiation.c -- generated by Trunnel v1.4.3. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#include <stdlib.h> +#include "trunnel-impl.h" + +#include "channelpadding_negotiation.h" + +#define TRUNNEL_SET_ERROR_CODE(obj) \ + do { \ + (obj)->trunnel_error_code_ = 1; \ + } while (0) + +#if defined(__COVERITY__) || defined(__clang_analyzer__) +/* If we're runnning a static analysis tool, we don't want it to complain + * that some of our remaining-bytes checks are dead-code. */ +int channelpaddingnegotiation_deadcode_dummy__ = 0; +#define OR_DEADCODE_DUMMY || channelpaddingnegotiation_deadcode_dummy__ +#else +#define OR_DEADCODE_DUMMY +#endif + +#define CHECK_REMAINING(nbytes, label) \ + do { \ + if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \ + goto label; \ + } \ + } while (0) + +channelpadding_negotiate_t * +channelpadding_negotiate_new(void) +{ + channelpadding_negotiate_t *val = trunnel_calloc(1, sizeof(channelpadding_negotiate_t)); + if (NULL == val) + return NULL; + val->command = CHANNELPADDING_COMMAND_START; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +channelpadding_negotiate_clear(channelpadding_negotiate_t *obj) +{ + (void) obj; +} + +void +channelpadding_negotiate_free(channelpadding_negotiate_t *obj) +{ + if (obj == NULL) + return; + channelpadding_negotiate_clear(obj); + trunnel_memwipe(obj, sizeof(channelpadding_negotiate_t)); + trunnel_free_(obj); +} + +uint8_t +channelpadding_negotiate_get_version(channelpadding_negotiate_t *inp) +{ + return inp->version; +} +int +channelpadding_negotiate_set_version(channelpadding_negotiate_t *inp, uint8_t val) +{ + if (! ((val == 0))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->version = val; + return 0; +} +uint8_t +channelpadding_negotiate_get_command(channelpadding_negotiate_t *inp) +{ + return inp->command; +} +int +channelpadding_negotiate_set_command(channelpadding_negotiate_t *inp, uint8_t val) +{ + if (! ((val == CHANNELPADDING_COMMAND_START || val == CHANNELPADDING_COMMAND_STOP))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->command = val; + return 0; +} +uint16_t +channelpadding_negotiate_get_ito_low_ms(channelpadding_negotiate_t *inp) +{ + return inp->ito_low_ms; +} +int +channelpadding_negotiate_set_ito_low_ms(channelpadding_negotiate_t *inp, uint16_t val) +{ + inp->ito_low_ms = val; + return 0; +} +uint16_t +channelpadding_negotiate_get_ito_high_ms(channelpadding_negotiate_t *inp) +{ + return inp->ito_high_ms; +} +int +channelpadding_negotiate_set_ito_high_ms(channelpadding_negotiate_t *inp, uint16_t val) +{ + inp->ito_high_ms = val; + return 0; +} +const char * +channelpadding_negotiate_check(const channelpadding_negotiate_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->version == 0)) + return "Integer out of bounds"; + if (! (obj->command == CHANNELPADDING_COMMAND_START || obj->command == CHANNELPADDING_COMMAND_STOP)) + return "Integer out of bounds"; + return NULL; +} + +ssize_t +channelpadding_negotiate_encoded_len(const channelpadding_negotiate_t *obj) +{ + ssize_t result = 0; + + if (NULL != channelpadding_negotiate_check(obj)) + return -1; + + + /* Length of u8 version IN [0] */ + result += 1; + + /* Length of u8 command IN [CHANNELPADDING_COMMAND_START, CHANNELPADDING_COMMAND_STOP] */ + result += 1; + + /* Length of u16 ito_low_ms */ + result += 2; + + /* Length of u16 ito_high_ms */ + result += 2; + return result; +} +int +channelpadding_negotiate_clear_errors(channelpadding_negotiate_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +channelpadding_negotiate_encode(uint8_t *output, const size_t avail, const channelpadding_negotiate_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = channelpadding_negotiate_encoded_len(obj); +#endif + + if (NULL != (msg = channelpadding_negotiate_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 version IN [0] */ + trunnel_assert(written <= avail); + if (avail - written < 1) + goto truncated; + trunnel_set_uint8(ptr, (obj->version)); + written += 1; ptr += 1; + + /* Encode u8 command IN [CHANNELPADDING_COMMAND_START, CHANNELPADDING_COMMAND_STOP] */ + trunnel_assert(written <= avail); + if (avail - written < 1) + goto truncated; + trunnel_set_uint8(ptr, (obj->command)); + written += 1; ptr += 1; + + /* Encode u16 ito_low_ms */ + trunnel_assert(written <= avail); + if (avail - written < 2) + goto truncated; + trunnel_set_uint16(ptr, trunnel_htons(obj->ito_low_ms)); + written += 2; ptr += 2; + + /* Encode u16 ito_high_ms */ + trunnel_assert(written <= avail); + if (avail - written < 2) + goto truncated; + trunnel_set_uint16(ptr, trunnel_htons(obj->ito_high_ms)); + written += 2; ptr += 2; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As channelpadding_negotiate_parse(), but do not allocate the + * output object. + */ +static ssize_t +channelpadding_negotiate_parse_into(channelpadding_negotiate_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 version IN [0] */ + CHECK_REMAINING(1, truncated); + obj->version = (trunnel_get_uint8(ptr)); + remaining -= 1; ptr += 1; + if (! (obj->version == 0)) + goto fail; + + /* Parse u8 command IN [CHANNELPADDING_COMMAND_START, CHANNELPADDING_COMMAND_STOP] */ + CHECK_REMAINING(1, truncated); + obj->command = (trunnel_get_uint8(ptr)); + remaining -= 1; ptr += 1; + if (! (obj->command == CHANNELPADDING_COMMAND_START || obj->command == CHANNELPADDING_COMMAND_STOP)) + goto fail; + + /* Parse u16 ito_low_ms */ + CHECK_REMAINING(2, truncated); + obj->ito_low_ms = trunnel_ntohs(trunnel_get_uint16(ptr)); + remaining -= 2; ptr += 2; + + /* Parse u16 ito_high_ms */ + CHECK_REMAINING(2, truncated); + obj->ito_high_ms = trunnel_ntohs(trunnel_get_uint16(ptr)); + remaining -= 2; ptr += 2; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + fail: + result = -1; + return result; +} + +ssize_t +channelpadding_negotiate_parse(channelpadding_negotiate_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = channelpadding_negotiate_new(); + if (NULL == *output) + return -1; + result = channelpadding_negotiate_parse_into(*output, input, len_in); + if (result < 0) { + channelpadding_negotiate_free(*output); + *output = NULL; + } + return result; +} diff --git a/src/trunnel/channelpadding_negotiation.h b/src/trunnel/channelpadding_negotiation.h new file mode 100644 index 0000000000..e58bda3be1 --- /dev/null +++ b/src/trunnel/channelpadding_negotiation.h @@ -0,0 +1,98 @@ +/* channelpadding_negotiation.h -- generated by by Trunnel v1.4.3. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#ifndef TRUNNEL_CHANNELPADDING_NEGOTIATION_H +#define TRUNNEL_CHANNELPADDING_NEGOTIATION_H + +#include <stdint.h> +#include "trunnel.h" + +#define CHANNELPADDING_COMMAND_STOP 1 +#define CHANNELPADDING_COMMAND_START 2 +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CHANNELPADDING_NEGOTIATE) +struct channelpadding_negotiate_st { + uint8_t version; + uint8_t command; + uint16_t ito_low_ms; + uint16_t ito_high_ms; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct channelpadding_negotiate_st channelpadding_negotiate_t; +/** Return a newly allocated channelpadding_negotiate with all + * elements set to zero. + */ +channelpadding_negotiate_t *channelpadding_negotiate_new(void); +/** Release all storage held by the channelpadding_negotiate in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void channelpadding_negotiate_free(channelpadding_negotiate_t *victim); +/** Try to parse a channelpadding_negotiate from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated channelpadding_negotiate_t. On failure, return -2 + * if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t channelpadding_negotiate_parse(channelpadding_negotiate_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * channelpadding_negotiate in 'obj'. On failure, return a negative + * value. Note that this value may be an overestimate, and can even be + * an underestimate for certain unencodeable objects. + */ +ssize_t channelpadding_negotiate_encoded_len(const channelpadding_negotiate_t *obj); +/** Try to encode the channelpadding_negotiate from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t channelpadding_negotiate_encode(uint8_t *output, size_t avail, const channelpadding_negotiate_t *input); +/** Check whether the internal state of the channelpadding_negotiate + * in 'obj' is consistent. Return NULL if it is, and a short message + * if it is not. + */ +const char *channelpadding_negotiate_check(const channelpadding_negotiate_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int channelpadding_negotiate_clear_errors(channelpadding_negotiate_t *obj); +/** Return the value of the version field of the + * channelpadding_negotiate_t in 'inp' + */ +uint8_t channelpadding_negotiate_get_version(channelpadding_negotiate_t *inp); +/** Set the value of the version field of the + * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int channelpadding_negotiate_set_version(channelpadding_negotiate_t *inp, uint8_t val); +/** Return the value of the command field of the + * channelpadding_negotiate_t in 'inp' + */ +uint8_t channelpadding_negotiate_get_command(channelpadding_negotiate_t *inp); +/** Set the value of the command field of the + * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int channelpadding_negotiate_set_command(channelpadding_negotiate_t *inp, uint8_t val); +/** Return the value of the ito_low_ms field of the + * channelpadding_negotiate_t in 'inp' + */ +uint16_t channelpadding_negotiate_get_ito_low_ms(channelpadding_negotiate_t *inp); +/** Set the value of the ito_low_ms field of the + * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int channelpadding_negotiate_set_ito_low_ms(channelpadding_negotiate_t *inp, uint16_t val); +/** Return the value of the ito_high_ms field of the + * channelpadding_negotiate_t in 'inp' + */ +uint16_t channelpadding_negotiate_get_ito_high_ms(channelpadding_negotiate_t *inp); +/** Set the value of the ito_high_ms field of the + * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int channelpadding_negotiate_set_ito_high_ms(channelpadding_negotiate_t *inp, uint16_t val); + + +#endif diff --git a/src/trunnel/channelpadding_negotiation.trunnel b/src/trunnel/channelpadding_negotiation.trunnel new file mode 100644 index 0000000000..7f2d4795b0 --- /dev/null +++ b/src/trunnel/channelpadding_negotiation.trunnel @@ -0,0 +1,17 @@ +const CHANNELPADDING_COMMAND_STOP = 1; +const CHANNELPADDING_COMMAND_START = 2; + +/* This command tells the relay to alter its min and max netflow + timeout range values, and send padding at that rate (resuming + if stopped). */ +struct channelpadding_negotiate { + u8 version IN [0]; + u8 command IN [CHANNELPADDING_COMMAND_START, CHANNELPADDING_COMMAND_STOP]; + + /* Min must not be lower than the current consensus parameter + nf_ito_low. */ + u16 ito_low_ms; + + /* Max must not be lower than ito_low_ms */ + u16 ito_high_ms; +}; diff --git a/src/trunnel/hs/cell_common.c b/src/trunnel/hs/cell_common.c index 830f2260ee..b7f19ffc60 100644 --- a/src/trunnel/hs/cell_common.c +++ b/src/trunnel/hs/cell_common.c @@ -28,10 +28,10 @@ int cellcommon_deadcode_dummy__ = 0; } \ } while (0) -cell_extension_fields_t * -cell_extension_fields_new(void) +trn_cell_extension_fields_t * +trn_cell_extension_fields_new(void) { - cell_extension_fields_t *val = trunnel_calloc(1, sizeof(cell_extension_fields_t)); + trn_cell_extension_fields_t *val = trunnel_calloc(1, sizeof(trn_cell_extension_fields_t)); if (NULL == val) return NULL; return val; @@ -40,7 +40,7 @@ cell_extension_fields_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -cell_extension_fields_clear(cell_extension_fields_t *obj) +trn_cell_extension_fields_clear(trn_cell_extension_fields_t *obj) { (void) obj; TRUNNEL_DYNARRAY_WIPE(&obj->field); @@ -48,62 +48,62 @@ cell_extension_fields_clear(cell_extension_fields_t *obj) } void -cell_extension_fields_free(cell_extension_fields_t *obj) +trn_cell_extension_fields_free(trn_cell_extension_fields_t *obj) { if (obj == NULL) return; - cell_extension_fields_clear(obj); - trunnel_memwipe(obj, sizeof(cell_extension_fields_t)); + trn_cell_extension_fields_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_extension_fields_t)); trunnel_free_(obj); } uint8_t -cell_extension_fields_get_field_type(const cell_extension_fields_t *inp) +trn_cell_extension_fields_get_field_type(const trn_cell_extension_fields_t *inp) { return inp->field_type; } int -cell_extension_fields_set_field_type(cell_extension_fields_t *inp, uint8_t val) +trn_cell_extension_fields_set_field_type(trn_cell_extension_fields_t *inp, uint8_t val) { inp->field_type = val; return 0; } uint8_t -cell_extension_fields_get_field_len(const cell_extension_fields_t *inp) +trn_cell_extension_fields_get_field_len(const trn_cell_extension_fields_t *inp) { return inp->field_len; } int -cell_extension_fields_set_field_len(cell_extension_fields_t *inp, uint8_t val) +trn_cell_extension_fields_set_field_len(trn_cell_extension_fields_t *inp, uint8_t val) { inp->field_len = val; return 0; } size_t -cell_extension_fields_getlen_field(const cell_extension_fields_t *inp) +trn_cell_extension_fields_getlen_field(const trn_cell_extension_fields_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->field); } uint8_t -cell_extension_fields_get_field(cell_extension_fields_t *inp, size_t idx) +trn_cell_extension_fields_get_field(trn_cell_extension_fields_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->field, idx); } uint8_t -cell_extension_fields_getconst_field(const cell_extension_fields_t *inp, size_t idx) +trn_cell_extension_fields_getconst_field(const trn_cell_extension_fields_t *inp, size_t idx) { - return cell_extension_fields_get_field((cell_extension_fields_t*)inp, idx); + return trn_cell_extension_fields_get_field((trn_cell_extension_fields_t*)inp, idx); } int -cell_extension_fields_set_field(cell_extension_fields_t *inp, size_t idx, uint8_t elt) +trn_cell_extension_fields_set_field(trn_cell_extension_fields_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->field, idx, elt); return 0; } int -cell_extension_fields_add_field(cell_extension_fields_t *inp, uint8_t elt) +trn_cell_extension_fields_add_field(trn_cell_extension_fields_t *inp, uint8_t elt) { #if SIZE_MAX >= UINT8_MAX if (inp->field.n_ == UINT8_MAX) @@ -117,17 +117,17 @@ cell_extension_fields_add_field(cell_extension_fields_t *inp, uint8_t elt) } uint8_t * -cell_extension_fields_getarray_field(cell_extension_fields_t *inp) +trn_cell_extension_fields_getarray_field(trn_cell_extension_fields_t *inp) { return inp->field.elts_; } const uint8_t * -cell_extension_fields_getconstarray_field(const cell_extension_fields_t *inp) +trn_cell_extension_fields_getconstarray_field(const trn_cell_extension_fields_t *inp) { - return (const uint8_t *)cell_extension_fields_getarray_field((cell_extension_fields_t*)inp); + return (const uint8_t *)trn_cell_extension_fields_getarray_field((trn_cell_extension_fields_t*)inp); } int -cell_extension_fields_setlen_field(cell_extension_fields_t *inp, size_t newlen) +trn_cell_extension_fields_setlen_field(trn_cell_extension_fields_t *inp, size_t newlen) { uint8_t *newptr; #if UINT8_MAX < SIZE_MAX @@ -147,7 +147,7 @@ cell_extension_fields_setlen_field(cell_extension_fields_t *inp, size_t newlen) return -1; } const char * -cell_extension_fields_check(const cell_extension_fields_t *obj) +trn_cell_extension_fields_check(const trn_cell_extension_fields_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -159,11 +159,11 @@ cell_extension_fields_check(const cell_extension_fields_t *obj) } ssize_t -cell_extension_fields_encoded_len(const cell_extension_fields_t *obj) +trn_cell_extension_fields_encoded_len(const trn_cell_extension_fields_t *obj) { ssize_t result = 0; - if (NULL != cell_extension_fields_check(obj)) + if (NULL != trn_cell_extension_fields_check(obj)) return -1; @@ -178,24 +178,24 @@ cell_extension_fields_encoded_len(const cell_extension_fields_t *obj) return result; } int -cell_extension_fields_clear_errors(cell_extension_fields_t *obj) +trn_cell_extension_fields_clear_errors(trn_cell_extension_fields_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -cell_extension_fields_encode(uint8_t *output, const size_t avail, const cell_extension_fields_t *obj) +trn_cell_extension_fields_encode(uint8_t *output, const size_t avail, const trn_cell_extension_fields_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = cell_extension_fields_encoded_len(obj); + const ssize_t encoded_len = trn_cell_extension_fields_encoded_len(obj); #endif - if (NULL != (msg = cell_extension_fields_check(obj))) + if (NULL != (msg = trn_cell_extension_fields_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -252,11 +252,11 @@ cell_extension_fields_encode(uint8_t *output, const size_t avail, const cell_ext return result; } -/** As cell_extension_fields_parse(), but do not allocate the output - * object. +/** As trn_cell_extension_fields_parse(), but do not allocate the + * output object. */ static ssize_t -cell_extension_fields_parse_into(cell_extension_fields_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_extension_fields_parse_into(trn_cell_extension_fields_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; @@ -290,23 +290,23 @@ cell_extension_fields_parse_into(cell_extension_fields_t *obj, const uint8_t *in } ssize_t -cell_extension_fields_parse(cell_extension_fields_t **output, const uint8_t *input, const size_t len_in) +trn_cell_extension_fields_parse(trn_cell_extension_fields_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = cell_extension_fields_new(); + *output = trn_cell_extension_fields_new(); if (NULL == *output) return -1; - result = cell_extension_fields_parse_into(*output, input, len_in); + result = trn_cell_extension_fields_parse_into(*output, input, len_in); if (result < 0) { - cell_extension_fields_free(*output); + trn_cell_extension_fields_free(*output); *output = NULL; } return result; } -cell_extension_t * -cell_extension_new(void) +trn_cell_extension_t * +trn_cell_extension_new(void) { - cell_extension_t *val = trunnel_calloc(1, sizeof(cell_extension_t)); + trn_cell_extension_t *val = trunnel_calloc(1, sizeof(trn_cell_extension_t)); if (NULL == val) return NULL; return val; @@ -315,14 +315,14 @@ cell_extension_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -cell_extension_clear(cell_extension_t *obj) +trn_cell_extension_clear(trn_cell_extension_t *obj) { (void) obj; { unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { - cell_extension_fields_free(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); + trn_cell_extension_fields_free(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); } } TRUNNEL_DYNARRAY_WIPE(&obj->fields); @@ -330,92 +330,92 @@ cell_extension_clear(cell_extension_t *obj) } void -cell_extension_free(cell_extension_t *obj) +trn_cell_extension_free(trn_cell_extension_t *obj) { if (obj == NULL) return; - cell_extension_clear(obj); - trunnel_memwipe(obj, sizeof(cell_extension_t)); + trn_cell_extension_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_extension_t)); trunnel_free_(obj); } uint8_t -cell_extension_get_num(const cell_extension_t *inp) +trn_cell_extension_get_num(const trn_cell_extension_t *inp) { return inp->num; } int -cell_extension_set_num(cell_extension_t *inp, uint8_t val) +trn_cell_extension_set_num(trn_cell_extension_t *inp, uint8_t val) { inp->num = val; return 0; } size_t -cell_extension_getlen_fields(const cell_extension_t *inp) +trn_cell_extension_getlen_fields(const trn_cell_extension_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->fields); } -struct cell_extension_fields_st * -cell_extension_get_fields(cell_extension_t *inp, size_t idx) +struct trn_cell_extension_fields_st * +trn_cell_extension_get_fields(trn_cell_extension_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->fields, idx); } - const struct cell_extension_fields_st * -cell_extension_getconst_fields(const cell_extension_t *inp, size_t idx) + const struct trn_cell_extension_fields_st * +trn_cell_extension_getconst_fields(const trn_cell_extension_t *inp, size_t idx) { - return cell_extension_get_fields((cell_extension_t*)inp, idx); + return trn_cell_extension_get_fields((trn_cell_extension_t*)inp, idx); } int -cell_extension_set_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt) +trn_cell_extension_set_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt) { - cell_extension_fields_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->fields, idx); + trn_cell_extension_fields_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->fields, idx); if (oldval && oldval != elt) - cell_extension_fields_free(oldval); - return cell_extension_set0_fields(inp, idx, elt); + trn_cell_extension_fields_free(oldval); + return trn_cell_extension_set0_fields(inp, idx, elt); } int -cell_extension_set0_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt) +trn_cell_extension_set0_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt) { TRUNNEL_DYNARRAY_SET(&inp->fields, idx, elt); return 0; } int -cell_extension_add_fields(cell_extension_t *inp, struct cell_extension_fields_st * elt) +trn_cell_extension_add_fields(trn_cell_extension_t *inp, struct trn_cell_extension_fields_st * elt) { #if SIZE_MAX >= UINT8_MAX if (inp->fields.n_ == UINT8_MAX) goto trunnel_alloc_failed; #endif - TRUNNEL_DYNARRAY_ADD(struct cell_extension_fields_st *, &inp->fields, elt, {}); + TRUNNEL_DYNARRAY_ADD(struct trn_cell_extension_fields_st *, &inp->fields, elt, {}); return 0; trunnel_alloc_failed: TRUNNEL_SET_ERROR_CODE(inp); return -1; } -struct cell_extension_fields_st * * -cell_extension_getarray_fields(cell_extension_t *inp) +struct trn_cell_extension_fields_st * * +trn_cell_extension_getarray_fields(trn_cell_extension_t *inp) { return inp->fields.elts_; } -const struct cell_extension_fields_st * const * -cell_extension_getconstarray_fields(const cell_extension_t *inp) +const struct trn_cell_extension_fields_st * const * +trn_cell_extension_getconstarray_fields(const trn_cell_extension_t *inp) { - return (const struct cell_extension_fields_st * const *)cell_extension_getarray_fields((cell_extension_t*)inp); + return (const struct trn_cell_extension_fields_st * const *)trn_cell_extension_getarray_fields((trn_cell_extension_t*)inp); } int -cell_extension_setlen_fields(cell_extension_t *inp, size_t newlen) +trn_cell_extension_setlen_fields(trn_cell_extension_t *inp, size_t newlen) { - struct cell_extension_fields_st * *newptr; + struct trn_cell_extension_fields_st * *newptr; #if UINT8_MAX < SIZE_MAX if (newlen > UINT8_MAX) goto trunnel_alloc_failed; #endif newptr = trunnel_dynarray_setlen(&inp->fields.allocated_, &inp->fields.n_, inp->fields.elts_, newlen, - sizeof(inp->fields.elts_[0]), (trunnel_free_fn_t) cell_extension_fields_free, + sizeof(inp->fields.elts_[0]), (trunnel_free_fn_t) trn_cell_extension_fields_free, &inp->trunnel_error_code_); if (newlen != 0 && newptr == NULL) goto trunnel_alloc_failed; @@ -426,7 +426,7 @@ cell_extension_setlen_fields(cell_extension_t *inp, size_t newlen) return -1; } const char * -cell_extension_check(const cell_extension_t *obj) +trn_cell_extension_check(const trn_cell_extension_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -437,7 +437,7 @@ cell_extension_check(const cell_extension_t *obj) unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { - if (NULL != (msg = cell_extension_fields_check(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)))) + if (NULL != (msg = trn_cell_extension_fields_check(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)))) return msg; } } @@ -447,46 +447,46 @@ cell_extension_check(const cell_extension_t *obj) } ssize_t -cell_extension_encoded_len(const cell_extension_t *obj) +trn_cell_extension_encoded_len(const trn_cell_extension_t *obj) { ssize_t result = 0; - if (NULL != cell_extension_check(obj)) + if (NULL != trn_cell_extension_check(obj)) return -1; /* Length of u8 num */ result += 1; - /* Length of struct cell_extension_fields fields[num] */ + /* Length of struct trn_cell_extension_fields fields[num] */ { unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { - result += cell_extension_fields_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); + result += trn_cell_extension_fields_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); } } return result; } int -cell_extension_clear_errors(cell_extension_t *obj) +trn_cell_extension_clear_errors(trn_cell_extension_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -cell_extension_encode(uint8_t *output, const size_t avail, const cell_extension_t *obj) +trn_cell_extension_encode(uint8_t *output, const size_t avail, const trn_cell_extension_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = cell_extension_encoded_len(obj); + const ssize_t encoded_len = trn_cell_extension_encoded_len(obj); #endif - if (NULL != (msg = cell_extension_check(obj))) + if (NULL != (msg = trn_cell_extension_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -500,13 +500,13 @@ cell_extension_encode(uint8_t *output, const size_t avail, const cell_extension_ trunnel_set_uint8(ptr, (obj->num)); written += 1; ptr += 1; - /* Encode struct cell_extension_fields fields[num] */ + /* Encode struct trn_cell_extension_fields fields[num] */ { unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { trunnel_assert(written <= avail); - result = cell_extension_fields_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); + result = trn_cell_extension_fields_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); if (result < 0) goto fail; /* XXXXXXX !*/ written += result; ptr += result; @@ -537,10 +537,11 @@ cell_extension_encode(uint8_t *output, const size_t avail, const cell_extension_ return result; } -/** As cell_extension_parse(), but do not allocate the output object. +/** As trn_cell_extension_parse(), but do not allocate the output + * object. */ static ssize_t -cell_extension_parse_into(cell_extension_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_extension_parse_into(trn_cell_extension_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; @@ -552,18 +553,18 @@ cell_extension_parse_into(cell_extension_t *obj, const uint8_t *input, const siz obj->num = (trunnel_get_uint8(ptr)); remaining -= 1; ptr += 1; - /* Parse struct cell_extension_fields fields[num] */ - TRUNNEL_DYNARRAY_EXPAND(cell_extension_fields_t *, &obj->fields, obj->num, {}); + /* Parse struct trn_cell_extension_fields fields[num] */ + TRUNNEL_DYNARRAY_EXPAND(trn_cell_extension_fields_t *, &obj->fields, obj->num, {}); { - cell_extension_fields_t * elt; + trn_cell_extension_fields_t * elt; unsigned idx; for (idx = 0; idx < obj->num; ++idx) { - result = cell_extension_fields_parse(&elt, ptr, remaining); + result = trn_cell_extension_fields_parse(&elt, ptr, remaining); if (result < 0) goto relay_fail; trunnel_assert((size_t)result <= remaining); remaining -= result; ptr += result; - TRUNNEL_DYNARRAY_ADD(cell_extension_fields_t *, &obj->fields, elt, {cell_extension_fields_free(elt);}); + TRUNNEL_DYNARRAY_ADD(trn_cell_extension_fields_t *, &obj->fields, elt, {trn_cell_extension_fields_free(elt);}); } } trunnel_assert(ptr + remaining == input + len_in); @@ -579,15 +580,15 @@ cell_extension_parse_into(cell_extension_t *obj, const uint8_t *input, const siz } ssize_t -cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in) +trn_cell_extension_parse(trn_cell_extension_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = cell_extension_new(); + *output = trn_cell_extension_new(); if (NULL == *output) return -1; - result = cell_extension_parse_into(*output, input, len_in); + result = trn_cell_extension_parse_into(*output, input, len_in); if (result < 0) { - cell_extension_free(*output); + trn_cell_extension_free(*output); *output = NULL; } return result; diff --git a/src/trunnel/hs/cell_common.h b/src/trunnel/hs/cell_common.h index 8999f7da40..4d98a54cf4 100644 --- a/src/trunnel/hs/cell_common.h +++ b/src/trunnel/hs/cell_common.h @@ -8,191 +8,196 @@ #include <stdint.h> #include "trunnel.h" -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CELL_EXTENSION_FIELDS) -struct cell_extension_fields_st { +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION_FIELDS) +struct trn_cell_extension_fields_st { uint8_t field_type; uint8_t field_len; TRUNNEL_DYNARRAY_HEAD(, uint8_t) field; uint8_t trunnel_error_code_; }; #endif -typedef struct cell_extension_fields_st cell_extension_fields_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CELL_EXTENSION) -struct cell_extension_st { +typedef struct trn_cell_extension_fields_st trn_cell_extension_fields_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION) +struct trn_cell_extension_st { uint8_t num; - TRUNNEL_DYNARRAY_HEAD(, struct cell_extension_fields_st *) fields; + TRUNNEL_DYNARRAY_HEAD(, struct trn_cell_extension_fields_st *) fields; uint8_t trunnel_error_code_; }; #endif -typedef struct cell_extension_st cell_extension_t; -/** Return a newly allocated cell_extension_fields with all elements - * set to zero. - */ -cell_extension_fields_t *cell_extension_fields_new(void); -/** Release all storage held by the cell_extension_fields in 'victim'. - * (Do nothing if 'victim' is NULL.) - */ -void cell_extension_fields_free(cell_extension_fields_t *victim); -/** Try to parse a cell_extension_fields from the buffer in 'input', - * using up to 'len_in' bytes from the input buffer. On success, - * return the number of bytes consumed and set *output to the newly - * allocated cell_extension_fields_t. On failure, return -2 if the - * input appears truncated, and -1 if the input is otherwise invalid. - */ -ssize_t cell_extension_fields_parse(cell_extension_fields_t **output, const uint8_t *input, const size_t len_in); +typedef struct trn_cell_extension_st trn_cell_extension_t; +/** Return a newly allocated trn_cell_extension_fields with all + * elements set to zero. + */ +trn_cell_extension_fields_t *trn_cell_extension_fields_new(void); +/** Release all storage held by the trn_cell_extension_fields in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void trn_cell_extension_fields_free(trn_cell_extension_fields_t *victim); +/** Try to parse a trn_cell_extension_fields from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated trn_cell_extension_fields_t. On failure, return -2 + * if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t trn_cell_extension_fields_parse(trn_cell_extension_fields_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * cell_extension_fields in 'obj'. On failure, return a negative + * trn_cell_extension_fields in 'obj'. On failure, return a negative * value. Note that this value may be an overestimate, and can even be * an underestimate for certain unencodeable objects. */ -ssize_t cell_extension_fields_encoded_len(const cell_extension_fields_t *obj); -/** Try to encode the cell_extension_fields from 'input' into the +ssize_t trn_cell_extension_fields_encoded_len(const trn_cell_extension_fields_t *obj); +/** Try to encode the trn_cell_extension_fields from 'input' into the * buffer at 'output', using up to 'avail' bytes of the output buffer. * On success, return the number of bytes used. On failure, return -2 * if the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t cell_extension_fields_encode(uint8_t *output, size_t avail, const cell_extension_fields_t *input); -/** Check whether the internal state of the cell_extension_fields in - * 'obj' is consistent. Return NULL if it is, and a short message if - * it is not. +ssize_t trn_cell_extension_fields_encode(uint8_t *output, size_t avail, const trn_cell_extension_fields_t *input); +/** Check whether the internal state of the trn_cell_extension_fields + * in 'obj' is consistent. Return NULL if it is, and a short message + * if it is not. */ -const char *cell_extension_fields_check(const cell_extension_fields_t *obj); +const char *trn_cell_extension_fields_check(const trn_cell_extension_fields_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int cell_extension_fields_clear_errors(cell_extension_fields_t *obj); +int trn_cell_extension_fields_clear_errors(trn_cell_extension_fields_t *obj); /** Return the value of the field_type field of the - * cell_extension_fields_t in 'inp' + * trn_cell_extension_fields_t in 'inp' */ -uint8_t cell_extension_fields_get_field_type(const cell_extension_fields_t *inp); +uint8_t trn_cell_extension_fields_get_field_type(const trn_cell_extension_fields_t *inp); /** Set the value of the field_type field of the - * cell_extension_fields_t in 'inp' to 'val'. Return 0 on success; + * trn_cell_extension_fields_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int cell_extension_fields_set_field_type(cell_extension_fields_t *inp, uint8_t val); +int trn_cell_extension_fields_set_field_type(trn_cell_extension_fields_t *inp, uint8_t val); /** Return the value of the field_len field of the - * cell_extension_fields_t in 'inp' + * trn_cell_extension_fields_t in 'inp' */ -uint8_t cell_extension_fields_get_field_len(const cell_extension_fields_t *inp); +uint8_t trn_cell_extension_fields_get_field_len(const trn_cell_extension_fields_t *inp); /** Set the value of the field_len field of the - * cell_extension_fields_t in 'inp' to 'val'. Return 0 on success; + * trn_cell_extension_fields_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int cell_extension_fields_set_field_len(cell_extension_fields_t *inp, uint8_t val); +int trn_cell_extension_fields_set_field_len(trn_cell_extension_fields_t *inp, uint8_t val); /** Return the length of the dynamic array holding the field field of - * the cell_extension_fields_t in 'inp'. + * the trn_cell_extension_fields_t in 'inp'. */ -size_t cell_extension_fields_getlen_field(const cell_extension_fields_t *inp); +size_t trn_cell_extension_fields_getlen_field(const trn_cell_extension_fields_t *inp); /** Return the element at position 'idx' of the dynamic array field - * field of the cell_extension_fields_t in 'inp'. + * field of the trn_cell_extension_fields_t in 'inp'. */ -uint8_t cell_extension_fields_get_field(cell_extension_fields_t *inp, size_t idx); -/** As cell_extension_fields_get_field, but take and return a const - * pointer +uint8_t trn_cell_extension_fields_get_field(trn_cell_extension_fields_t *inp, size_t idx); +/** As trn_cell_extension_fields_get_field, but take and return a + * const pointer */ -uint8_t cell_extension_fields_getconst_field(const cell_extension_fields_t *inp, size_t idx); +uint8_t trn_cell_extension_fields_getconst_field(const trn_cell_extension_fields_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * field of the cell_extension_fields_t in 'inp', so that it will hold - * the value 'elt'. + * field of the trn_cell_extension_fields_t in 'inp', so that it will + * hold the value 'elt'. */ -int cell_extension_fields_set_field(cell_extension_fields_t *inp, size_t idx, uint8_t elt); +int trn_cell_extension_fields_set_field(trn_cell_extension_fields_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field field of the - * cell_extension_fields_t in 'inp'. + * trn_cell_extension_fields_t in 'inp'. */ -int cell_extension_fields_add_field(cell_extension_fields_t *inp, uint8_t elt); +int trn_cell_extension_fields_add_field(trn_cell_extension_fields_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field field of * 'inp'. */ -uint8_t * cell_extension_fields_getarray_field(cell_extension_fields_t *inp); -/** As cell_extension_fields_get_field, but take and return a const - * pointer +uint8_t * trn_cell_extension_fields_getarray_field(trn_cell_extension_fields_t *inp); +/** As trn_cell_extension_fields_get_field, but take and return a + * const pointer */ -const uint8_t * cell_extension_fields_getconstarray_field(const cell_extension_fields_t *inp); +const uint8_t * trn_cell_extension_fields_getconstarray_field(const trn_cell_extension_fields_t *inp); /** Change the length of the variable-length array field field of * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int cell_extension_fields_setlen_field(cell_extension_fields_t *inp, size_t newlen); -/** Return a newly allocated cell_extension with all elements set to - * zero. +int trn_cell_extension_fields_setlen_field(trn_cell_extension_fields_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_extension with all elements set + * to zero. */ -cell_extension_t *cell_extension_new(void); -/** Release all storage held by the cell_extension in 'victim'. (Do - * nothing if 'victim' is NULL.) +trn_cell_extension_t *trn_cell_extension_new(void); +/** Release all storage held by the trn_cell_extension in 'victim'. + * (Do nothing if 'victim' is NULL.) */ -void cell_extension_free(cell_extension_t *victim); -/** Try to parse a cell_extension from the buffer in 'input', using up - * to 'len_in' bytes from the input buffer. On success, return the - * number of bytes consumed and set *output to the newly allocated - * cell_extension_t. On failure, return -2 if the input appears - * truncated, and -1 if the input is otherwise invalid. +void trn_cell_extension_free(trn_cell_extension_t *victim); +/** Try to parse a trn_cell_extension from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated trn_cell_extension_t. On failure, return -2 if the input + * appears truncated, and -1 if the input is otherwise invalid. */ -ssize_t cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_extension_parse(trn_cell_extension_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * cell_extension in 'obj'. On failure, return a negative value. Note - * that this value may be an overestimate, and can even be an + * trn_cell_extension in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an * underestimate for certain unencodeable objects. */ -ssize_t cell_extension_encoded_len(const cell_extension_t *obj); -/** Try to encode the cell_extension from 'input' into the buffer at - * 'output', using up to 'avail' bytes of the output buffer. On +ssize_t trn_cell_extension_encoded_len(const trn_cell_extension_t *obj); +/** Try to encode the trn_cell_extension from 'input' into the buffer + * at 'output', using up to 'avail' bytes of the output buffer. On * success, return the number of bytes used. On failure, return -2 if * the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t cell_extension_encode(uint8_t *output, size_t avail, const cell_extension_t *input); -/** Check whether the internal state of the cell_extension in 'obj' is - * consistent. Return NULL if it is, and a short message if it is not. +ssize_t trn_cell_extension_encode(uint8_t *output, size_t avail, const trn_cell_extension_t *input); +/** Check whether the internal state of the trn_cell_extension in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. */ -const char *cell_extension_check(const cell_extension_t *obj); +const char *trn_cell_extension_check(const trn_cell_extension_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int cell_extension_clear_errors(cell_extension_t *obj); -/** Return the value of the num field of the cell_extension_t in 'inp' +int trn_cell_extension_clear_errors(trn_cell_extension_t *obj); +/** Return the value of the num field of the trn_cell_extension_t in + * 'inp' */ -uint8_t cell_extension_get_num(const cell_extension_t *inp); -/** Set the value of the num field of the cell_extension_t in 'inp' to - * 'val'. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. +uint8_t trn_cell_extension_get_num(const trn_cell_extension_t *inp); +/** Set the value of the num field of the trn_cell_extension_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. */ -int cell_extension_set_num(cell_extension_t *inp, uint8_t val); +int trn_cell_extension_set_num(trn_cell_extension_t *inp, uint8_t val); /** Return the length of the dynamic array holding the fields field of - * the cell_extension_t in 'inp'. + * the trn_cell_extension_t in 'inp'. */ -size_t cell_extension_getlen_fields(const cell_extension_t *inp); +size_t trn_cell_extension_getlen_fields(const trn_cell_extension_t *inp); /** Return the element at position 'idx' of the dynamic array field - * fields of the cell_extension_t in 'inp'. + * fields of the trn_cell_extension_t in 'inp'. */ -struct cell_extension_fields_st * cell_extension_get_fields(cell_extension_t *inp, size_t idx); -/** As cell_extension_get_fields, but take and return a const pointer +struct trn_cell_extension_fields_st * trn_cell_extension_get_fields(trn_cell_extension_t *inp, size_t idx); +/** As trn_cell_extension_get_fields, but take and return a const + * pointer */ - const struct cell_extension_fields_st * cell_extension_getconst_fields(const cell_extension_t *inp, size_t idx); + const struct trn_cell_extension_fields_st * trn_cell_extension_getconst_fields(const trn_cell_extension_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * fields of the cell_extension_t in 'inp', so that it will hold the - * value 'elt'. Free the previous value, if any. + * fields of the trn_cell_extension_t in 'inp', so that it will hold + * the value 'elt'. Free the previous value, if any. */ -int cell_extension_set_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt); -/** As cell_extension_set_fields, but does not free the previous +int trn_cell_extension_set_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt); +/** As trn_cell_extension_set_fields, but does not free the previous * value. */ -int cell_extension_set0_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt); +int trn_cell_extension_set0_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt); /** Append a new element 'elt' to the dynamic array field fields of - * the cell_extension_t in 'inp'. + * the trn_cell_extension_t in 'inp'. */ -int cell_extension_add_fields(cell_extension_t *inp, struct cell_extension_fields_st * elt); +int trn_cell_extension_add_fields(trn_cell_extension_t *inp, struct trn_cell_extension_fields_st * elt); /** Return a pointer to the variable-length array field fields of * 'inp'. */ -struct cell_extension_fields_st * * cell_extension_getarray_fields(cell_extension_t *inp); -/** As cell_extension_get_fields, but take and return a const pointer +struct trn_cell_extension_fields_st * * trn_cell_extension_getarray_fields(trn_cell_extension_t *inp); +/** As trn_cell_extension_get_fields, but take and return a const + * pointer */ -const struct cell_extension_fields_st * const * cell_extension_getconstarray_fields(const cell_extension_t *inp); +const struct trn_cell_extension_fields_st * const * trn_cell_extension_getconstarray_fields(const trn_cell_extension_t *inp); /** Change the length of the variable-length array field fields of * 'inp' to 'newlen'.Fill extra elements with NULL; free removed * elements. Return 0 on success; return -1 and set the error code on * 'inp' on failure. */ -int cell_extension_setlen_fields(cell_extension_t *inp, size_t newlen); +int trn_cell_extension_setlen_fields(trn_cell_extension_t *inp, size_t newlen); #endif diff --git a/src/trunnel/hs/cell_common.trunnel b/src/trunnel/hs/cell_common.trunnel index 1bbec5a1fe..1aa6999de7 100644 --- a/src/trunnel/hs/cell_common.trunnel +++ b/src/trunnel/hs/cell_common.trunnel @@ -1,12 +1,12 @@ /* This file contains common data structure that cells use. */ -struct cell_extension_fields { +struct trn_cell_extension_fields { u8 field_type; u8 field_len; u8 field[field_len]; }; -struct cell_extension { +struct trn_cell_extension { u8 num; - struct cell_extension_fields fields[num]; + struct trn_cell_extension_fields fields[num]; }; diff --git a/src/trunnel/hs/cell_establish_intro.c b/src/trunnel/hs/cell_establish_intro.c index 633bd7c214..22e198c369 100644 --- a/src/trunnel/hs/cell_establish_intro.c +++ b/src/trunnel/hs/cell_establish_intro.c @@ -28,18 +28,18 @@ int cellestablishintro_deadcode_dummy__ = 0; } \ } while (0) -typedef struct cell_extension_st cell_extension_t; -cell_extension_t *cell_extension_new(void); -void cell_extension_free(cell_extension_t *victim); -ssize_t cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in); -ssize_t cell_extension_encoded_len(const cell_extension_t *obj); -ssize_t cell_extension_encode(uint8_t *output, size_t avail, const cell_extension_t *input); -const char *cell_extension_check(const cell_extension_t *obj); -int cell_extension_clear_errors(cell_extension_t *obj); -hs_cell_establish_intro_t * -hs_cell_establish_intro_new(void) -{ - hs_cell_establish_intro_t *val = trunnel_calloc(1, sizeof(hs_cell_establish_intro_t)); +typedef struct trn_cell_extension_st trn_cell_extension_t; +trn_cell_extension_t *trn_cell_extension_new(void); +void trn_cell_extension_free(trn_cell_extension_t *victim); +ssize_t trn_cell_extension_parse(trn_cell_extension_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_extension_encoded_len(const trn_cell_extension_t *obj); +ssize_t trn_cell_extension_encode(uint8_t *output, size_t avail, const trn_cell_extension_t *input); +const char *trn_cell_extension_check(const trn_cell_extension_t *obj); +int trn_cell_extension_clear_errors(trn_cell_extension_t *obj); +trn_cell_establish_intro_t * +trn_cell_establish_intro_new(void) +{ + trn_cell_establish_intro_t *val = trunnel_calloc(1, sizeof(trn_cell_establish_intro_t)); if (NULL == val) return NULL; return val; @@ -48,39 +48,39 @@ hs_cell_establish_intro_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -hs_cell_establish_intro_clear(hs_cell_establish_intro_t *obj) +trn_cell_establish_intro_clear(trn_cell_establish_intro_t *obj) { (void) obj; TRUNNEL_DYNARRAY_WIPE(&obj->auth_key); TRUNNEL_DYNARRAY_CLEAR(&obj->auth_key); - cell_extension_free(obj->extensions); + trn_cell_extension_free(obj->extensions); obj->extensions = NULL; TRUNNEL_DYNARRAY_WIPE(&obj->sig); TRUNNEL_DYNARRAY_CLEAR(&obj->sig); } void -hs_cell_establish_intro_free(hs_cell_establish_intro_t *obj) +trn_cell_establish_intro_free(trn_cell_establish_intro_t *obj) { if (obj == NULL) return; - hs_cell_establish_intro_clear(obj); - trunnel_memwipe(obj, sizeof(hs_cell_establish_intro_t)); + trn_cell_establish_intro_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_establish_intro_t)); trunnel_free_(obj); } const uint8_t * -hs_cell_establish_intro_get_start_cell(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_get_start_cell(const trn_cell_establish_intro_t *inp) { return inp->start_cell; } uint8_t -hs_cell_establish_intro_get_auth_key_type(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_get_auth_key_type(const trn_cell_establish_intro_t *inp) { return inp->auth_key_type; } int -hs_cell_establish_intro_set_auth_key_type(hs_cell_establish_intro_t *inp, uint8_t val) +trn_cell_establish_intro_set_auth_key_type(trn_cell_establish_intro_t *inp, uint8_t val) { if (! ((val == 0 || val == 1 || val == 2))) { TRUNNEL_SET_ERROR_CODE(inp); @@ -90,41 +90,41 @@ hs_cell_establish_intro_set_auth_key_type(hs_cell_establish_intro_t *inp, uint8_ return 0; } uint16_t -hs_cell_establish_intro_get_auth_key_len(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_get_auth_key_len(const trn_cell_establish_intro_t *inp) { return inp->auth_key_len; } int -hs_cell_establish_intro_set_auth_key_len(hs_cell_establish_intro_t *inp, uint16_t val) +trn_cell_establish_intro_set_auth_key_len(trn_cell_establish_intro_t *inp, uint16_t val) { inp->auth_key_len = val; return 0; } size_t -hs_cell_establish_intro_getlen_auth_key(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getlen_auth_key(const trn_cell_establish_intro_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->auth_key); } uint8_t -hs_cell_establish_intro_get_auth_key(hs_cell_establish_intro_t *inp, size_t idx) +trn_cell_establish_intro_get_auth_key(trn_cell_establish_intro_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->auth_key, idx); } uint8_t -hs_cell_establish_intro_getconst_auth_key(const hs_cell_establish_intro_t *inp, size_t idx) +trn_cell_establish_intro_getconst_auth_key(const trn_cell_establish_intro_t *inp, size_t idx) { - return hs_cell_establish_intro_get_auth_key((hs_cell_establish_intro_t*)inp, idx); + return trn_cell_establish_intro_get_auth_key((trn_cell_establish_intro_t*)inp, idx); } int -hs_cell_establish_intro_set_auth_key(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt) +trn_cell_establish_intro_set_auth_key(trn_cell_establish_intro_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->auth_key, idx, elt); return 0; } int -hs_cell_establish_intro_add_auth_key(hs_cell_establish_intro_t *inp, uint8_t elt) +trn_cell_establish_intro_add_auth_key(trn_cell_establish_intro_t *inp, uint8_t elt) { #if SIZE_MAX >= UINT16_MAX if (inp->auth_key.n_ == UINT16_MAX) @@ -138,17 +138,17 @@ hs_cell_establish_intro_add_auth_key(hs_cell_establish_intro_t *inp, uint8_t elt } uint8_t * -hs_cell_establish_intro_getarray_auth_key(hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getarray_auth_key(trn_cell_establish_intro_t *inp) { return inp->auth_key.elts_; } const uint8_t * -hs_cell_establish_intro_getconstarray_auth_key(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getconstarray_auth_key(const trn_cell_establish_intro_t *inp) { - return (const uint8_t *)hs_cell_establish_intro_getarray_auth_key((hs_cell_establish_intro_t*)inp); + return (const uint8_t *)trn_cell_establish_intro_getarray_auth_key((trn_cell_establish_intro_t*)inp); } int -hs_cell_establish_intro_setlen_auth_key(hs_cell_establish_intro_t *inp, size_t newlen) +trn_cell_establish_intro_setlen_auth_key(trn_cell_establish_intro_t *inp, size_t newlen) { uint8_t *newptr; #if UINT16_MAX < SIZE_MAX @@ -167,54 +167,54 @@ hs_cell_establish_intro_setlen_auth_key(hs_cell_establish_intro_t *inp, size_t n TRUNNEL_SET_ERROR_CODE(inp); return -1; } -struct cell_extension_st * -hs_cell_establish_intro_get_extensions(hs_cell_establish_intro_t *inp) +struct trn_cell_extension_st * +trn_cell_establish_intro_get_extensions(trn_cell_establish_intro_t *inp) { return inp->extensions; } -const struct cell_extension_st * -hs_cell_establish_intro_getconst_extensions(const hs_cell_establish_intro_t *inp) +const struct trn_cell_extension_st * +trn_cell_establish_intro_getconst_extensions(const trn_cell_establish_intro_t *inp) { - return hs_cell_establish_intro_get_extensions((hs_cell_establish_intro_t*) inp); + return trn_cell_establish_intro_get_extensions((trn_cell_establish_intro_t*) inp); } int -hs_cell_establish_intro_set_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val) +trn_cell_establish_intro_set_extensions(trn_cell_establish_intro_t *inp, struct trn_cell_extension_st *val) { if (inp->extensions && inp->extensions != val) - cell_extension_free(inp->extensions); - return hs_cell_establish_intro_set0_extensions(inp, val); + trn_cell_extension_free(inp->extensions); + return trn_cell_establish_intro_set0_extensions(inp, val); } int -hs_cell_establish_intro_set0_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val) +trn_cell_establish_intro_set0_extensions(trn_cell_establish_intro_t *inp, struct trn_cell_extension_st *val) { inp->extensions = val; return 0; } const uint8_t * -hs_cell_establish_intro_get_end_mac_fields(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_get_end_mac_fields(const trn_cell_establish_intro_t *inp) { return inp->end_mac_fields; } size_t -hs_cell_establish_intro_getlen_handshake_mac(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getlen_handshake_mac(const trn_cell_establish_intro_t *inp) { (void)inp; return TRUNNEL_SHA3_256_LEN; } uint8_t -hs_cell_establish_intro_get_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx) +trn_cell_establish_intro_get_handshake_mac(trn_cell_establish_intro_t *inp, size_t idx) { trunnel_assert(idx < TRUNNEL_SHA3_256_LEN); return inp->handshake_mac[idx]; } uint8_t -hs_cell_establish_intro_getconst_handshake_mac(const hs_cell_establish_intro_t *inp, size_t idx) +trn_cell_establish_intro_getconst_handshake_mac(const trn_cell_establish_intro_t *inp, size_t idx) { - return hs_cell_establish_intro_get_handshake_mac((hs_cell_establish_intro_t*)inp, idx); + return trn_cell_establish_intro_get_handshake_mac((trn_cell_establish_intro_t*)inp, idx); } int -hs_cell_establish_intro_set_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt) +trn_cell_establish_intro_set_handshake_mac(trn_cell_establish_intro_t *inp, size_t idx, uint8_t elt) { trunnel_assert(idx < TRUNNEL_SHA3_256_LEN); inp->handshake_mac[idx] = elt; @@ -222,56 +222,56 @@ hs_cell_establish_intro_set_handshake_mac(hs_cell_establish_intro_t *inp, size_t } uint8_t * -hs_cell_establish_intro_getarray_handshake_mac(hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getarray_handshake_mac(trn_cell_establish_intro_t *inp) { return inp->handshake_mac; } const uint8_t * -hs_cell_establish_intro_getconstarray_handshake_mac(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getconstarray_handshake_mac(const trn_cell_establish_intro_t *inp) { - return (const uint8_t *)hs_cell_establish_intro_getarray_handshake_mac((hs_cell_establish_intro_t*)inp); + return (const uint8_t *)trn_cell_establish_intro_getarray_handshake_mac((trn_cell_establish_intro_t*)inp); } const uint8_t * -hs_cell_establish_intro_get_end_sig_fields(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_get_end_sig_fields(const trn_cell_establish_intro_t *inp) { return inp->end_sig_fields; } uint16_t -hs_cell_establish_intro_get_sig_len(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_get_sig_len(const trn_cell_establish_intro_t *inp) { return inp->sig_len; } int -hs_cell_establish_intro_set_sig_len(hs_cell_establish_intro_t *inp, uint16_t val) +trn_cell_establish_intro_set_sig_len(trn_cell_establish_intro_t *inp, uint16_t val) { inp->sig_len = val; return 0; } size_t -hs_cell_establish_intro_getlen_sig(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getlen_sig(const trn_cell_establish_intro_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->sig); } uint8_t -hs_cell_establish_intro_get_sig(hs_cell_establish_intro_t *inp, size_t idx) +trn_cell_establish_intro_get_sig(trn_cell_establish_intro_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->sig, idx); } uint8_t -hs_cell_establish_intro_getconst_sig(const hs_cell_establish_intro_t *inp, size_t idx) +trn_cell_establish_intro_getconst_sig(const trn_cell_establish_intro_t *inp, size_t idx) { - return hs_cell_establish_intro_get_sig((hs_cell_establish_intro_t*)inp, idx); + return trn_cell_establish_intro_get_sig((trn_cell_establish_intro_t*)inp, idx); } int -hs_cell_establish_intro_set_sig(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt) +trn_cell_establish_intro_set_sig(trn_cell_establish_intro_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->sig, idx, elt); return 0; } int -hs_cell_establish_intro_add_sig(hs_cell_establish_intro_t *inp, uint8_t elt) +trn_cell_establish_intro_add_sig(trn_cell_establish_intro_t *inp, uint8_t elt) { #if SIZE_MAX >= UINT16_MAX if (inp->sig.n_ == UINT16_MAX) @@ -285,17 +285,17 @@ hs_cell_establish_intro_add_sig(hs_cell_establish_intro_t *inp, uint8_t elt) } uint8_t * -hs_cell_establish_intro_getarray_sig(hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getarray_sig(trn_cell_establish_intro_t *inp) { return inp->sig.elts_; } const uint8_t * -hs_cell_establish_intro_getconstarray_sig(const hs_cell_establish_intro_t *inp) +trn_cell_establish_intro_getconstarray_sig(const trn_cell_establish_intro_t *inp) { - return (const uint8_t *)hs_cell_establish_intro_getarray_sig((hs_cell_establish_intro_t*)inp); + return (const uint8_t *)trn_cell_establish_intro_getarray_sig((trn_cell_establish_intro_t*)inp); } int -hs_cell_establish_intro_setlen_sig(hs_cell_establish_intro_t *inp, size_t newlen) +trn_cell_establish_intro_setlen_sig(trn_cell_establish_intro_t *inp, size_t newlen) { uint8_t *newptr; #if UINT16_MAX < SIZE_MAX @@ -315,7 +315,7 @@ hs_cell_establish_intro_setlen_sig(hs_cell_establish_intro_t *inp, size_t newlen return -1; } const char * -hs_cell_establish_intro_check(const hs_cell_establish_intro_t *obj) +trn_cell_establish_intro_check(const trn_cell_establish_intro_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -327,7 +327,7 @@ hs_cell_establish_intro_check(const hs_cell_establish_intro_t *obj) return "Length mismatch for auth_key"; { const char *msg; - if (NULL != (msg = cell_extension_check(obj->extensions))) + if (NULL != (msg = trn_cell_extension_check(obj->extensions))) return msg; } if (TRUNNEL_DYNARRAY_LEN(&obj->sig) != obj->sig_len) @@ -336,11 +336,11 @@ hs_cell_establish_intro_check(const hs_cell_establish_intro_t *obj) } ssize_t -hs_cell_establish_intro_encoded_len(const hs_cell_establish_intro_t *obj) +trn_cell_establish_intro_encoded_len(const trn_cell_establish_intro_t *obj) { ssize_t result = 0; - if (NULL != hs_cell_establish_intro_check(obj)) + if (NULL != trn_cell_establish_intro_check(obj)) return -1; @@ -353,8 +353,8 @@ hs_cell_establish_intro_encoded_len(const hs_cell_establish_intro_t *obj) /* Length of u8 auth_key[auth_key_len] */ result += TRUNNEL_DYNARRAY_LEN(&obj->auth_key); - /* Length of struct cell_extension extensions */ - result += cell_extension_encoded_len(obj->extensions); + /* Length of struct trn_cell_extension extensions */ + result += trn_cell_extension_encoded_len(obj->extensions); /* Length of u8 handshake_mac[TRUNNEL_SHA3_256_LEN] */ result += TRUNNEL_SHA3_256_LEN; @@ -367,24 +367,24 @@ hs_cell_establish_intro_encoded_len(const hs_cell_establish_intro_t *obj) return result; } int -hs_cell_establish_intro_clear_errors(hs_cell_establish_intro_t *obj) +trn_cell_establish_intro_clear_errors(trn_cell_establish_intro_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -hs_cell_establish_intro_encode(uint8_t *output, const size_t avail, const hs_cell_establish_intro_t *obj) +trn_cell_establish_intro_encode(uint8_t *output, const size_t avail, const trn_cell_establish_intro_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = hs_cell_establish_intro_encoded_len(obj); + const ssize_t encoded_len = trn_cell_establish_intro_encoded_len(obj); #endif - if (NULL != (msg = hs_cell_establish_intro_check(obj))) + if (NULL != (msg = trn_cell_establish_intro_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -417,9 +417,9 @@ hs_cell_establish_intro_encode(uint8_t *output, const size_t avail, const hs_cel written += elt_len; ptr += elt_len; } - /* Encode struct cell_extension extensions */ + /* Encode struct trn_cell_extension extensions */ trunnel_assert(written <= avail); - result = cell_extension_encode(ptr, avail - written, obj->extensions); + result = trn_cell_extension_encode(ptr, avail - written, obj->extensions); if (result < 0) goto fail; /* XXXXXXX !*/ written += result; ptr += result; @@ -474,11 +474,11 @@ hs_cell_establish_intro_encode(uint8_t *output, const size_t avail, const hs_cel return result; } -/** As hs_cell_establish_intro_parse(), but do not allocate the output - * object. +/** As trn_cell_establish_intro_parse(), but do not allocate the + * output object. */ static ssize_t -hs_cell_establish_intro_parse_into(hs_cell_establish_intro_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_establish_intro_parse_into(trn_cell_establish_intro_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; @@ -506,8 +506,8 @@ hs_cell_establish_intro_parse_into(hs_cell_establish_intro_t *obj, const uint8_t memcpy(obj->auth_key.elts_, ptr, obj->auth_key_len); ptr += obj->auth_key_len; remaining -= obj->auth_key_len; - /* Parse struct cell_extension extensions */ - result = cell_extension_parse(&obj->extensions, ptr, remaining); + /* Parse struct trn_cell_extension extensions */ + result = trn_cell_extension_parse(&obj->extensions, ptr, remaining); if (result < 0) goto relay_fail; trunnel_assert((size_t)result <= remaining); @@ -548,23 +548,23 @@ hs_cell_establish_intro_parse_into(hs_cell_establish_intro_t *obj, const uint8_t } ssize_t -hs_cell_establish_intro_parse(hs_cell_establish_intro_t **output, const uint8_t *input, const size_t len_in) +trn_cell_establish_intro_parse(trn_cell_establish_intro_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = hs_cell_establish_intro_new(); + *output = trn_cell_establish_intro_new(); if (NULL == *output) return -1; - result = hs_cell_establish_intro_parse_into(*output, input, len_in); + result = trn_cell_establish_intro_parse_into(*output, input, len_in); if (result < 0) { - hs_cell_establish_intro_free(*output); + trn_cell_establish_intro_free(*output); *output = NULL; } return result; } -hs_cell_intro_established_t * -hs_cell_intro_established_new(void) +trn_cell_intro_established_t * +trn_cell_intro_established_new(void) { - hs_cell_intro_established_t *val = trunnel_calloc(1, sizeof(hs_cell_intro_established_t)); + trn_cell_intro_established_t *val = trunnel_calloc(1, sizeof(trn_cell_intro_established_t)); if (NULL == val) return NULL; return val; @@ -573,48 +573,48 @@ hs_cell_intro_established_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -hs_cell_intro_established_clear(hs_cell_intro_established_t *obj) +trn_cell_intro_established_clear(trn_cell_intro_established_t *obj) { (void) obj; - cell_extension_free(obj->extensions); + trn_cell_extension_free(obj->extensions); obj->extensions = NULL; } void -hs_cell_intro_established_free(hs_cell_intro_established_t *obj) +trn_cell_intro_established_free(trn_cell_intro_established_t *obj) { if (obj == NULL) return; - hs_cell_intro_established_clear(obj); - trunnel_memwipe(obj, sizeof(hs_cell_intro_established_t)); + trn_cell_intro_established_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_intro_established_t)); trunnel_free_(obj); } -struct cell_extension_st * -hs_cell_intro_established_get_extensions(hs_cell_intro_established_t *inp) +struct trn_cell_extension_st * +trn_cell_intro_established_get_extensions(trn_cell_intro_established_t *inp) { return inp->extensions; } -const struct cell_extension_st * -hs_cell_intro_established_getconst_extensions(const hs_cell_intro_established_t *inp) +const struct trn_cell_extension_st * +trn_cell_intro_established_getconst_extensions(const trn_cell_intro_established_t *inp) { - return hs_cell_intro_established_get_extensions((hs_cell_intro_established_t*) inp); + return trn_cell_intro_established_get_extensions((trn_cell_intro_established_t*) inp); } int -hs_cell_intro_established_set_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val) +trn_cell_intro_established_set_extensions(trn_cell_intro_established_t *inp, struct trn_cell_extension_st *val) { if (inp->extensions && inp->extensions != val) - cell_extension_free(inp->extensions); - return hs_cell_intro_established_set0_extensions(inp, val); + trn_cell_extension_free(inp->extensions); + return trn_cell_intro_established_set0_extensions(inp, val); } int -hs_cell_intro_established_set0_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val) +trn_cell_intro_established_set0_extensions(trn_cell_intro_established_t *inp, struct trn_cell_extension_st *val) { inp->extensions = val; return 0; } const char * -hs_cell_intro_established_check(const hs_cell_intro_established_t *obj) +trn_cell_intro_established_check(const trn_cell_intro_established_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -622,53 +622,53 @@ hs_cell_intro_established_check(const hs_cell_intro_established_t *obj) return "A set function failed on this object"; { const char *msg; - if (NULL != (msg = cell_extension_check(obj->extensions))) + if (NULL != (msg = trn_cell_extension_check(obj->extensions))) return msg; } return NULL; } ssize_t -hs_cell_intro_established_encoded_len(const hs_cell_intro_established_t *obj) +trn_cell_intro_established_encoded_len(const trn_cell_intro_established_t *obj) { ssize_t result = 0; - if (NULL != hs_cell_intro_established_check(obj)) + if (NULL != trn_cell_intro_established_check(obj)) return -1; - /* Length of struct cell_extension extensions */ - result += cell_extension_encoded_len(obj->extensions); + /* Length of struct trn_cell_extension extensions */ + result += trn_cell_extension_encoded_len(obj->extensions); return result; } int -hs_cell_intro_established_clear_errors(hs_cell_intro_established_t *obj) +trn_cell_intro_established_clear_errors(trn_cell_intro_established_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -hs_cell_intro_established_encode(uint8_t *output, const size_t avail, const hs_cell_intro_established_t *obj) +trn_cell_intro_established_encode(uint8_t *output, const size_t avail, const trn_cell_intro_established_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = hs_cell_intro_established_encoded_len(obj); + const ssize_t encoded_len = trn_cell_intro_established_encoded_len(obj); #endif - if (NULL != (msg = hs_cell_intro_established_check(obj))) + if (NULL != (msg = trn_cell_intro_established_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN trunnel_assert(encoded_len >= 0); #endif - /* Encode struct cell_extension extensions */ + /* Encode struct trn_cell_extension extensions */ trunnel_assert(written <= avail); - result = cell_extension_encode(ptr, avail - written, obj->extensions); + result = trn_cell_extension_encode(ptr, avail - written, obj->extensions); if (result < 0) goto fail; /* XXXXXXX !*/ written += result; ptr += result; @@ -694,19 +694,19 @@ hs_cell_intro_established_encode(uint8_t *output, const size_t avail, const hs_c return result; } -/** As hs_cell_intro_established_parse(), but do not allocate the +/** As trn_cell_intro_established_parse(), but do not allocate the * output object. */ static ssize_t -hs_cell_intro_established_parse_into(hs_cell_intro_established_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_intro_established_parse_into(trn_cell_intro_established_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; ssize_t result = 0; (void)result; - /* Parse struct cell_extension extensions */ - result = cell_extension_parse(&obj->extensions, ptr, remaining); + /* Parse struct trn_cell_extension extensions */ + result = trn_cell_extension_parse(&obj->extensions, ptr, remaining); if (result < 0) goto relay_fail; trunnel_assert((size_t)result <= remaining); @@ -720,15 +720,15 @@ hs_cell_intro_established_parse_into(hs_cell_intro_established_t *obj, const uin } ssize_t -hs_cell_intro_established_parse(hs_cell_intro_established_t **output, const uint8_t *input, const size_t len_in) +trn_cell_intro_established_parse(trn_cell_intro_established_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = hs_cell_intro_established_new(); + *output = trn_cell_intro_established_new(); if (NULL == *output) return -1; - result = hs_cell_intro_established_parse_into(*output, input, len_in); + result = trn_cell_intro_established_parse_into(*output, input, len_in); if (result < 0) { - hs_cell_intro_established_free(*output); + trn_cell_intro_established_free(*output); *output = NULL; } return result; diff --git a/src/trunnel/hs/cell_establish_intro.h b/src/trunnel/hs/cell_establish_intro.h index 725d47cd85..2f0d893659 100644 --- a/src/trunnel/hs/cell_establish_intro.h +++ b/src/trunnel/hs/cell_establish_intro.h @@ -8,15 +8,15 @@ #include <stdint.h> #include "trunnel.h" -struct cell_extension_st; +struct trn_cell_extension_st; #define TRUNNEL_SHA3_256_LEN 32 -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_ESTABLISH_INTRO) -struct hs_cell_establish_intro_st { +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_ESTABLISH_INTRO) +struct trn_cell_establish_intro_st { const uint8_t *start_cell; uint8_t auth_key_type; uint16_t auth_key_len; TRUNNEL_DYNARRAY_HEAD(, uint8_t) auth_key; - struct cell_extension_st *extensions; + struct trn_cell_extension_st *extensions; const uint8_t *end_mac_fields; uint8_t handshake_mac[TRUNNEL_SHA3_256_LEN]; const uint8_t *end_sig_fields; @@ -25,251 +25,252 @@ struct hs_cell_establish_intro_st { uint8_t trunnel_error_code_; }; #endif -typedef struct hs_cell_establish_intro_st hs_cell_establish_intro_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRO_ESTABLISHED) -struct hs_cell_intro_established_st { - struct cell_extension_st *extensions; +typedef struct trn_cell_establish_intro_st trn_cell_establish_intro_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_INTRO_ESTABLISHED) +struct trn_cell_intro_established_st { + struct trn_cell_extension_st *extensions; uint8_t trunnel_error_code_; }; #endif -typedef struct hs_cell_intro_established_st hs_cell_intro_established_t; -/** Return a newly allocated hs_cell_establish_intro with all elements - * set to zero. +typedef struct trn_cell_intro_established_st trn_cell_intro_established_t; +/** Return a newly allocated trn_cell_establish_intro with all + * elements set to zero. */ -hs_cell_establish_intro_t *hs_cell_establish_intro_new(void); -/** Release all storage held by the hs_cell_establish_intro in +trn_cell_establish_intro_t *trn_cell_establish_intro_new(void); +/** Release all storage held by the trn_cell_establish_intro in * 'victim'. (Do nothing if 'victim' is NULL.) */ -void hs_cell_establish_intro_free(hs_cell_establish_intro_t *victim); -/** Try to parse a hs_cell_establish_intro from the buffer in 'input', - * using up to 'len_in' bytes from the input buffer. On success, - * return the number of bytes consumed and set *output to the newly - * allocated hs_cell_establish_intro_t. On failure, return -2 if the - * input appears truncated, and -1 if the input is otherwise invalid. +void trn_cell_establish_intro_free(trn_cell_establish_intro_t *victim); +/** Try to parse a trn_cell_establish_intro from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated trn_cell_establish_intro_t. On failure, return -2 + * if the input appears truncated, and -1 if the input is otherwise + * invalid. */ -ssize_t hs_cell_establish_intro_parse(hs_cell_establish_intro_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_establish_intro_parse(trn_cell_establish_intro_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * hs_cell_establish_intro in 'obj'. On failure, return a negative + * trn_cell_establish_intro in 'obj'. On failure, return a negative * value. Note that this value may be an overestimate, and can even be * an underestimate for certain unencodeable objects. */ -ssize_t hs_cell_establish_intro_encoded_len(const hs_cell_establish_intro_t *obj); -/** Try to encode the hs_cell_establish_intro from 'input' into the +ssize_t trn_cell_establish_intro_encoded_len(const trn_cell_establish_intro_t *obj); +/** Try to encode the trn_cell_establish_intro from 'input' into the * buffer at 'output', using up to 'avail' bytes of the output buffer. * On success, return the number of bytes used. On failure, return -2 * if the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t hs_cell_establish_intro_encode(uint8_t *output, size_t avail, const hs_cell_establish_intro_t *input); -/** Check whether the internal state of the hs_cell_establish_intro in - * 'obj' is consistent. Return NULL if it is, and a short message if - * it is not. +ssize_t trn_cell_establish_intro_encode(uint8_t *output, size_t avail, const trn_cell_establish_intro_t *input); +/** Check whether the internal state of the trn_cell_establish_intro + * in 'obj' is consistent. Return NULL if it is, and a short message + * if it is not. */ -const char *hs_cell_establish_intro_check(const hs_cell_establish_intro_t *obj); +const char *trn_cell_establish_intro_check(const trn_cell_establish_intro_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int hs_cell_establish_intro_clear_errors(hs_cell_establish_intro_t *obj); +int trn_cell_establish_intro_clear_errors(trn_cell_establish_intro_t *obj); /** Return the position for start_cell when we parsed this object */ -const uint8_t * hs_cell_establish_intro_get_start_cell(const hs_cell_establish_intro_t *inp); +const uint8_t * trn_cell_establish_intro_get_start_cell(const trn_cell_establish_intro_t *inp); /** Return the value of the auth_key_type field of the - * hs_cell_establish_intro_t in 'inp' + * trn_cell_establish_intro_t in 'inp' */ -uint8_t hs_cell_establish_intro_get_auth_key_type(const hs_cell_establish_intro_t *inp); +uint8_t trn_cell_establish_intro_get_auth_key_type(const trn_cell_establish_intro_t *inp); /** Set the value of the auth_key_type field of the - * hs_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success; + * trn_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_establish_intro_set_auth_key_type(hs_cell_establish_intro_t *inp, uint8_t val); +int trn_cell_establish_intro_set_auth_key_type(trn_cell_establish_intro_t *inp, uint8_t val); /** Return the value of the auth_key_len field of the - * hs_cell_establish_intro_t in 'inp' + * trn_cell_establish_intro_t in 'inp' */ -uint16_t hs_cell_establish_intro_get_auth_key_len(const hs_cell_establish_intro_t *inp); +uint16_t trn_cell_establish_intro_get_auth_key_len(const trn_cell_establish_intro_t *inp); /** Set the value of the auth_key_len field of the - * hs_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success; + * trn_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_establish_intro_set_auth_key_len(hs_cell_establish_intro_t *inp, uint16_t val); +int trn_cell_establish_intro_set_auth_key_len(trn_cell_establish_intro_t *inp, uint16_t val); /** Return the length of the dynamic array holding the auth_key field - * of the hs_cell_establish_intro_t in 'inp'. + * of the trn_cell_establish_intro_t in 'inp'. */ -size_t hs_cell_establish_intro_getlen_auth_key(const hs_cell_establish_intro_t *inp); +size_t trn_cell_establish_intro_getlen_auth_key(const trn_cell_establish_intro_t *inp); /** Return the element at position 'idx' of the dynamic array field - * auth_key of the hs_cell_establish_intro_t in 'inp'. + * auth_key of the trn_cell_establish_intro_t in 'inp'. */ -uint8_t hs_cell_establish_intro_get_auth_key(hs_cell_establish_intro_t *inp, size_t idx); -/** As hs_cell_establish_intro_get_auth_key, but take and return a +uint8_t trn_cell_establish_intro_get_auth_key(trn_cell_establish_intro_t *inp, size_t idx); +/** As trn_cell_establish_intro_get_auth_key, but take and return a * const pointer */ -uint8_t hs_cell_establish_intro_getconst_auth_key(const hs_cell_establish_intro_t *inp, size_t idx); +uint8_t trn_cell_establish_intro_getconst_auth_key(const trn_cell_establish_intro_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * auth_key of the hs_cell_establish_intro_t in 'inp', so that it will - * hold the value 'elt'. + * auth_key of the trn_cell_establish_intro_t in 'inp', so that it + * will hold the value 'elt'. */ -int hs_cell_establish_intro_set_auth_key(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt); +int trn_cell_establish_intro_set_auth_key(trn_cell_establish_intro_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field auth_key of - * the hs_cell_establish_intro_t in 'inp'. + * the trn_cell_establish_intro_t in 'inp'. */ -int hs_cell_establish_intro_add_auth_key(hs_cell_establish_intro_t *inp, uint8_t elt); +int trn_cell_establish_intro_add_auth_key(trn_cell_establish_intro_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field auth_key of * 'inp'. */ -uint8_t * hs_cell_establish_intro_getarray_auth_key(hs_cell_establish_intro_t *inp); -/** As hs_cell_establish_intro_get_auth_key, but take and return a +uint8_t * trn_cell_establish_intro_getarray_auth_key(trn_cell_establish_intro_t *inp); +/** As trn_cell_establish_intro_get_auth_key, but take and return a * const pointer */ -const uint8_t * hs_cell_establish_intro_getconstarray_auth_key(const hs_cell_establish_intro_t *inp); +const uint8_t * trn_cell_establish_intro_getconstarray_auth_key(const trn_cell_establish_intro_t *inp); /** Change the length of the variable-length array field auth_key of * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_establish_intro_setlen_auth_key(hs_cell_establish_intro_t *inp, size_t newlen); +int trn_cell_establish_intro_setlen_auth_key(trn_cell_establish_intro_t *inp, size_t newlen); /** Return the value of the extensions field of the - * hs_cell_establish_intro_t in 'inp' + * trn_cell_establish_intro_t in 'inp' */ -struct cell_extension_st * hs_cell_establish_intro_get_extensions(hs_cell_establish_intro_t *inp); -/** As hs_cell_establish_intro_get_extensions, but take and return a +struct trn_cell_extension_st * trn_cell_establish_intro_get_extensions(trn_cell_establish_intro_t *inp); +/** As trn_cell_establish_intro_get_extensions, but take and return a * const pointer */ -const struct cell_extension_st * hs_cell_establish_intro_getconst_extensions(const hs_cell_establish_intro_t *inp); +const struct trn_cell_extension_st * trn_cell_establish_intro_getconst_extensions(const trn_cell_establish_intro_t *inp); /** Set the value of the extensions field of the - * hs_cell_establish_intro_t in 'inp' to 'val'. Free the old value if + * trn_cell_establish_intro_t in 'inp' to 'val'. Free the old value if * any. Steals the referenceto 'val'.Return 0 on success; return -1 * and set the error code on 'inp' on failure. */ -int hs_cell_establish_intro_set_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val); -/** As hs_cell_establish_intro_set_extensions, but does not free the +int trn_cell_establish_intro_set_extensions(trn_cell_establish_intro_t *inp, struct trn_cell_extension_st *val); +/** As trn_cell_establish_intro_set_extensions, but does not free the * previous value. */ -int hs_cell_establish_intro_set0_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val); +int trn_cell_establish_intro_set0_extensions(trn_cell_establish_intro_t *inp, struct trn_cell_extension_st *val); /** Return the position for end_mac_fields when we parsed this object */ -const uint8_t * hs_cell_establish_intro_get_end_mac_fields(const hs_cell_establish_intro_t *inp); +const uint8_t * trn_cell_establish_intro_get_end_mac_fields(const trn_cell_establish_intro_t *inp); /** Return the (constant) length of the array holding the - * handshake_mac field of the hs_cell_establish_intro_t in 'inp'. + * handshake_mac field of the trn_cell_establish_intro_t in 'inp'. */ -size_t hs_cell_establish_intro_getlen_handshake_mac(const hs_cell_establish_intro_t *inp); +size_t trn_cell_establish_intro_getlen_handshake_mac(const trn_cell_establish_intro_t *inp); /** Return the element at position 'idx' of the fixed array field - * handshake_mac of the hs_cell_establish_intro_t in 'inp'. + * handshake_mac of the trn_cell_establish_intro_t in 'inp'. */ -uint8_t hs_cell_establish_intro_get_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx); -/** As hs_cell_establish_intro_get_handshake_mac, but take and return +uint8_t trn_cell_establish_intro_get_handshake_mac(trn_cell_establish_intro_t *inp, size_t idx); +/** As trn_cell_establish_intro_get_handshake_mac, but take and return * a const pointer */ -uint8_t hs_cell_establish_intro_getconst_handshake_mac(const hs_cell_establish_intro_t *inp, size_t idx); +uint8_t trn_cell_establish_intro_getconst_handshake_mac(const trn_cell_establish_intro_t *inp, size_t idx); /** Change the element at position 'idx' of the fixed array field - * handshake_mac of the hs_cell_establish_intro_t in 'inp', so that it - * will hold the value 'elt'. + * handshake_mac of the trn_cell_establish_intro_t in 'inp', so that + * it will hold the value 'elt'. */ -int hs_cell_establish_intro_set_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt); +int trn_cell_establish_intro_set_handshake_mac(trn_cell_establish_intro_t *inp, size_t idx, uint8_t elt); /** Return a pointer to the TRUNNEL_SHA3_256_LEN-element array field * handshake_mac of 'inp'. */ -uint8_t * hs_cell_establish_intro_getarray_handshake_mac(hs_cell_establish_intro_t *inp); -/** As hs_cell_establish_intro_get_handshake_mac, but take and return +uint8_t * trn_cell_establish_intro_getarray_handshake_mac(trn_cell_establish_intro_t *inp); +/** As trn_cell_establish_intro_get_handshake_mac, but take and return * a const pointer */ -const uint8_t * hs_cell_establish_intro_getconstarray_handshake_mac(const hs_cell_establish_intro_t *inp); +const uint8_t * trn_cell_establish_intro_getconstarray_handshake_mac(const trn_cell_establish_intro_t *inp); /** Return the position for end_sig_fields when we parsed this object */ -const uint8_t * hs_cell_establish_intro_get_end_sig_fields(const hs_cell_establish_intro_t *inp); +const uint8_t * trn_cell_establish_intro_get_end_sig_fields(const trn_cell_establish_intro_t *inp); /** Return the value of the sig_len field of the - * hs_cell_establish_intro_t in 'inp' + * trn_cell_establish_intro_t in 'inp' */ -uint16_t hs_cell_establish_intro_get_sig_len(const hs_cell_establish_intro_t *inp); +uint16_t trn_cell_establish_intro_get_sig_len(const trn_cell_establish_intro_t *inp); /** Set the value of the sig_len field of the - * hs_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success; + * trn_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_establish_intro_set_sig_len(hs_cell_establish_intro_t *inp, uint16_t val); +int trn_cell_establish_intro_set_sig_len(trn_cell_establish_intro_t *inp, uint16_t val); /** Return the length of the dynamic array holding the sig field of - * the hs_cell_establish_intro_t in 'inp'. + * the trn_cell_establish_intro_t in 'inp'. */ -size_t hs_cell_establish_intro_getlen_sig(const hs_cell_establish_intro_t *inp); +size_t trn_cell_establish_intro_getlen_sig(const trn_cell_establish_intro_t *inp); /** Return the element at position 'idx' of the dynamic array field - * sig of the hs_cell_establish_intro_t in 'inp'. + * sig of the trn_cell_establish_intro_t in 'inp'. */ -uint8_t hs_cell_establish_intro_get_sig(hs_cell_establish_intro_t *inp, size_t idx); -/** As hs_cell_establish_intro_get_sig, but take and return a const +uint8_t trn_cell_establish_intro_get_sig(trn_cell_establish_intro_t *inp, size_t idx); +/** As trn_cell_establish_intro_get_sig, but take and return a const * pointer */ -uint8_t hs_cell_establish_intro_getconst_sig(const hs_cell_establish_intro_t *inp, size_t idx); +uint8_t trn_cell_establish_intro_getconst_sig(const trn_cell_establish_intro_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * sig of the hs_cell_establish_intro_t in 'inp', so that it will hold - * the value 'elt'. + * sig of the trn_cell_establish_intro_t in 'inp', so that it will + * hold the value 'elt'. */ -int hs_cell_establish_intro_set_sig(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt); +int trn_cell_establish_intro_set_sig(trn_cell_establish_intro_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field sig of the - * hs_cell_establish_intro_t in 'inp'. + * trn_cell_establish_intro_t in 'inp'. */ -int hs_cell_establish_intro_add_sig(hs_cell_establish_intro_t *inp, uint8_t elt); +int trn_cell_establish_intro_add_sig(trn_cell_establish_intro_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field sig of 'inp'. */ -uint8_t * hs_cell_establish_intro_getarray_sig(hs_cell_establish_intro_t *inp); -/** As hs_cell_establish_intro_get_sig, but take and return a const +uint8_t * trn_cell_establish_intro_getarray_sig(trn_cell_establish_intro_t *inp); +/** As trn_cell_establish_intro_get_sig, but take and return a const * pointer */ -const uint8_t * hs_cell_establish_intro_getconstarray_sig(const hs_cell_establish_intro_t *inp); +const uint8_t * trn_cell_establish_intro_getconstarray_sig(const trn_cell_establish_intro_t *inp); /** Change the length of the variable-length array field sig of 'inp' * to 'newlen'.Fill extra elements with 0. Return 0 on success; return * -1 and set the error code on 'inp' on failure. */ -int hs_cell_establish_intro_setlen_sig(hs_cell_establish_intro_t *inp, size_t newlen); -/** Return a newly allocated hs_cell_intro_established with all +int trn_cell_establish_intro_setlen_sig(trn_cell_establish_intro_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_intro_established with all * elements set to zero. */ -hs_cell_intro_established_t *hs_cell_intro_established_new(void); -/** Release all storage held by the hs_cell_intro_established in +trn_cell_intro_established_t *trn_cell_intro_established_new(void); +/** Release all storage held by the trn_cell_intro_established in * 'victim'. (Do nothing if 'victim' is NULL.) */ -void hs_cell_intro_established_free(hs_cell_intro_established_t *victim); -/** Try to parse a hs_cell_intro_established from the buffer in +void trn_cell_intro_established_free(trn_cell_intro_established_t *victim); +/** Try to parse a trn_cell_intro_established from the buffer in * 'input', using up to 'len_in' bytes from the input buffer. On * success, return the number of bytes consumed and set *output to the - * newly allocated hs_cell_intro_established_t. On failure, return -2 + * newly allocated trn_cell_intro_established_t. On failure, return -2 * if the input appears truncated, and -1 if the input is otherwise * invalid. */ -ssize_t hs_cell_intro_established_parse(hs_cell_intro_established_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_intro_established_parse(trn_cell_intro_established_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * hs_cell_intro_established in 'obj'. On failure, return a negative + * trn_cell_intro_established in 'obj'. On failure, return a negative * value. Note that this value may be an overestimate, and can even be * an underestimate for certain unencodeable objects. */ -ssize_t hs_cell_intro_established_encoded_len(const hs_cell_intro_established_t *obj); -/** Try to encode the hs_cell_intro_established from 'input' into the +ssize_t trn_cell_intro_established_encoded_len(const trn_cell_intro_established_t *obj); +/** Try to encode the trn_cell_intro_established from 'input' into the * buffer at 'output', using up to 'avail' bytes of the output buffer. * On success, return the number of bytes used. On failure, return -2 * if the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t hs_cell_intro_established_encode(uint8_t *output, size_t avail, const hs_cell_intro_established_t *input); -/** Check whether the internal state of the hs_cell_intro_established +ssize_t trn_cell_intro_established_encode(uint8_t *output, size_t avail, const trn_cell_intro_established_t *input); +/** Check whether the internal state of the trn_cell_intro_established * in 'obj' is consistent. Return NULL if it is, and a short message * if it is not. */ -const char *hs_cell_intro_established_check(const hs_cell_intro_established_t *obj); +const char *trn_cell_intro_established_check(const trn_cell_intro_established_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int hs_cell_intro_established_clear_errors(hs_cell_intro_established_t *obj); +int trn_cell_intro_established_clear_errors(trn_cell_intro_established_t *obj); /** Return the value of the extensions field of the - * hs_cell_intro_established_t in 'inp' + * trn_cell_intro_established_t in 'inp' */ -struct cell_extension_st * hs_cell_intro_established_get_extensions(hs_cell_intro_established_t *inp); -/** As hs_cell_intro_established_get_extensions, but take and return a - * const pointer +struct trn_cell_extension_st * trn_cell_intro_established_get_extensions(trn_cell_intro_established_t *inp); +/** As trn_cell_intro_established_get_extensions, but take and return + * a const pointer */ -const struct cell_extension_st * hs_cell_intro_established_getconst_extensions(const hs_cell_intro_established_t *inp); +const struct trn_cell_extension_st * trn_cell_intro_established_getconst_extensions(const trn_cell_intro_established_t *inp); /** Set the value of the extensions field of the - * hs_cell_intro_established_t in 'inp' to 'val'. Free the old value + * trn_cell_intro_established_t in 'inp' to 'val'. Free the old value * if any. Steals the referenceto 'val'.Return 0 on success; return -1 * and set the error code on 'inp' on failure. */ -int hs_cell_intro_established_set_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val); -/** As hs_cell_intro_established_set_extensions, but does not free the - * previous value. +int trn_cell_intro_established_set_extensions(trn_cell_intro_established_t *inp, struct trn_cell_extension_st *val); +/** As trn_cell_intro_established_set_extensions, but does not free + * the previous value. */ -int hs_cell_intro_established_set0_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val); +int trn_cell_intro_established_set0_extensions(trn_cell_intro_established_t *inp, struct trn_cell_extension_st *val); #endif diff --git a/src/trunnel/hs/cell_establish_intro.trunnel b/src/trunnel/hs/cell_establish_intro.trunnel index 33a133bf67..011ee62a15 100644 --- a/src/trunnel/hs/cell_establish_intro.trunnel +++ b/src/trunnel/hs/cell_establish_intro.trunnel @@ -4,12 +4,12 @@ * specified in proposal 224 section 3.1. */ -extern struct cell_extension; +extern struct trn_cell_extension; const TRUNNEL_SHA3_256_LEN = 32; /* ESTABLISH_INTRO payload. See details in section 3.1.1 */ -struct hs_cell_establish_intro { +struct trn_cell_establish_intro { /* Indicate the start of the handshake authentication data. */ @ptr start_cell; @@ -19,7 +19,7 @@ struct hs_cell_establish_intro { u8 auth_key[auth_key_len]; /* Extension(s). Reserved fields. */ - struct cell_extension extensions; + struct trn_cell_extension extensions; @ptr end_mac_fields; /* Handshake MAC. */ @@ -35,7 +35,7 @@ struct hs_cell_establish_intro { /* INTRO_ESTABLISHED payload which is an acknowledge of the ESTABLISH_INTRO * cell. For legacy node, this payload is empty so the following only applies * to version >= 3. */ -struct hs_cell_intro_established { +struct trn_cell_intro_established { /* Extension(s). Reserved fields. */ - struct cell_extension extensions; + struct trn_cell_extension extensions; }; diff --git a/src/trunnel/hs/cell_introduce1.c b/src/trunnel/hs/cell_introduce1.c index 5922a086dc..7501f6f196 100644 --- a/src/trunnel/hs/cell_introduce1.c +++ b/src/trunnel/hs/cell_introduce1.c @@ -28,14 +28,14 @@ int cellintroduce_deadcode_dummy__ = 0; } \ } while (0) -typedef struct cell_extension_st cell_extension_t; -cell_extension_t *cell_extension_new(void); -void cell_extension_free(cell_extension_t *victim); -ssize_t cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in); -ssize_t cell_extension_encoded_len(const cell_extension_t *obj); -ssize_t cell_extension_encode(uint8_t *output, size_t avail, const cell_extension_t *input); -const char *cell_extension_check(const cell_extension_t *obj); -int cell_extension_clear_errors(cell_extension_t *obj); +typedef struct trn_cell_extension_st trn_cell_extension_t; +trn_cell_extension_t *trn_cell_extension_new(void); +void trn_cell_extension_free(trn_cell_extension_t *victim); +ssize_t trn_cell_extension_parse(trn_cell_extension_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_extension_encoded_len(const trn_cell_extension_t *obj); +ssize_t trn_cell_extension_encode(uint8_t *output, size_t avail, const trn_cell_extension_t *input); +const char *trn_cell_extension_check(const trn_cell_extension_t *obj); +int trn_cell_extension_clear_errors(trn_cell_extension_t *obj); typedef struct link_specifier_st link_specifier_t; link_specifier_t *link_specifier_new(void); void link_specifier_free(link_specifier_t *victim); @@ -44,10 +44,10 @@ ssize_t link_specifier_encoded_len(const link_specifier_t *obj); ssize_t link_specifier_encode(uint8_t *output, size_t avail, const link_specifier_t *input); const char *link_specifier_check(const link_specifier_t *obj); int link_specifier_clear_errors(link_specifier_t *obj); -hs_cell_introduce1_t * -hs_cell_introduce1_new(void) +trn_cell_introduce1_t * +trn_cell_introduce1_new(void) { - hs_cell_introduce1_t *val = trunnel_calloc(1, sizeof(hs_cell_introduce1_t)); + trn_cell_introduce1_t *val = trunnel_calloc(1, sizeof(trn_cell_introduce1_t)); if (NULL == val) return NULL; return val; @@ -56,47 +56,47 @@ hs_cell_introduce1_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -hs_cell_introduce1_clear(hs_cell_introduce1_t *obj) +trn_cell_introduce1_clear(trn_cell_introduce1_t *obj) { (void) obj; TRUNNEL_DYNARRAY_WIPE(&obj->auth_key); TRUNNEL_DYNARRAY_CLEAR(&obj->auth_key); - cell_extension_free(obj->extensions); + trn_cell_extension_free(obj->extensions); obj->extensions = NULL; TRUNNEL_DYNARRAY_WIPE(&obj->encrypted); TRUNNEL_DYNARRAY_CLEAR(&obj->encrypted); } void -hs_cell_introduce1_free(hs_cell_introduce1_t *obj) +trn_cell_introduce1_free(trn_cell_introduce1_t *obj) { if (obj == NULL) return; - hs_cell_introduce1_clear(obj); - trunnel_memwipe(obj, sizeof(hs_cell_introduce1_t)); + trn_cell_introduce1_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_introduce1_t)); trunnel_free_(obj); } size_t -hs_cell_introduce1_getlen_legacy_key_id(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_getlen_legacy_key_id(const trn_cell_introduce1_t *inp) { (void)inp; return TRUNNEL_SHA1_LEN; } uint8_t -hs_cell_introduce1_get_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx) +trn_cell_introduce1_get_legacy_key_id(trn_cell_introduce1_t *inp, size_t idx) { trunnel_assert(idx < TRUNNEL_SHA1_LEN); return inp->legacy_key_id[idx]; } uint8_t -hs_cell_introduce1_getconst_legacy_key_id(const hs_cell_introduce1_t *inp, size_t idx) +trn_cell_introduce1_getconst_legacy_key_id(const trn_cell_introduce1_t *inp, size_t idx) { - return hs_cell_introduce1_get_legacy_key_id((hs_cell_introduce1_t*)inp, idx); + return trn_cell_introduce1_get_legacy_key_id((trn_cell_introduce1_t*)inp, idx); } int -hs_cell_introduce1_set_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt) +trn_cell_introduce1_set_legacy_key_id(trn_cell_introduce1_t *inp, size_t idx, uint8_t elt) { trunnel_assert(idx < TRUNNEL_SHA1_LEN); inp->legacy_key_id[idx] = elt; @@ -104,22 +104,22 @@ hs_cell_introduce1_set_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx, uint } uint8_t * -hs_cell_introduce1_getarray_legacy_key_id(hs_cell_introduce1_t *inp) +trn_cell_introduce1_getarray_legacy_key_id(trn_cell_introduce1_t *inp) { return inp->legacy_key_id; } const uint8_t * -hs_cell_introduce1_getconstarray_legacy_key_id(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_getconstarray_legacy_key_id(const trn_cell_introduce1_t *inp) { - return (const uint8_t *)hs_cell_introduce1_getarray_legacy_key_id((hs_cell_introduce1_t*)inp); + return (const uint8_t *)trn_cell_introduce1_getarray_legacy_key_id((trn_cell_introduce1_t*)inp); } uint8_t -hs_cell_introduce1_get_auth_key_type(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_get_auth_key_type(const trn_cell_introduce1_t *inp) { return inp->auth_key_type; } int -hs_cell_introduce1_set_auth_key_type(hs_cell_introduce1_t *inp, uint8_t val) +trn_cell_introduce1_set_auth_key_type(trn_cell_introduce1_t *inp, uint8_t val) { if (! ((val == 0 || val == 1 || val == 2))) { TRUNNEL_SET_ERROR_CODE(inp); @@ -129,41 +129,41 @@ hs_cell_introduce1_set_auth_key_type(hs_cell_introduce1_t *inp, uint8_t val) return 0; } uint16_t -hs_cell_introduce1_get_auth_key_len(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_get_auth_key_len(const trn_cell_introduce1_t *inp) { return inp->auth_key_len; } int -hs_cell_introduce1_set_auth_key_len(hs_cell_introduce1_t *inp, uint16_t val) +trn_cell_introduce1_set_auth_key_len(trn_cell_introduce1_t *inp, uint16_t val) { inp->auth_key_len = val; return 0; } size_t -hs_cell_introduce1_getlen_auth_key(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_getlen_auth_key(const trn_cell_introduce1_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->auth_key); } uint8_t -hs_cell_introduce1_get_auth_key(hs_cell_introduce1_t *inp, size_t idx) +trn_cell_introduce1_get_auth_key(trn_cell_introduce1_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->auth_key, idx); } uint8_t -hs_cell_introduce1_getconst_auth_key(const hs_cell_introduce1_t *inp, size_t idx) +trn_cell_introduce1_getconst_auth_key(const trn_cell_introduce1_t *inp, size_t idx) { - return hs_cell_introduce1_get_auth_key((hs_cell_introduce1_t*)inp, idx); + return trn_cell_introduce1_get_auth_key((trn_cell_introduce1_t*)inp, idx); } int -hs_cell_introduce1_set_auth_key(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt) +trn_cell_introduce1_set_auth_key(trn_cell_introduce1_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->auth_key, idx, elt); return 0; } int -hs_cell_introduce1_add_auth_key(hs_cell_introduce1_t *inp, uint8_t elt) +trn_cell_introduce1_add_auth_key(trn_cell_introduce1_t *inp, uint8_t elt) { #if SIZE_MAX >= UINT16_MAX if (inp->auth_key.n_ == UINT16_MAX) @@ -177,17 +177,17 @@ hs_cell_introduce1_add_auth_key(hs_cell_introduce1_t *inp, uint8_t elt) } uint8_t * -hs_cell_introduce1_getarray_auth_key(hs_cell_introduce1_t *inp) +trn_cell_introduce1_getarray_auth_key(trn_cell_introduce1_t *inp) { return inp->auth_key.elts_; } const uint8_t * -hs_cell_introduce1_getconstarray_auth_key(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_getconstarray_auth_key(const trn_cell_introduce1_t *inp) { - return (const uint8_t *)hs_cell_introduce1_getarray_auth_key((hs_cell_introduce1_t*)inp); + return (const uint8_t *)trn_cell_introduce1_getarray_auth_key((trn_cell_introduce1_t*)inp); } int -hs_cell_introduce1_setlen_auth_key(hs_cell_introduce1_t *inp, size_t newlen) +trn_cell_introduce1_setlen_auth_key(trn_cell_introduce1_t *inp, size_t newlen) { uint8_t *newptr; #if UINT16_MAX < SIZE_MAX @@ -206,54 +206,54 @@ hs_cell_introduce1_setlen_auth_key(hs_cell_introduce1_t *inp, size_t newlen) TRUNNEL_SET_ERROR_CODE(inp); return -1; } -struct cell_extension_st * -hs_cell_introduce1_get_extensions(hs_cell_introduce1_t *inp) +struct trn_cell_extension_st * +trn_cell_introduce1_get_extensions(trn_cell_introduce1_t *inp) { return inp->extensions; } -const struct cell_extension_st * -hs_cell_introduce1_getconst_extensions(const hs_cell_introduce1_t *inp) +const struct trn_cell_extension_st * +trn_cell_introduce1_getconst_extensions(const trn_cell_introduce1_t *inp) { - return hs_cell_introduce1_get_extensions((hs_cell_introduce1_t*) inp); + return trn_cell_introduce1_get_extensions((trn_cell_introduce1_t*) inp); } int -hs_cell_introduce1_set_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val) +trn_cell_introduce1_set_extensions(trn_cell_introduce1_t *inp, struct trn_cell_extension_st *val) { if (inp->extensions && inp->extensions != val) - cell_extension_free(inp->extensions); - return hs_cell_introduce1_set0_extensions(inp, val); + trn_cell_extension_free(inp->extensions); + return trn_cell_introduce1_set0_extensions(inp, val); } int -hs_cell_introduce1_set0_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val) +trn_cell_introduce1_set0_extensions(trn_cell_introduce1_t *inp, struct trn_cell_extension_st *val) { inp->extensions = val; return 0; } size_t -hs_cell_introduce1_getlen_encrypted(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_getlen_encrypted(const trn_cell_introduce1_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->encrypted); } uint8_t -hs_cell_introduce1_get_encrypted(hs_cell_introduce1_t *inp, size_t idx) +trn_cell_introduce1_get_encrypted(trn_cell_introduce1_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->encrypted, idx); } uint8_t -hs_cell_introduce1_getconst_encrypted(const hs_cell_introduce1_t *inp, size_t idx) +trn_cell_introduce1_getconst_encrypted(const trn_cell_introduce1_t *inp, size_t idx) { - return hs_cell_introduce1_get_encrypted((hs_cell_introduce1_t*)inp, idx); + return trn_cell_introduce1_get_encrypted((trn_cell_introduce1_t*)inp, idx); } int -hs_cell_introduce1_set_encrypted(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt) +trn_cell_introduce1_set_encrypted(trn_cell_introduce1_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->encrypted, idx, elt); return 0; } int -hs_cell_introduce1_add_encrypted(hs_cell_introduce1_t *inp, uint8_t elt) +trn_cell_introduce1_add_encrypted(trn_cell_introduce1_t *inp, uint8_t elt) { TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->encrypted, elt, {}); return 0; @@ -263,17 +263,17 @@ hs_cell_introduce1_add_encrypted(hs_cell_introduce1_t *inp, uint8_t elt) } uint8_t * -hs_cell_introduce1_getarray_encrypted(hs_cell_introduce1_t *inp) +trn_cell_introduce1_getarray_encrypted(trn_cell_introduce1_t *inp) { return inp->encrypted.elts_; } const uint8_t * -hs_cell_introduce1_getconstarray_encrypted(const hs_cell_introduce1_t *inp) +trn_cell_introduce1_getconstarray_encrypted(const trn_cell_introduce1_t *inp) { - return (const uint8_t *)hs_cell_introduce1_getarray_encrypted((hs_cell_introduce1_t*)inp); + return (const uint8_t *)trn_cell_introduce1_getarray_encrypted((trn_cell_introduce1_t*)inp); } int -hs_cell_introduce1_setlen_encrypted(hs_cell_introduce1_t *inp, size_t newlen) +trn_cell_introduce1_setlen_encrypted(trn_cell_introduce1_t *inp, size_t newlen) { uint8_t *newptr; newptr = trunnel_dynarray_setlen(&inp->encrypted.allocated_, @@ -289,7 +289,7 @@ hs_cell_introduce1_setlen_encrypted(hs_cell_introduce1_t *inp, size_t newlen) return -1; } const char * -hs_cell_introduce1_check(const hs_cell_introduce1_t *obj) +trn_cell_introduce1_check(const trn_cell_introduce1_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -301,18 +301,18 @@ hs_cell_introduce1_check(const hs_cell_introduce1_t *obj) return "Length mismatch for auth_key"; { const char *msg; - if (NULL != (msg = cell_extension_check(obj->extensions))) + if (NULL != (msg = trn_cell_extension_check(obj->extensions))) return msg; } return NULL; } ssize_t -hs_cell_introduce1_encoded_len(const hs_cell_introduce1_t *obj) +trn_cell_introduce1_encoded_len(const trn_cell_introduce1_t *obj) { ssize_t result = 0; - if (NULL != hs_cell_introduce1_check(obj)) + if (NULL != trn_cell_introduce1_check(obj)) return -1; @@ -328,32 +328,32 @@ hs_cell_introduce1_encoded_len(const hs_cell_introduce1_t *obj) /* Length of u8 auth_key[auth_key_len] */ result += TRUNNEL_DYNARRAY_LEN(&obj->auth_key); - /* Length of struct cell_extension extensions */ - result += cell_extension_encoded_len(obj->extensions); + /* Length of struct trn_cell_extension extensions */ + result += trn_cell_extension_encoded_len(obj->extensions); /* Length of u8 encrypted[] */ result += TRUNNEL_DYNARRAY_LEN(&obj->encrypted); return result; } int -hs_cell_introduce1_clear_errors(hs_cell_introduce1_t *obj) +trn_cell_introduce1_clear_errors(trn_cell_introduce1_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -hs_cell_introduce1_encode(uint8_t *output, const size_t avail, const hs_cell_introduce1_t *obj) +trn_cell_introduce1_encode(uint8_t *output, const size_t avail, const trn_cell_introduce1_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = hs_cell_introduce1_encoded_len(obj); + const ssize_t encoded_len = trn_cell_introduce1_encoded_len(obj); #endif - if (NULL != (msg = hs_cell_introduce1_check(obj))) + if (NULL != (msg = trn_cell_introduce1_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -393,9 +393,9 @@ hs_cell_introduce1_encode(uint8_t *output, const size_t avail, const hs_cell_int written += elt_len; ptr += elt_len; } - /* Encode struct cell_extension extensions */ + /* Encode struct trn_cell_extension extensions */ trunnel_assert(written <= avail); - result = cell_extension_encode(ptr, avail - written, obj->extensions); + result = trn_cell_extension_encode(ptr, avail - written, obj->extensions); if (result < 0) goto fail; /* XXXXXXX !*/ written += result; ptr += result; @@ -435,11 +435,11 @@ hs_cell_introduce1_encode(uint8_t *output, const size_t avail, const hs_cell_int return result; } -/** As hs_cell_introduce1_parse(), but do not allocate the output +/** As trn_cell_introduce1_parse(), but do not allocate the output * object. */ static ssize_t -hs_cell_introduce1_parse_into(hs_cell_introduce1_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_introduce1_parse_into(trn_cell_introduce1_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; @@ -471,8 +471,8 @@ hs_cell_introduce1_parse_into(hs_cell_introduce1_t *obj, const uint8_t *input, c memcpy(obj->auth_key.elts_, ptr, obj->auth_key_len); ptr += obj->auth_key_len; remaining -= obj->auth_key_len; - /* Parse struct cell_extension extensions */ - result = cell_extension_parse(&obj->extensions, ptr, remaining); + /* Parse struct trn_cell_extension extensions */ + result = trn_cell_extension_parse(&obj->extensions, ptr, remaining); if (result < 0) goto relay_fail; trunnel_assert((size_t)result <= remaining); @@ -500,23 +500,23 @@ hs_cell_introduce1_parse_into(hs_cell_introduce1_t *obj, const uint8_t *input, c } ssize_t -hs_cell_introduce1_parse(hs_cell_introduce1_t **output, const uint8_t *input, const size_t len_in) +trn_cell_introduce1_parse(trn_cell_introduce1_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = hs_cell_introduce1_new(); + *output = trn_cell_introduce1_new(); if (NULL == *output) return -1; - result = hs_cell_introduce1_parse_into(*output, input, len_in); + result = trn_cell_introduce1_parse_into(*output, input, len_in); if (result < 0) { - hs_cell_introduce1_free(*output); + trn_cell_introduce1_free(*output); *output = NULL; } return result; } -hs_cell_introduce_ack_t * -hs_cell_introduce_ack_new(void) +trn_cell_introduce_ack_t * +trn_cell_introduce_ack_new(void) { - hs_cell_introduce_ack_t *val = trunnel_calloc(1, sizeof(hs_cell_introduce_ack_t)); + trn_cell_introduce_ack_t *val = trunnel_calloc(1, sizeof(trn_cell_introduce_ack_t)); if (NULL == val) return NULL; return val; @@ -525,30 +525,30 @@ hs_cell_introduce_ack_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -hs_cell_introduce_ack_clear(hs_cell_introduce_ack_t *obj) +trn_cell_introduce_ack_clear(trn_cell_introduce_ack_t *obj) { (void) obj; - cell_extension_free(obj->extensions); + trn_cell_extension_free(obj->extensions); obj->extensions = NULL; } void -hs_cell_introduce_ack_free(hs_cell_introduce_ack_t *obj) +trn_cell_introduce_ack_free(trn_cell_introduce_ack_t *obj) { if (obj == NULL) return; - hs_cell_introduce_ack_clear(obj); - trunnel_memwipe(obj, sizeof(hs_cell_introduce_ack_t)); + trn_cell_introduce_ack_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_introduce_ack_t)); trunnel_free_(obj); } uint16_t -hs_cell_introduce_ack_get_status(const hs_cell_introduce_ack_t *inp) +trn_cell_introduce_ack_get_status(const trn_cell_introduce_ack_t *inp) { return inp->status; } int -hs_cell_introduce_ack_set_status(hs_cell_introduce_ack_t *inp, uint16_t val) +trn_cell_introduce_ack_set_status(trn_cell_introduce_ack_t *inp, uint16_t val) { if (! ((val == 0 || val == 1 || val == 2))) { TRUNNEL_SET_ERROR_CODE(inp); @@ -557,31 +557,31 @@ hs_cell_introduce_ack_set_status(hs_cell_introduce_ack_t *inp, uint16_t val) inp->status = val; return 0; } -struct cell_extension_st * -hs_cell_introduce_ack_get_extensions(hs_cell_introduce_ack_t *inp) +struct trn_cell_extension_st * +trn_cell_introduce_ack_get_extensions(trn_cell_introduce_ack_t *inp) { return inp->extensions; } -const struct cell_extension_st * -hs_cell_introduce_ack_getconst_extensions(const hs_cell_introduce_ack_t *inp) +const struct trn_cell_extension_st * +trn_cell_introduce_ack_getconst_extensions(const trn_cell_introduce_ack_t *inp) { - return hs_cell_introduce_ack_get_extensions((hs_cell_introduce_ack_t*) inp); + return trn_cell_introduce_ack_get_extensions((trn_cell_introduce_ack_t*) inp); } int -hs_cell_introduce_ack_set_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val) +trn_cell_introduce_ack_set_extensions(trn_cell_introduce_ack_t *inp, struct trn_cell_extension_st *val) { if (inp->extensions && inp->extensions != val) - cell_extension_free(inp->extensions); - return hs_cell_introduce_ack_set0_extensions(inp, val); + trn_cell_extension_free(inp->extensions); + return trn_cell_introduce_ack_set0_extensions(inp, val); } int -hs_cell_introduce_ack_set0_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val) +trn_cell_introduce_ack_set0_extensions(trn_cell_introduce_ack_t *inp, struct trn_cell_extension_st *val) { inp->extensions = val; return 0; } const char * -hs_cell_introduce_ack_check(const hs_cell_introduce_ack_t *obj) +trn_cell_introduce_ack_check(const trn_cell_introduce_ack_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -591,47 +591,47 @@ hs_cell_introduce_ack_check(const hs_cell_introduce_ack_t *obj) return "Integer out of bounds"; { const char *msg; - if (NULL != (msg = cell_extension_check(obj->extensions))) + if (NULL != (msg = trn_cell_extension_check(obj->extensions))) return msg; } return NULL; } ssize_t -hs_cell_introduce_ack_encoded_len(const hs_cell_introduce_ack_t *obj) +trn_cell_introduce_ack_encoded_len(const trn_cell_introduce_ack_t *obj) { ssize_t result = 0; - if (NULL != hs_cell_introduce_ack_check(obj)) + if (NULL != trn_cell_introduce_ack_check(obj)) return -1; /* Length of u16 status IN [0, 1, 2] */ result += 2; - /* Length of struct cell_extension extensions */ - result += cell_extension_encoded_len(obj->extensions); + /* Length of struct trn_cell_extension extensions */ + result += trn_cell_extension_encoded_len(obj->extensions); return result; } int -hs_cell_introduce_ack_clear_errors(hs_cell_introduce_ack_t *obj) +trn_cell_introduce_ack_clear_errors(trn_cell_introduce_ack_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -hs_cell_introduce_ack_encode(uint8_t *output, const size_t avail, const hs_cell_introduce_ack_t *obj) +trn_cell_introduce_ack_encode(uint8_t *output, const size_t avail, const trn_cell_introduce_ack_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = hs_cell_introduce_ack_encoded_len(obj); + const ssize_t encoded_len = trn_cell_introduce_ack_encoded_len(obj); #endif - if (NULL != (msg = hs_cell_introduce_ack_check(obj))) + if (NULL != (msg = trn_cell_introduce_ack_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -645,9 +645,9 @@ hs_cell_introduce_ack_encode(uint8_t *output, const size_t avail, const hs_cell_ trunnel_set_uint16(ptr, trunnel_htons(obj->status)); written += 2; ptr += 2; - /* Encode struct cell_extension extensions */ + /* Encode struct trn_cell_extension extensions */ trunnel_assert(written <= avail); - result = cell_extension_encode(ptr, avail - written, obj->extensions); + result = trn_cell_extension_encode(ptr, avail - written, obj->extensions); if (result < 0) goto fail; /* XXXXXXX !*/ written += result; ptr += result; @@ -676,11 +676,11 @@ hs_cell_introduce_ack_encode(uint8_t *output, const size_t avail, const hs_cell_ return result; } -/** As hs_cell_introduce_ack_parse(), but do not allocate the output +/** As trn_cell_introduce_ack_parse(), but do not allocate the output * object. */ static ssize_t -hs_cell_introduce_ack_parse_into(hs_cell_introduce_ack_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_introduce_ack_parse_into(trn_cell_introduce_ack_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; @@ -694,8 +694,8 @@ hs_cell_introduce_ack_parse_into(hs_cell_introduce_ack_t *obj, const uint8_t *in if (! (obj->status == 0 || obj->status == 1 || obj->status == 2)) goto fail; - /* Parse struct cell_extension extensions */ - result = cell_extension_parse(&obj->extensions, ptr, remaining); + /* Parse struct trn_cell_extension extensions */ + result = trn_cell_extension_parse(&obj->extensions, ptr, remaining); if (result < 0) goto relay_fail; trunnel_assert((size_t)result <= remaining); @@ -714,23 +714,23 @@ hs_cell_introduce_ack_parse_into(hs_cell_introduce_ack_t *obj, const uint8_t *in } ssize_t -hs_cell_introduce_ack_parse(hs_cell_introduce_ack_t **output, const uint8_t *input, const size_t len_in) +trn_cell_introduce_ack_parse(trn_cell_introduce_ack_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = hs_cell_introduce_ack_new(); + *output = trn_cell_introduce_ack_new(); if (NULL == *output) return -1; - result = hs_cell_introduce_ack_parse_into(*output, input, len_in); + result = trn_cell_introduce_ack_parse_into(*output, input, len_in); if (result < 0) { - hs_cell_introduce_ack_free(*output); + trn_cell_introduce_ack_free(*output); *output = NULL; } return result; } -hs_cell_introduce_encrypted_t * -hs_cell_introduce_encrypted_new(void) +trn_cell_introduce_encrypted_t * +trn_cell_introduce_encrypted_new(void) { - hs_cell_introduce_encrypted_t *val = trunnel_calloc(1, sizeof(hs_cell_introduce_encrypted_t)); + trn_cell_introduce_encrypted_t *val = trunnel_calloc(1, sizeof(trn_cell_introduce_encrypted_t)); if (NULL == val) return NULL; val->onion_key_type = 1; @@ -740,10 +740,10 @@ hs_cell_introduce_encrypted_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -hs_cell_introduce_encrypted_clear(hs_cell_introduce_encrypted_t *obj) +trn_cell_introduce_encrypted_clear(trn_cell_introduce_encrypted_t *obj) { (void) obj; - cell_extension_free(obj->extensions); + trn_cell_extension_free(obj->extensions); obj->extensions = NULL; TRUNNEL_DYNARRAY_WIPE(&obj->onion_key); TRUNNEL_DYNARRAY_CLEAR(&obj->onion_key); @@ -761,35 +761,35 @@ hs_cell_introduce_encrypted_clear(hs_cell_introduce_encrypted_t *obj) } void -hs_cell_introduce_encrypted_free(hs_cell_introduce_encrypted_t *obj) +trn_cell_introduce_encrypted_free(trn_cell_introduce_encrypted_t *obj) { if (obj == NULL) return; - hs_cell_introduce_encrypted_clear(obj); - trunnel_memwipe(obj, sizeof(hs_cell_introduce_encrypted_t)); + trn_cell_introduce_encrypted_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_introduce_encrypted_t)); trunnel_free_(obj); } size_t -hs_cell_introduce_encrypted_getlen_rend_cookie(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getlen_rend_cookie(const trn_cell_introduce_encrypted_t *inp) { (void)inp; return TRUNNEL_REND_COOKIE_LEN; } uint8_t -hs_cell_introduce_encrypted_get_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_get_rend_cookie(trn_cell_introduce_encrypted_t *inp, size_t idx) { trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN); return inp->rend_cookie[idx]; } uint8_t -hs_cell_introduce_encrypted_getconst_rend_cookie(const hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_getconst_rend_cookie(const trn_cell_introduce_encrypted_t *inp, size_t idx) { - return hs_cell_introduce_encrypted_get_rend_cookie((hs_cell_introduce_encrypted_t*)inp, idx); + return trn_cell_introduce_encrypted_get_rend_cookie((trn_cell_introduce_encrypted_t*)inp, idx); } int -hs_cell_introduce_encrypted_set_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt) +trn_cell_introduce_encrypted_set_rend_cookie(trn_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt) { trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN); inp->rend_cookie[idx] = elt; @@ -797,45 +797,45 @@ hs_cell_introduce_encrypted_set_rend_cookie(hs_cell_introduce_encrypted_t *inp, } uint8_t * -hs_cell_introduce_encrypted_getarray_rend_cookie(hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getarray_rend_cookie(trn_cell_introduce_encrypted_t *inp) { return inp->rend_cookie; } const uint8_t * -hs_cell_introduce_encrypted_getconstarray_rend_cookie(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getconstarray_rend_cookie(const trn_cell_introduce_encrypted_t *inp) { - return (const uint8_t *)hs_cell_introduce_encrypted_getarray_rend_cookie((hs_cell_introduce_encrypted_t*)inp); + return (const uint8_t *)trn_cell_introduce_encrypted_getarray_rend_cookie((trn_cell_introduce_encrypted_t*)inp); } -struct cell_extension_st * -hs_cell_introduce_encrypted_get_extensions(hs_cell_introduce_encrypted_t *inp) +struct trn_cell_extension_st * +trn_cell_introduce_encrypted_get_extensions(trn_cell_introduce_encrypted_t *inp) { return inp->extensions; } -const struct cell_extension_st * -hs_cell_introduce_encrypted_getconst_extensions(const hs_cell_introduce_encrypted_t *inp) +const struct trn_cell_extension_st * +trn_cell_introduce_encrypted_getconst_extensions(const trn_cell_introduce_encrypted_t *inp) { - return hs_cell_introduce_encrypted_get_extensions((hs_cell_introduce_encrypted_t*) inp); + return trn_cell_introduce_encrypted_get_extensions((trn_cell_introduce_encrypted_t*) inp); } int -hs_cell_introduce_encrypted_set_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val) +trn_cell_introduce_encrypted_set_extensions(trn_cell_introduce_encrypted_t *inp, struct trn_cell_extension_st *val) { if (inp->extensions && inp->extensions != val) - cell_extension_free(inp->extensions); - return hs_cell_introduce_encrypted_set0_extensions(inp, val); + trn_cell_extension_free(inp->extensions); + return trn_cell_introduce_encrypted_set0_extensions(inp, val); } int -hs_cell_introduce_encrypted_set0_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val) +trn_cell_introduce_encrypted_set0_extensions(trn_cell_introduce_encrypted_t *inp, struct trn_cell_extension_st *val) { inp->extensions = val; return 0; } uint8_t -hs_cell_introduce_encrypted_get_onion_key_type(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_get_onion_key_type(const trn_cell_introduce_encrypted_t *inp) { return inp->onion_key_type; } int -hs_cell_introduce_encrypted_set_onion_key_type(hs_cell_introduce_encrypted_t *inp, uint8_t val) +trn_cell_introduce_encrypted_set_onion_key_type(trn_cell_introduce_encrypted_t *inp, uint8_t val) { if (! ((val == 1))) { TRUNNEL_SET_ERROR_CODE(inp); @@ -845,41 +845,41 @@ hs_cell_introduce_encrypted_set_onion_key_type(hs_cell_introduce_encrypted_t *in return 0; } uint16_t -hs_cell_introduce_encrypted_get_onion_key_len(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_get_onion_key_len(const trn_cell_introduce_encrypted_t *inp) { return inp->onion_key_len; } int -hs_cell_introduce_encrypted_set_onion_key_len(hs_cell_introduce_encrypted_t *inp, uint16_t val) +trn_cell_introduce_encrypted_set_onion_key_len(trn_cell_introduce_encrypted_t *inp, uint16_t val) { inp->onion_key_len = val; return 0; } size_t -hs_cell_introduce_encrypted_getlen_onion_key(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getlen_onion_key(const trn_cell_introduce_encrypted_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->onion_key); } uint8_t -hs_cell_introduce_encrypted_get_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_get_onion_key(trn_cell_introduce_encrypted_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->onion_key, idx); } uint8_t -hs_cell_introduce_encrypted_getconst_onion_key(const hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_getconst_onion_key(const trn_cell_introduce_encrypted_t *inp, size_t idx) { - return hs_cell_introduce_encrypted_get_onion_key((hs_cell_introduce_encrypted_t*)inp, idx); + return trn_cell_introduce_encrypted_get_onion_key((trn_cell_introduce_encrypted_t*)inp, idx); } int -hs_cell_introduce_encrypted_set_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt) +trn_cell_introduce_encrypted_set_onion_key(trn_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->onion_key, idx, elt); return 0; } int -hs_cell_introduce_encrypted_add_onion_key(hs_cell_introduce_encrypted_t *inp, uint8_t elt) +trn_cell_introduce_encrypted_add_onion_key(trn_cell_introduce_encrypted_t *inp, uint8_t elt) { #if SIZE_MAX >= UINT16_MAX if (inp->onion_key.n_ == UINT16_MAX) @@ -893,17 +893,17 @@ hs_cell_introduce_encrypted_add_onion_key(hs_cell_introduce_encrypted_t *inp, ui } uint8_t * -hs_cell_introduce_encrypted_getarray_onion_key(hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getarray_onion_key(trn_cell_introduce_encrypted_t *inp) { return inp->onion_key.elts_; } const uint8_t * -hs_cell_introduce_encrypted_getconstarray_onion_key(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getconstarray_onion_key(const trn_cell_introduce_encrypted_t *inp) { - return (const uint8_t *)hs_cell_introduce_encrypted_getarray_onion_key((hs_cell_introduce_encrypted_t*)inp); + return (const uint8_t *)trn_cell_introduce_encrypted_getarray_onion_key((trn_cell_introduce_encrypted_t*)inp); } int -hs_cell_introduce_encrypted_setlen_onion_key(hs_cell_introduce_encrypted_t *inp, size_t newlen) +trn_cell_introduce_encrypted_setlen_onion_key(trn_cell_introduce_encrypted_t *inp, size_t newlen) { uint8_t *newptr; #if UINT16_MAX < SIZE_MAX @@ -923,49 +923,49 @@ hs_cell_introduce_encrypted_setlen_onion_key(hs_cell_introduce_encrypted_t *inp, return -1; } uint8_t -hs_cell_introduce_encrypted_get_nspec(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_get_nspec(const trn_cell_introduce_encrypted_t *inp) { return inp->nspec; } int -hs_cell_introduce_encrypted_set_nspec(hs_cell_introduce_encrypted_t *inp, uint8_t val) +trn_cell_introduce_encrypted_set_nspec(trn_cell_introduce_encrypted_t *inp, uint8_t val) { inp->nspec = val; return 0; } size_t -hs_cell_introduce_encrypted_getlen_nspecs(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getlen_nspecs(const trn_cell_introduce_encrypted_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->nspecs); } struct link_specifier_st * -hs_cell_introduce_encrypted_get_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_get_nspecs(trn_cell_introduce_encrypted_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->nspecs, idx); } const struct link_specifier_st * -hs_cell_introduce_encrypted_getconst_nspecs(const hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_getconst_nspecs(const trn_cell_introduce_encrypted_t *inp, size_t idx) { - return hs_cell_introduce_encrypted_get_nspecs((hs_cell_introduce_encrypted_t*)inp, idx); + return trn_cell_introduce_encrypted_get_nspecs((trn_cell_introduce_encrypted_t*)inp, idx); } int -hs_cell_introduce_encrypted_set_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt) +trn_cell_introduce_encrypted_set_nspecs(trn_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt) { link_specifier_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->nspecs, idx); if (oldval && oldval != elt) link_specifier_free(oldval); - return hs_cell_introduce_encrypted_set0_nspecs(inp, idx, elt); + return trn_cell_introduce_encrypted_set0_nspecs(inp, idx, elt); } int -hs_cell_introduce_encrypted_set0_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt) +trn_cell_introduce_encrypted_set0_nspecs(trn_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt) { TRUNNEL_DYNARRAY_SET(&inp->nspecs, idx, elt); return 0; } int -hs_cell_introduce_encrypted_add_nspecs(hs_cell_introduce_encrypted_t *inp, struct link_specifier_st * elt) +trn_cell_introduce_encrypted_add_nspecs(trn_cell_introduce_encrypted_t *inp, struct link_specifier_st * elt) { #if SIZE_MAX >= UINT8_MAX if (inp->nspecs.n_ == UINT8_MAX) @@ -979,17 +979,17 @@ hs_cell_introduce_encrypted_add_nspecs(hs_cell_introduce_encrypted_t *inp, struc } struct link_specifier_st * * -hs_cell_introduce_encrypted_getarray_nspecs(hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getarray_nspecs(trn_cell_introduce_encrypted_t *inp) { return inp->nspecs.elts_; } const struct link_specifier_st * const * -hs_cell_introduce_encrypted_getconstarray_nspecs(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getconstarray_nspecs(const trn_cell_introduce_encrypted_t *inp) { - return (const struct link_specifier_st * const *)hs_cell_introduce_encrypted_getarray_nspecs((hs_cell_introduce_encrypted_t*)inp); + return (const struct link_specifier_st * const *)trn_cell_introduce_encrypted_getarray_nspecs((trn_cell_introduce_encrypted_t*)inp); } int -hs_cell_introduce_encrypted_setlen_nspecs(hs_cell_introduce_encrypted_t *inp, size_t newlen) +trn_cell_introduce_encrypted_setlen_nspecs(trn_cell_introduce_encrypted_t *inp, size_t newlen) { struct link_specifier_st * *newptr; #if UINT8_MAX < SIZE_MAX @@ -1009,30 +1009,30 @@ hs_cell_introduce_encrypted_setlen_nspecs(hs_cell_introduce_encrypted_t *inp, si return -1; } size_t -hs_cell_introduce_encrypted_getlen_pad(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getlen_pad(const trn_cell_introduce_encrypted_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->pad); } uint8_t -hs_cell_introduce_encrypted_get_pad(hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_get_pad(trn_cell_introduce_encrypted_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->pad, idx); } uint8_t -hs_cell_introduce_encrypted_getconst_pad(const hs_cell_introduce_encrypted_t *inp, size_t idx) +trn_cell_introduce_encrypted_getconst_pad(const trn_cell_introduce_encrypted_t *inp, size_t idx) { - return hs_cell_introduce_encrypted_get_pad((hs_cell_introduce_encrypted_t*)inp, idx); + return trn_cell_introduce_encrypted_get_pad((trn_cell_introduce_encrypted_t*)inp, idx); } int -hs_cell_introduce_encrypted_set_pad(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt) +trn_cell_introduce_encrypted_set_pad(trn_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->pad, idx, elt); return 0; } int -hs_cell_introduce_encrypted_add_pad(hs_cell_introduce_encrypted_t *inp, uint8_t elt) +trn_cell_introduce_encrypted_add_pad(trn_cell_introduce_encrypted_t *inp, uint8_t elt) { TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->pad, elt, {}); return 0; @@ -1042,17 +1042,17 @@ hs_cell_introduce_encrypted_add_pad(hs_cell_introduce_encrypted_t *inp, uint8_t } uint8_t * -hs_cell_introduce_encrypted_getarray_pad(hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getarray_pad(trn_cell_introduce_encrypted_t *inp) { return inp->pad.elts_; } const uint8_t * -hs_cell_introduce_encrypted_getconstarray_pad(const hs_cell_introduce_encrypted_t *inp) +trn_cell_introduce_encrypted_getconstarray_pad(const trn_cell_introduce_encrypted_t *inp) { - return (const uint8_t *)hs_cell_introduce_encrypted_getarray_pad((hs_cell_introduce_encrypted_t*)inp); + return (const uint8_t *)trn_cell_introduce_encrypted_getarray_pad((trn_cell_introduce_encrypted_t*)inp); } int -hs_cell_introduce_encrypted_setlen_pad(hs_cell_introduce_encrypted_t *inp, size_t newlen) +trn_cell_introduce_encrypted_setlen_pad(trn_cell_introduce_encrypted_t *inp, size_t newlen) { uint8_t *newptr; newptr = trunnel_dynarray_setlen(&inp->pad.allocated_, @@ -1068,7 +1068,7 @@ hs_cell_introduce_encrypted_setlen_pad(hs_cell_introduce_encrypted_t *inp, size_ return -1; } const char * -hs_cell_introduce_encrypted_check(const hs_cell_introduce_encrypted_t *obj) +trn_cell_introduce_encrypted_check(const trn_cell_introduce_encrypted_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -1076,7 +1076,7 @@ hs_cell_introduce_encrypted_check(const hs_cell_introduce_encrypted_t *obj) return "A set function failed on this object"; { const char *msg; - if (NULL != (msg = cell_extension_check(obj->extensions))) + if (NULL != (msg = trn_cell_extension_check(obj->extensions))) return msg; } if (! (obj->onion_key_type == 1)) @@ -1098,19 +1098,19 @@ hs_cell_introduce_encrypted_check(const hs_cell_introduce_encrypted_t *obj) } ssize_t -hs_cell_introduce_encrypted_encoded_len(const hs_cell_introduce_encrypted_t *obj) +trn_cell_introduce_encrypted_encoded_len(const trn_cell_introduce_encrypted_t *obj) { ssize_t result = 0; - if (NULL != hs_cell_introduce_encrypted_check(obj)) + if (NULL != trn_cell_introduce_encrypted_check(obj)) return -1; /* Length of u8 rend_cookie[TRUNNEL_REND_COOKIE_LEN] */ result += TRUNNEL_REND_COOKIE_LEN; - /* Length of struct cell_extension extensions */ - result += cell_extension_encoded_len(obj->extensions); + /* Length of struct trn_cell_extension extensions */ + result += trn_cell_extension_encoded_len(obj->extensions); /* Length of u8 onion_key_type IN [1] */ result += 1; @@ -1138,24 +1138,24 @@ hs_cell_introduce_encrypted_encoded_len(const hs_cell_introduce_encrypted_t *obj return result; } int -hs_cell_introduce_encrypted_clear_errors(hs_cell_introduce_encrypted_t *obj) +trn_cell_introduce_encrypted_clear_errors(trn_cell_introduce_encrypted_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -hs_cell_introduce_encrypted_encode(uint8_t *output, const size_t avail, const hs_cell_introduce_encrypted_t *obj) +trn_cell_introduce_encrypted_encode(uint8_t *output, const size_t avail, const trn_cell_introduce_encrypted_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = hs_cell_introduce_encrypted_encoded_len(obj); + const ssize_t encoded_len = trn_cell_introduce_encrypted_encoded_len(obj); #endif - if (NULL != (msg = hs_cell_introduce_encrypted_check(obj))) + if (NULL != (msg = trn_cell_introduce_encrypted_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -1169,9 +1169,9 @@ hs_cell_introduce_encrypted_encode(uint8_t *output, const size_t avail, const hs memcpy(ptr, obj->rend_cookie, TRUNNEL_REND_COOKIE_LEN); written += TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN; - /* Encode struct cell_extension extensions */ + /* Encode struct trn_cell_extension extensions */ trunnel_assert(written <= avail); - result = cell_extension_encode(ptr, avail - written, obj->extensions); + result = trn_cell_extension_encode(ptr, avail - written, obj->extensions); if (result < 0) goto fail; /* XXXXXXX !*/ written += result; ptr += result; @@ -1257,11 +1257,11 @@ hs_cell_introduce_encrypted_encode(uint8_t *output, const size_t avail, const hs return result; } -/** As hs_cell_introduce_encrypted_parse(), but do not allocate the +/** As trn_cell_introduce_encrypted_parse(), but do not allocate the * output object. */ static ssize_t -hs_cell_introduce_encrypted_parse_into(hs_cell_introduce_encrypted_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_introduce_encrypted_parse_into(trn_cell_introduce_encrypted_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; @@ -1273,8 +1273,8 @@ hs_cell_introduce_encrypted_parse_into(hs_cell_introduce_encrypted_t *obj, const memcpy(obj->rend_cookie, ptr, TRUNNEL_REND_COOKIE_LEN); remaining -= TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN; - /* Parse struct cell_extension extensions */ - result = cell_extension_parse(&obj->extensions, ptr, remaining); + /* Parse struct trn_cell_extension extensions */ + result = trn_cell_extension_parse(&obj->extensions, ptr, remaining); if (result < 0) goto relay_fail; trunnel_assert((size_t)result <= remaining); @@ -1342,15 +1342,15 @@ hs_cell_introduce_encrypted_parse_into(hs_cell_introduce_encrypted_t *obj, const } ssize_t -hs_cell_introduce_encrypted_parse(hs_cell_introduce_encrypted_t **output, const uint8_t *input, const size_t len_in) +trn_cell_introduce_encrypted_parse(trn_cell_introduce_encrypted_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = hs_cell_introduce_encrypted_new(); + *output = trn_cell_introduce_encrypted_new(); if (NULL == *output) return -1; - result = hs_cell_introduce_encrypted_parse_into(*output, input, len_in); + result = trn_cell_introduce_encrypted_parse_into(*output, input, len_in); if (result < 0) { - hs_cell_introduce_encrypted_free(*output); + trn_cell_introduce_encrypted_free(*output); *output = NULL; } return result; diff --git a/src/trunnel/hs/cell_introduce1.h b/src/trunnel/hs/cell_introduce1.h index ccd2cda904..cca825a431 100644 --- a/src/trunnel/hs/cell_introduce1.h +++ b/src/trunnel/hs/cell_introduce1.h @@ -8,34 +8,34 @@ #include <stdint.h> #include "trunnel.h" -struct cell_extension_st; +struct trn_cell_extension_st; struct link_specifier_st; #define TRUNNEL_SHA1_LEN 20 #define TRUNNEL_REND_COOKIE_LEN 20 -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRODUCE1) -struct hs_cell_introduce1_st { +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_INTRODUCE1) +struct trn_cell_introduce1_st { uint8_t legacy_key_id[TRUNNEL_SHA1_LEN]; uint8_t auth_key_type; uint16_t auth_key_len; TRUNNEL_DYNARRAY_HEAD(, uint8_t) auth_key; - struct cell_extension_st *extensions; + struct trn_cell_extension_st *extensions; TRUNNEL_DYNARRAY_HEAD(, uint8_t) encrypted; uint8_t trunnel_error_code_; }; #endif -typedef struct hs_cell_introduce1_st hs_cell_introduce1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRODUCE_ACK) -struct hs_cell_introduce_ack_st { +typedef struct trn_cell_introduce1_st trn_cell_introduce1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_INTRODUCE_ACK) +struct trn_cell_introduce_ack_st { uint16_t status; - struct cell_extension_st *extensions; + struct trn_cell_extension_st *extensions; uint8_t trunnel_error_code_; }; #endif -typedef struct hs_cell_introduce_ack_st hs_cell_introduce_ack_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRODUCE_ENCRYPTED) -struct hs_cell_introduce_encrypted_st { +typedef struct trn_cell_introduce_ack_st trn_cell_introduce_ack_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_INTRODUCE_ENCRYPTED) +struct trn_cell_introduce_encrypted_st { uint8_t rend_cookie[TRUNNEL_REND_COOKIE_LEN]; - struct cell_extension_st *extensions; + struct trn_cell_extension_st *extensions; uint8_t onion_key_type; uint16_t onion_key_len; TRUNNEL_DYNARRAY_HEAD(, uint8_t) onion_key; @@ -45,449 +45,449 @@ struct hs_cell_introduce_encrypted_st { uint8_t trunnel_error_code_; }; #endif -typedef struct hs_cell_introduce_encrypted_st hs_cell_introduce_encrypted_t; -/** Return a newly allocated hs_cell_introduce1 with all elements set +typedef struct trn_cell_introduce_encrypted_st trn_cell_introduce_encrypted_t; +/** Return a newly allocated trn_cell_introduce1 with all elements set * to zero. */ -hs_cell_introduce1_t *hs_cell_introduce1_new(void); -/** Release all storage held by the hs_cell_introduce1 in 'victim'. +trn_cell_introduce1_t *trn_cell_introduce1_new(void); +/** Release all storage held by the trn_cell_introduce1 in 'victim'. * (Do nothing if 'victim' is NULL.) */ -void hs_cell_introduce1_free(hs_cell_introduce1_t *victim); -/** Try to parse a hs_cell_introduce1 from the buffer in 'input', +void trn_cell_introduce1_free(trn_cell_introduce1_t *victim); +/** Try to parse a trn_cell_introduce1 from the buffer in 'input', * using up to 'len_in' bytes from the input buffer. On success, * return the number of bytes consumed and set *output to the newly - * allocated hs_cell_introduce1_t. On failure, return -2 if the input + * allocated trn_cell_introduce1_t. On failure, return -2 if the input * appears truncated, and -1 if the input is otherwise invalid. */ -ssize_t hs_cell_introduce1_parse(hs_cell_introduce1_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_introduce1_parse(trn_cell_introduce1_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * hs_cell_introduce1 in 'obj'. On failure, return a negative value. + * trn_cell_introduce1 in 'obj'. On failure, return a negative value. * Note that this value may be an overestimate, and can even be an * underestimate for certain unencodeable objects. */ -ssize_t hs_cell_introduce1_encoded_len(const hs_cell_introduce1_t *obj); -/** Try to encode the hs_cell_introduce1 from 'input' into the buffer +ssize_t trn_cell_introduce1_encoded_len(const trn_cell_introduce1_t *obj); +/** Try to encode the trn_cell_introduce1 from 'input' into the buffer * at 'output', using up to 'avail' bytes of the output buffer. On * success, return the number of bytes used. On failure, return -2 if * the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t hs_cell_introduce1_encode(uint8_t *output, size_t avail, const hs_cell_introduce1_t *input); -/** Check whether the internal state of the hs_cell_introduce1 in +ssize_t trn_cell_introduce1_encode(uint8_t *output, size_t avail, const trn_cell_introduce1_t *input); +/** Check whether the internal state of the trn_cell_introduce1 in * 'obj' is consistent. Return NULL if it is, and a short message if * it is not. */ -const char *hs_cell_introduce1_check(const hs_cell_introduce1_t *obj); +const char *trn_cell_introduce1_check(const trn_cell_introduce1_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int hs_cell_introduce1_clear_errors(hs_cell_introduce1_t *obj); +int trn_cell_introduce1_clear_errors(trn_cell_introduce1_t *obj); /** Return the (constant) length of the array holding the - * legacy_key_id field of the hs_cell_introduce1_t in 'inp'. + * legacy_key_id field of the trn_cell_introduce1_t in 'inp'. */ -size_t hs_cell_introduce1_getlen_legacy_key_id(const hs_cell_introduce1_t *inp); +size_t trn_cell_introduce1_getlen_legacy_key_id(const trn_cell_introduce1_t *inp); /** Return the element at position 'idx' of the fixed array field - * legacy_key_id of the hs_cell_introduce1_t in 'inp'. + * legacy_key_id of the trn_cell_introduce1_t in 'inp'. */ -uint8_t hs_cell_introduce1_get_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx); -/** As hs_cell_introduce1_get_legacy_key_id, but take and return a +uint8_t trn_cell_introduce1_get_legacy_key_id(trn_cell_introduce1_t *inp, size_t idx); +/** As trn_cell_introduce1_get_legacy_key_id, but take and return a * const pointer */ -uint8_t hs_cell_introduce1_getconst_legacy_key_id(const hs_cell_introduce1_t *inp, size_t idx); +uint8_t trn_cell_introduce1_getconst_legacy_key_id(const trn_cell_introduce1_t *inp, size_t idx); /** Change the element at position 'idx' of the fixed array field - * legacy_key_id of the hs_cell_introduce1_t in 'inp', so that it will - * hold the value 'elt'. + * legacy_key_id of the trn_cell_introduce1_t in 'inp', so that it + * will hold the value 'elt'. */ -int hs_cell_introduce1_set_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt); +int trn_cell_introduce1_set_legacy_key_id(trn_cell_introduce1_t *inp, size_t idx, uint8_t elt); /** Return a pointer to the TRUNNEL_SHA1_LEN-element array field * legacy_key_id of 'inp'. */ -uint8_t * hs_cell_introduce1_getarray_legacy_key_id(hs_cell_introduce1_t *inp); -/** As hs_cell_introduce1_get_legacy_key_id, but take and return a +uint8_t * trn_cell_introduce1_getarray_legacy_key_id(trn_cell_introduce1_t *inp); +/** As trn_cell_introduce1_get_legacy_key_id, but take and return a * const pointer */ -const uint8_t * hs_cell_introduce1_getconstarray_legacy_key_id(const hs_cell_introduce1_t *inp); +const uint8_t * trn_cell_introduce1_getconstarray_legacy_key_id(const trn_cell_introduce1_t *inp); /** Return the value of the auth_key_type field of the - * hs_cell_introduce1_t in 'inp' + * trn_cell_introduce1_t in 'inp' */ -uint8_t hs_cell_introduce1_get_auth_key_type(const hs_cell_introduce1_t *inp); +uint8_t trn_cell_introduce1_get_auth_key_type(const trn_cell_introduce1_t *inp); /** Set the value of the auth_key_type field of the - * hs_cell_introduce1_t in 'inp' to 'val'. Return 0 on success; return - * -1 and set the error code on 'inp' on failure. + * trn_cell_introduce1_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce1_set_auth_key_type(hs_cell_introduce1_t *inp, uint8_t val); +int trn_cell_introduce1_set_auth_key_type(trn_cell_introduce1_t *inp, uint8_t val); /** Return the value of the auth_key_len field of the - * hs_cell_introduce1_t in 'inp' + * trn_cell_introduce1_t in 'inp' */ -uint16_t hs_cell_introduce1_get_auth_key_len(const hs_cell_introduce1_t *inp); +uint16_t trn_cell_introduce1_get_auth_key_len(const trn_cell_introduce1_t *inp); /** Set the value of the auth_key_len field of the - * hs_cell_introduce1_t in 'inp' to 'val'. Return 0 on success; return - * -1 and set the error code on 'inp' on failure. + * trn_cell_introduce1_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce1_set_auth_key_len(hs_cell_introduce1_t *inp, uint16_t val); +int trn_cell_introduce1_set_auth_key_len(trn_cell_introduce1_t *inp, uint16_t val); /** Return the length of the dynamic array holding the auth_key field - * of the hs_cell_introduce1_t in 'inp'. + * of the trn_cell_introduce1_t in 'inp'. */ -size_t hs_cell_introduce1_getlen_auth_key(const hs_cell_introduce1_t *inp); +size_t trn_cell_introduce1_getlen_auth_key(const trn_cell_introduce1_t *inp); /** Return the element at position 'idx' of the dynamic array field - * auth_key of the hs_cell_introduce1_t in 'inp'. + * auth_key of the trn_cell_introduce1_t in 'inp'. */ -uint8_t hs_cell_introduce1_get_auth_key(hs_cell_introduce1_t *inp, size_t idx); -/** As hs_cell_introduce1_get_auth_key, but take and return a const +uint8_t trn_cell_introduce1_get_auth_key(trn_cell_introduce1_t *inp, size_t idx); +/** As trn_cell_introduce1_get_auth_key, but take and return a const * pointer */ -uint8_t hs_cell_introduce1_getconst_auth_key(const hs_cell_introduce1_t *inp, size_t idx); +uint8_t trn_cell_introduce1_getconst_auth_key(const trn_cell_introduce1_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * auth_key of the hs_cell_introduce1_t in 'inp', so that it will hold - * the value 'elt'. + * auth_key of the trn_cell_introduce1_t in 'inp', so that it will + * hold the value 'elt'. */ -int hs_cell_introduce1_set_auth_key(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt); +int trn_cell_introduce1_set_auth_key(trn_cell_introduce1_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field auth_key of - * the hs_cell_introduce1_t in 'inp'. + * the trn_cell_introduce1_t in 'inp'. */ -int hs_cell_introduce1_add_auth_key(hs_cell_introduce1_t *inp, uint8_t elt); +int trn_cell_introduce1_add_auth_key(trn_cell_introduce1_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field auth_key of * 'inp'. */ -uint8_t * hs_cell_introduce1_getarray_auth_key(hs_cell_introduce1_t *inp); -/** As hs_cell_introduce1_get_auth_key, but take and return a const +uint8_t * trn_cell_introduce1_getarray_auth_key(trn_cell_introduce1_t *inp); +/** As trn_cell_introduce1_get_auth_key, but take and return a const * pointer */ -const uint8_t * hs_cell_introduce1_getconstarray_auth_key(const hs_cell_introduce1_t *inp); +const uint8_t * trn_cell_introduce1_getconstarray_auth_key(const trn_cell_introduce1_t *inp); /** Change the length of the variable-length array field auth_key of * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce1_setlen_auth_key(hs_cell_introduce1_t *inp, size_t newlen); +int trn_cell_introduce1_setlen_auth_key(trn_cell_introduce1_t *inp, size_t newlen); /** Return the value of the extensions field of the - * hs_cell_introduce1_t in 'inp' + * trn_cell_introduce1_t in 'inp' */ -struct cell_extension_st * hs_cell_introduce1_get_extensions(hs_cell_introduce1_t *inp); -/** As hs_cell_introduce1_get_extensions, but take and return a const +struct trn_cell_extension_st * trn_cell_introduce1_get_extensions(trn_cell_introduce1_t *inp); +/** As trn_cell_introduce1_get_extensions, but take and return a const * pointer */ -const struct cell_extension_st * hs_cell_introduce1_getconst_extensions(const hs_cell_introduce1_t *inp); -/** Set the value of the extensions field of the hs_cell_introduce1_t +const struct trn_cell_extension_st * trn_cell_introduce1_getconst_extensions(const trn_cell_introduce1_t *inp); +/** Set the value of the extensions field of the trn_cell_introduce1_t * in 'inp' to 'val'. Free the old value if any. Steals the * referenceto 'val'.Return 0 on success; return -1 and set the error * code on 'inp' on failure. */ -int hs_cell_introduce1_set_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val); -/** As hs_cell_introduce1_set_extensions, but does not free the +int trn_cell_introduce1_set_extensions(trn_cell_introduce1_t *inp, struct trn_cell_extension_st *val); +/** As trn_cell_introduce1_set_extensions, but does not free the * previous value. */ -int hs_cell_introduce1_set0_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val); +int trn_cell_introduce1_set0_extensions(trn_cell_introduce1_t *inp, struct trn_cell_extension_st *val); /** Return the length of the dynamic array holding the encrypted field - * of the hs_cell_introduce1_t in 'inp'. + * of the trn_cell_introduce1_t in 'inp'. */ -size_t hs_cell_introduce1_getlen_encrypted(const hs_cell_introduce1_t *inp); +size_t trn_cell_introduce1_getlen_encrypted(const trn_cell_introduce1_t *inp); /** Return the element at position 'idx' of the dynamic array field - * encrypted of the hs_cell_introduce1_t in 'inp'. + * encrypted of the trn_cell_introduce1_t in 'inp'. */ -uint8_t hs_cell_introduce1_get_encrypted(hs_cell_introduce1_t *inp, size_t idx); -/** As hs_cell_introduce1_get_encrypted, but take and return a const +uint8_t trn_cell_introduce1_get_encrypted(trn_cell_introduce1_t *inp, size_t idx); +/** As trn_cell_introduce1_get_encrypted, but take and return a const * pointer */ -uint8_t hs_cell_introduce1_getconst_encrypted(const hs_cell_introduce1_t *inp, size_t idx); +uint8_t trn_cell_introduce1_getconst_encrypted(const trn_cell_introduce1_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * encrypted of the hs_cell_introduce1_t in 'inp', so that it will + * encrypted of the trn_cell_introduce1_t in 'inp', so that it will * hold the value 'elt'. */ -int hs_cell_introduce1_set_encrypted(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt); +int trn_cell_introduce1_set_encrypted(trn_cell_introduce1_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field encrypted of - * the hs_cell_introduce1_t in 'inp'. + * the trn_cell_introduce1_t in 'inp'. */ -int hs_cell_introduce1_add_encrypted(hs_cell_introduce1_t *inp, uint8_t elt); +int trn_cell_introduce1_add_encrypted(trn_cell_introduce1_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field encrypted of * 'inp'. */ -uint8_t * hs_cell_introduce1_getarray_encrypted(hs_cell_introduce1_t *inp); -/** As hs_cell_introduce1_get_encrypted, but take and return a const +uint8_t * trn_cell_introduce1_getarray_encrypted(trn_cell_introduce1_t *inp); +/** As trn_cell_introduce1_get_encrypted, but take and return a const * pointer */ -const uint8_t * hs_cell_introduce1_getconstarray_encrypted(const hs_cell_introduce1_t *inp); +const uint8_t * trn_cell_introduce1_getconstarray_encrypted(const trn_cell_introduce1_t *inp); /** Change the length of the variable-length array field encrypted of * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce1_setlen_encrypted(hs_cell_introduce1_t *inp, size_t newlen); -/** Return a newly allocated hs_cell_introduce_ack with all elements +int trn_cell_introduce1_setlen_encrypted(trn_cell_introduce1_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_introduce_ack with all elements * set to zero. */ -hs_cell_introduce_ack_t *hs_cell_introduce_ack_new(void); -/** Release all storage held by the hs_cell_introduce_ack in 'victim'. - * (Do nothing if 'victim' is NULL.) +trn_cell_introduce_ack_t *trn_cell_introduce_ack_new(void); +/** Release all storage held by the trn_cell_introduce_ack in + * 'victim'. (Do nothing if 'victim' is NULL.) */ -void hs_cell_introduce_ack_free(hs_cell_introduce_ack_t *victim); -/** Try to parse a hs_cell_introduce_ack from the buffer in 'input', +void trn_cell_introduce_ack_free(trn_cell_introduce_ack_t *victim); +/** Try to parse a trn_cell_introduce_ack from the buffer in 'input', * using up to 'len_in' bytes from the input buffer. On success, * return the number of bytes consumed and set *output to the newly - * allocated hs_cell_introduce_ack_t. On failure, return -2 if the + * allocated trn_cell_introduce_ack_t. On failure, return -2 if the * input appears truncated, and -1 if the input is otherwise invalid. */ -ssize_t hs_cell_introduce_ack_parse(hs_cell_introduce_ack_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_introduce_ack_parse(trn_cell_introduce_ack_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * hs_cell_introduce_ack in 'obj'. On failure, return a negative + * trn_cell_introduce_ack in 'obj'. On failure, return a negative * value. Note that this value may be an overestimate, and can even be * an underestimate for certain unencodeable objects. */ -ssize_t hs_cell_introduce_ack_encoded_len(const hs_cell_introduce_ack_t *obj); -/** Try to encode the hs_cell_introduce_ack from 'input' into the +ssize_t trn_cell_introduce_ack_encoded_len(const trn_cell_introduce_ack_t *obj); +/** Try to encode the trn_cell_introduce_ack from 'input' into the * buffer at 'output', using up to 'avail' bytes of the output buffer. * On success, return the number of bytes used. On failure, return -2 * if the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t hs_cell_introduce_ack_encode(uint8_t *output, size_t avail, const hs_cell_introduce_ack_t *input); -/** Check whether the internal state of the hs_cell_introduce_ack in +ssize_t trn_cell_introduce_ack_encode(uint8_t *output, size_t avail, const trn_cell_introduce_ack_t *input); +/** Check whether the internal state of the trn_cell_introduce_ack in * 'obj' is consistent. Return NULL if it is, and a short message if * it is not. */ -const char *hs_cell_introduce_ack_check(const hs_cell_introduce_ack_t *obj); +const char *trn_cell_introduce_ack_check(const trn_cell_introduce_ack_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int hs_cell_introduce_ack_clear_errors(hs_cell_introduce_ack_t *obj); +int trn_cell_introduce_ack_clear_errors(trn_cell_introduce_ack_t *obj); /** Return the value of the status field of the - * hs_cell_introduce_ack_t in 'inp' + * trn_cell_introduce_ack_t in 'inp' */ -uint16_t hs_cell_introduce_ack_get_status(const hs_cell_introduce_ack_t *inp); -/** Set the value of the status field of the hs_cell_introduce_ack_t +uint16_t trn_cell_introduce_ack_get_status(const trn_cell_introduce_ack_t *inp); +/** Set the value of the status field of the trn_cell_introduce_ack_t * in 'inp' to 'val'. Return 0 on success; return -1 and set the error * code on 'inp' on failure. */ -int hs_cell_introduce_ack_set_status(hs_cell_introduce_ack_t *inp, uint16_t val); +int trn_cell_introduce_ack_set_status(trn_cell_introduce_ack_t *inp, uint16_t val); /** Return the value of the extensions field of the - * hs_cell_introduce_ack_t in 'inp' + * trn_cell_introduce_ack_t in 'inp' */ -struct cell_extension_st * hs_cell_introduce_ack_get_extensions(hs_cell_introduce_ack_t *inp); -/** As hs_cell_introduce_ack_get_extensions, but take and return a +struct trn_cell_extension_st * trn_cell_introduce_ack_get_extensions(trn_cell_introduce_ack_t *inp); +/** As trn_cell_introduce_ack_get_extensions, but take and return a * const pointer */ -const struct cell_extension_st * hs_cell_introduce_ack_getconst_extensions(const hs_cell_introduce_ack_t *inp); +const struct trn_cell_extension_st * trn_cell_introduce_ack_getconst_extensions(const trn_cell_introduce_ack_t *inp); /** Set the value of the extensions field of the - * hs_cell_introduce_ack_t in 'inp' to 'val'. Free the old value if + * trn_cell_introduce_ack_t in 'inp' to 'val'. Free the old value if * any. Steals the referenceto 'val'.Return 0 on success; return -1 * and set the error code on 'inp' on failure. */ -int hs_cell_introduce_ack_set_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val); -/** As hs_cell_introduce_ack_set_extensions, but does not free the +int trn_cell_introduce_ack_set_extensions(trn_cell_introduce_ack_t *inp, struct trn_cell_extension_st *val); +/** As trn_cell_introduce_ack_set_extensions, but does not free the * previous value. */ -int hs_cell_introduce_ack_set0_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val); -/** Return a newly allocated hs_cell_introduce_encrypted with all +int trn_cell_introduce_ack_set0_extensions(trn_cell_introduce_ack_t *inp, struct trn_cell_extension_st *val); +/** Return a newly allocated trn_cell_introduce_encrypted with all * elements set to zero. */ -hs_cell_introduce_encrypted_t *hs_cell_introduce_encrypted_new(void); -/** Release all storage held by the hs_cell_introduce_encrypted in +trn_cell_introduce_encrypted_t *trn_cell_introduce_encrypted_new(void); +/** Release all storage held by the trn_cell_introduce_encrypted in * 'victim'. (Do nothing if 'victim' is NULL.) */ -void hs_cell_introduce_encrypted_free(hs_cell_introduce_encrypted_t *victim); -/** Try to parse a hs_cell_introduce_encrypted from the buffer in +void trn_cell_introduce_encrypted_free(trn_cell_introduce_encrypted_t *victim); +/** Try to parse a trn_cell_introduce_encrypted from the buffer in * 'input', using up to 'len_in' bytes from the input buffer. On * success, return the number of bytes consumed and set *output to the - * newly allocated hs_cell_introduce_encrypted_t. On failure, return + * newly allocated trn_cell_introduce_encrypted_t. On failure, return * -2 if the input appears truncated, and -1 if the input is otherwise * invalid. */ -ssize_t hs_cell_introduce_encrypted_parse(hs_cell_introduce_encrypted_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_introduce_encrypted_parse(trn_cell_introduce_encrypted_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * hs_cell_introduce_encrypted in 'obj'. On failure, return a negative - * value. Note that this value may be an overestimate, and can even be - * an underestimate for certain unencodeable objects. + * trn_cell_introduce_encrypted in 'obj'. On failure, return a + * negative value. Note that this value may be an overestimate, and + * can even be an underestimate for certain unencodeable objects. */ -ssize_t hs_cell_introduce_encrypted_encoded_len(const hs_cell_introduce_encrypted_t *obj); -/** Try to encode the hs_cell_introduce_encrypted from 'input' into +ssize_t trn_cell_introduce_encrypted_encoded_len(const trn_cell_introduce_encrypted_t *obj); +/** Try to encode the trn_cell_introduce_encrypted from 'input' into * the buffer at 'output', using up to 'avail' bytes of the output * buffer. On success, return the number of bytes used. On failure, * return -2 if the buffer was not long enough, and -1 if the input * was invalid. */ -ssize_t hs_cell_introduce_encrypted_encode(uint8_t *output, size_t avail, const hs_cell_introduce_encrypted_t *input); +ssize_t trn_cell_introduce_encrypted_encode(uint8_t *output, size_t avail, const trn_cell_introduce_encrypted_t *input); /** Check whether the internal state of the - * hs_cell_introduce_encrypted in 'obj' is consistent. Return NULL if + * trn_cell_introduce_encrypted in 'obj' is consistent. Return NULL if * it is, and a short message if it is not. */ -const char *hs_cell_introduce_encrypted_check(const hs_cell_introduce_encrypted_t *obj); +const char *trn_cell_introduce_encrypted_check(const trn_cell_introduce_encrypted_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int hs_cell_introduce_encrypted_clear_errors(hs_cell_introduce_encrypted_t *obj); +int trn_cell_introduce_encrypted_clear_errors(trn_cell_introduce_encrypted_t *obj); /** Return the (constant) length of the array holding the rend_cookie - * field of the hs_cell_introduce_encrypted_t in 'inp'. + * field of the trn_cell_introduce_encrypted_t in 'inp'. */ -size_t hs_cell_introduce_encrypted_getlen_rend_cookie(const hs_cell_introduce_encrypted_t *inp); +size_t trn_cell_introduce_encrypted_getlen_rend_cookie(const trn_cell_introduce_encrypted_t *inp); /** Return the element at position 'idx' of the fixed array field - * rend_cookie of the hs_cell_introduce_encrypted_t in 'inp'. + * rend_cookie of the trn_cell_introduce_encrypted_t in 'inp'. */ -uint8_t hs_cell_introduce_encrypted_get_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx); -/** As hs_cell_introduce_encrypted_get_rend_cookie, but take and +uint8_t trn_cell_introduce_encrypted_get_rend_cookie(trn_cell_introduce_encrypted_t *inp, size_t idx); +/** As trn_cell_introduce_encrypted_get_rend_cookie, but take and * return a const pointer */ -uint8_t hs_cell_introduce_encrypted_getconst_rend_cookie(const hs_cell_introduce_encrypted_t *inp, size_t idx); +uint8_t trn_cell_introduce_encrypted_getconst_rend_cookie(const trn_cell_introduce_encrypted_t *inp, size_t idx); /** Change the element at position 'idx' of the fixed array field - * rend_cookie of the hs_cell_introduce_encrypted_t in 'inp', so that + * rend_cookie of the trn_cell_introduce_encrypted_t in 'inp', so that * it will hold the value 'elt'. */ -int hs_cell_introduce_encrypted_set_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt); +int trn_cell_introduce_encrypted_set_rend_cookie(trn_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt); /** Return a pointer to the TRUNNEL_REND_COOKIE_LEN-element array * field rend_cookie of 'inp'. */ -uint8_t * hs_cell_introduce_encrypted_getarray_rend_cookie(hs_cell_introduce_encrypted_t *inp); -/** As hs_cell_introduce_encrypted_get_rend_cookie, but take and +uint8_t * trn_cell_introduce_encrypted_getarray_rend_cookie(trn_cell_introduce_encrypted_t *inp); +/** As trn_cell_introduce_encrypted_get_rend_cookie, but take and * return a const pointer */ -const uint8_t * hs_cell_introduce_encrypted_getconstarray_rend_cookie(const hs_cell_introduce_encrypted_t *inp); +const uint8_t * trn_cell_introduce_encrypted_getconstarray_rend_cookie(const trn_cell_introduce_encrypted_t *inp); /** Return the value of the extensions field of the - * hs_cell_introduce_encrypted_t in 'inp' + * trn_cell_introduce_encrypted_t in 'inp' */ -struct cell_extension_st * hs_cell_introduce_encrypted_get_extensions(hs_cell_introduce_encrypted_t *inp); -/** As hs_cell_introduce_encrypted_get_extensions, but take and return - * a const pointer +struct trn_cell_extension_st * trn_cell_introduce_encrypted_get_extensions(trn_cell_introduce_encrypted_t *inp); +/** As trn_cell_introduce_encrypted_get_extensions, but take and + * return a const pointer */ -const struct cell_extension_st * hs_cell_introduce_encrypted_getconst_extensions(const hs_cell_introduce_encrypted_t *inp); +const struct trn_cell_extension_st * trn_cell_introduce_encrypted_getconst_extensions(const trn_cell_introduce_encrypted_t *inp); /** Set the value of the extensions field of the - * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Free the old value - * if any. Steals the referenceto 'val'.Return 0 on success; return -1 - * and set the error code on 'inp' on failure. + * trn_cell_introduce_encrypted_t in 'inp' to 'val'. Free the old + * value if any. Steals the referenceto 'val'.Return 0 on success; + * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce_encrypted_set_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val); -/** As hs_cell_introduce_encrypted_set_extensions, but does not free +int trn_cell_introduce_encrypted_set_extensions(trn_cell_introduce_encrypted_t *inp, struct trn_cell_extension_st *val); +/** As trn_cell_introduce_encrypted_set_extensions, but does not free * the previous value. */ -int hs_cell_introduce_encrypted_set0_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val); +int trn_cell_introduce_encrypted_set0_extensions(trn_cell_introduce_encrypted_t *inp, struct trn_cell_extension_st *val); /** Return the value of the onion_key_type field of the - * hs_cell_introduce_encrypted_t in 'inp' + * trn_cell_introduce_encrypted_t in 'inp' */ -uint8_t hs_cell_introduce_encrypted_get_onion_key_type(const hs_cell_introduce_encrypted_t *inp); +uint8_t trn_cell_introduce_encrypted_get_onion_key_type(const trn_cell_introduce_encrypted_t *inp); /** Set the value of the onion_key_type field of the - * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on + * trn_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on * success; return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce_encrypted_set_onion_key_type(hs_cell_introduce_encrypted_t *inp, uint8_t val); +int trn_cell_introduce_encrypted_set_onion_key_type(trn_cell_introduce_encrypted_t *inp, uint8_t val); /** Return the value of the onion_key_len field of the - * hs_cell_introduce_encrypted_t in 'inp' + * trn_cell_introduce_encrypted_t in 'inp' */ -uint16_t hs_cell_introduce_encrypted_get_onion_key_len(const hs_cell_introduce_encrypted_t *inp); +uint16_t trn_cell_introduce_encrypted_get_onion_key_len(const trn_cell_introduce_encrypted_t *inp); /** Set the value of the onion_key_len field of the - * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on + * trn_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on * success; return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce_encrypted_set_onion_key_len(hs_cell_introduce_encrypted_t *inp, uint16_t val); +int trn_cell_introduce_encrypted_set_onion_key_len(trn_cell_introduce_encrypted_t *inp, uint16_t val); /** Return the length of the dynamic array holding the onion_key field - * of the hs_cell_introduce_encrypted_t in 'inp'. + * of the trn_cell_introduce_encrypted_t in 'inp'. */ -size_t hs_cell_introduce_encrypted_getlen_onion_key(const hs_cell_introduce_encrypted_t *inp); +size_t trn_cell_introduce_encrypted_getlen_onion_key(const trn_cell_introduce_encrypted_t *inp); /** Return the element at position 'idx' of the dynamic array field - * onion_key of the hs_cell_introduce_encrypted_t in 'inp'. + * onion_key of the trn_cell_introduce_encrypted_t in 'inp'. */ -uint8_t hs_cell_introduce_encrypted_get_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx); -/** As hs_cell_introduce_encrypted_get_onion_key, but take and return +uint8_t trn_cell_introduce_encrypted_get_onion_key(trn_cell_introduce_encrypted_t *inp, size_t idx); +/** As trn_cell_introduce_encrypted_get_onion_key, but take and return * a const pointer */ -uint8_t hs_cell_introduce_encrypted_getconst_onion_key(const hs_cell_introduce_encrypted_t *inp, size_t idx); +uint8_t trn_cell_introduce_encrypted_getconst_onion_key(const trn_cell_introduce_encrypted_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * onion_key of the hs_cell_introduce_encrypted_t in 'inp', so that it - * will hold the value 'elt'. + * onion_key of the trn_cell_introduce_encrypted_t in 'inp', so that + * it will hold the value 'elt'. */ -int hs_cell_introduce_encrypted_set_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt); +int trn_cell_introduce_encrypted_set_onion_key(trn_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field onion_key of - * the hs_cell_introduce_encrypted_t in 'inp'. + * the trn_cell_introduce_encrypted_t in 'inp'. */ -int hs_cell_introduce_encrypted_add_onion_key(hs_cell_introduce_encrypted_t *inp, uint8_t elt); +int trn_cell_introduce_encrypted_add_onion_key(trn_cell_introduce_encrypted_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field onion_key of * 'inp'. */ -uint8_t * hs_cell_introduce_encrypted_getarray_onion_key(hs_cell_introduce_encrypted_t *inp); -/** As hs_cell_introduce_encrypted_get_onion_key, but take and return +uint8_t * trn_cell_introduce_encrypted_getarray_onion_key(trn_cell_introduce_encrypted_t *inp); +/** As trn_cell_introduce_encrypted_get_onion_key, but take and return * a const pointer */ -const uint8_t * hs_cell_introduce_encrypted_getconstarray_onion_key(const hs_cell_introduce_encrypted_t *inp); +const uint8_t * trn_cell_introduce_encrypted_getconstarray_onion_key(const trn_cell_introduce_encrypted_t *inp); /** Change the length of the variable-length array field onion_key of * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce_encrypted_setlen_onion_key(hs_cell_introduce_encrypted_t *inp, size_t newlen); +int trn_cell_introduce_encrypted_setlen_onion_key(trn_cell_introduce_encrypted_t *inp, size_t newlen); /** Return the value of the nspec field of the - * hs_cell_introduce_encrypted_t in 'inp' + * trn_cell_introduce_encrypted_t in 'inp' */ -uint8_t hs_cell_introduce_encrypted_get_nspec(const hs_cell_introduce_encrypted_t *inp); +uint8_t trn_cell_introduce_encrypted_get_nspec(const trn_cell_introduce_encrypted_t *inp); /** Set the value of the nspec field of the - * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on + * trn_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on * success; return -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce_encrypted_set_nspec(hs_cell_introduce_encrypted_t *inp, uint8_t val); +int trn_cell_introduce_encrypted_set_nspec(trn_cell_introduce_encrypted_t *inp, uint8_t val); /** Return the length of the dynamic array holding the nspecs field of - * the hs_cell_introduce_encrypted_t in 'inp'. + * the trn_cell_introduce_encrypted_t in 'inp'. */ -size_t hs_cell_introduce_encrypted_getlen_nspecs(const hs_cell_introduce_encrypted_t *inp); +size_t trn_cell_introduce_encrypted_getlen_nspecs(const trn_cell_introduce_encrypted_t *inp); /** Return the element at position 'idx' of the dynamic array field - * nspecs of the hs_cell_introduce_encrypted_t in 'inp'. + * nspecs of the trn_cell_introduce_encrypted_t in 'inp'. */ -struct link_specifier_st * hs_cell_introduce_encrypted_get_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx); -/** As hs_cell_introduce_encrypted_get_nspecs, but take and return a +struct link_specifier_st * trn_cell_introduce_encrypted_get_nspecs(trn_cell_introduce_encrypted_t *inp, size_t idx); +/** As trn_cell_introduce_encrypted_get_nspecs, but take and return a * const pointer */ - const struct link_specifier_st * hs_cell_introduce_encrypted_getconst_nspecs(const hs_cell_introduce_encrypted_t *inp, size_t idx); + const struct link_specifier_st * trn_cell_introduce_encrypted_getconst_nspecs(const trn_cell_introduce_encrypted_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * nspecs of the hs_cell_introduce_encrypted_t in 'inp', so that it + * nspecs of the trn_cell_introduce_encrypted_t in 'inp', so that it * will hold the value 'elt'. Free the previous value, if any. */ -int hs_cell_introduce_encrypted_set_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt); -/** As hs_cell_introduce_encrypted_set_nspecs, but does not free the +int trn_cell_introduce_encrypted_set_nspecs(trn_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt); +/** As trn_cell_introduce_encrypted_set_nspecs, but does not free the * previous value. */ -int hs_cell_introduce_encrypted_set0_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt); +int trn_cell_introduce_encrypted_set0_nspecs(trn_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt); /** Append a new element 'elt' to the dynamic array field nspecs of - * the hs_cell_introduce_encrypted_t in 'inp'. + * the trn_cell_introduce_encrypted_t in 'inp'. */ -int hs_cell_introduce_encrypted_add_nspecs(hs_cell_introduce_encrypted_t *inp, struct link_specifier_st * elt); +int trn_cell_introduce_encrypted_add_nspecs(trn_cell_introduce_encrypted_t *inp, struct link_specifier_st * elt); /** Return a pointer to the variable-length array field nspecs of * 'inp'. */ -struct link_specifier_st * * hs_cell_introduce_encrypted_getarray_nspecs(hs_cell_introduce_encrypted_t *inp); -/** As hs_cell_introduce_encrypted_get_nspecs, but take and return a +struct link_specifier_st * * trn_cell_introduce_encrypted_getarray_nspecs(trn_cell_introduce_encrypted_t *inp); +/** As trn_cell_introduce_encrypted_get_nspecs, but take and return a * const pointer */ -const struct link_specifier_st * const * hs_cell_introduce_encrypted_getconstarray_nspecs(const hs_cell_introduce_encrypted_t *inp); +const struct link_specifier_st * const * trn_cell_introduce_encrypted_getconstarray_nspecs(const trn_cell_introduce_encrypted_t *inp); /** Change the length of the variable-length array field nspecs of * 'inp' to 'newlen'.Fill extra elements with NULL; free removed * elements. Return 0 on success; return -1 and set the error code on * 'inp' on failure. */ -int hs_cell_introduce_encrypted_setlen_nspecs(hs_cell_introduce_encrypted_t *inp, size_t newlen); +int trn_cell_introduce_encrypted_setlen_nspecs(trn_cell_introduce_encrypted_t *inp, size_t newlen); /** Return the length of the dynamic array holding the pad field of - * the hs_cell_introduce_encrypted_t in 'inp'. + * the trn_cell_introduce_encrypted_t in 'inp'. */ -size_t hs_cell_introduce_encrypted_getlen_pad(const hs_cell_introduce_encrypted_t *inp); +size_t trn_cell_introduce_encrypted_getlen_pad(const trn_cell_introduce_encrypted_t *inp); /** Return the element at position 'idx' of the dynamic array field - * pad of the hs_cell_introduce_encrypted_t in 'inp'. + * pad of the trn_cell_introduce_encrypted_t in 'inp'. */ -uint8_t hs_cell_introduce_encrypted_get_pad(hs_cell_introduce_encrypted_t *inp, size_t idx); -/** As hs_cell_introduce_encrypted_get_pad, but take and return a +uint8_t trn_cell_introduce_encrypted_get_pad(trn_cell_introduce_encrypted_t *inp, size_t idx); +/** As trn_cell_introduce_encrypted_get_pad, but take and return a * const pointer */ -uint8_t hs_cell_introduce_encrypted_getconst_pad(const hs_cell_introduce_encrypted_t *inp, size_t idx); +uint8_t trn_cell_introduce_encrypted_getconst_pad(const trn_cell_introduce_encrypted_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * pad of the hs_cell_introduce_encrypted_t in 'inp', so that it will + * pad of the trn_cell_introduce_encrypted_t in 'inp', so that it will * hold the value 'elt'. */ -int hs_cell_introduce_encrypted_set_pad(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt); +int trn_cell_introduce_encrypted_set_pad(trn_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field pad of the - * hs_cell_introduce_encrypted_t in 'inp'. + * trn_cell_introduce_encrypted_t in 'inp'. */ -int hs_cell_introduce_encrypted_add_pad(hs_cell_introduce_encrypted_t *inp, uint8_t elt); +int trn_cell_introduce_encrypted_add_pad(trn_cell_introduce_encrypted_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field pad of 'inp'. */ -uint8_t * hs_cell_introduce_encrypted_getarray_pad(hs_cell_introduce_encrypted_t *inp); -/** As hs_cell_introduce_encrypted_get_pad, but take and return a +uint8_t * trn_cell_introduce_encrypted_getarray_pad(trn_cell_introduce_encrypted_t *inp); +/** As trn_cell_introduce_encrypted_get_pad, but take and return a * const pointer */ -const uint8_t * hs_cell_introduce_encrypted_getconstarray_pad(const hs_cell_introduce_encrypted_t *inp); +const uint8_t * trn_cell_introduce_encrypted_getconstarray_pad(const trn_cell_introduce_encrypted_t *inp); /** Change the length of the variable-length array field pad of 'inp' * to 'newlen'.Fill extra elements with 0. Return 0 on success; return * -1 and set the error code on 'inp' on failure. */ -int hs_cell_introduce_encrypted_setlen_pad(hs_cell_introduce_encrypted_t *inp, size_t newlen); +int trn_cell_introduce_encrypted_setlen_pad(trn_cell_introduce_encrypted_t *inp, size_t newlen); #endif diff --git a/src/trunnel/hs/cell_introduce1.trunnel b/src/trunnel/hs/cell_introduce1.trunnel index f7776879cd..7577c1526f 100644 --- a/src/trunnel/hs/cell_introduce1.trunnel +++ b/src/trunnel/hs/cell_introduce1.trunnel @@ -5,7 +5,7 @@ */ /* From cell_common.trunnel. */ -extern struct cell_extension; +extern struct trn_cell_extension; /* From ed25519_cert.trunnel. */ extern struct link_specifier; @@ -13,7 +13,7 @@ const TRUNNEL_SHA1_LEN = 20; const TRUNNEL_REND_COOKIE_LEN = 20; /* INTRODUCE1 payload. See details in section 3.2.1. */ -struct hs_cell_introduce1 { +struct trn_cell_introduce1 { /* Always zeroed. MUST be checked explicitely by the caller. */ u8 legacy_key_id[TRUNNEL_SHA1_LEN]; @@ -23,28 +23,28 @@ struct hs_cell_introduce1 { u8 auth_key[auth_key_len]; /* Extension(s). Reserved fields. */ - struct cell_extension extensions; + struct trn_cell_extension extensions; /* Variable length, up to the end of cell. */ u8 encrypted[]; }; /* INTRODUCE_ACK payload. See details in section 3.2.2. */ -struct hs_cell_introduce_ack { +struct trn_cell_introduce_ack { /* Status of introduction. */ u16 status IN [0x0000, 0x0001, 0x0002]; /* Extension(s). Reserved fields. */ - struct cell_extension extensions; + struct trn_cell_extension extensions; }; /* Encrypted section of the INTRODUCE1/INTRODUCE2 cell. */ -struct hs_cell_introduce_encrypted { +struct trn_cell_introduce_encrypted { /* Rendezvous cookie. */ u8 rend_cookie[TRUNNEL_REND_COOKIE_LEN]; /* Extension(s). Reserved fields. */ - struct cell_extension extensions; + struct trn_cell_extension extensions; /* Onion key material. */ u8 onion_key_type IN [0x01]; diff --git a/src/trunnel/include.am b/src/trunnel/include.am index 9b26d58615..de6cf4781f 100644 --- a/src/trunnel/include.am +++ b/src/trunnel/include.am @@ -11,7 +11,8 @@ AM_CPPFLAGS += -I$(srcdir)/src/ext/trunnel -I$(srcdir)/src/trunnel TRUNNELINPUTS = \ src/trunnel/ed25519_cert.trunnel \ src/trunnel/link_handshake.trunnel \ - src/trunnel/pwbox.trunnel + src/trunnel/pwbox.trunnel \ + src/trunnel/channelpadding_negotiation.trunnel TRUNNELSOURCES = \ src/ext/trunnel/trunnel.c \ @@ -20,7 +21,8 @@ TRUNNELSOURCES = \ src/trunnel/pwbox.c \ src/trunnel/hs/cell_common.c \ src/trunnel/hs/cell_establish_intro.c \ - src/trunnel/hs/cell_introduce1.c + src/trunnel/hs/cell_introduce1.c \ + src/trunnel/channelpadding_negotiation.c TRUNNELHEADERS = \ src/ext/trunnel/trunnel.h \ @@ -31,7 +33,8 @@ TRUNNELHEADERS = \ src/trunnel/pwbox.h \ src/trunnel/hs/cell_common.h \ src/trunnel/hs/cell_establish_intro.h \ - src/trunnel/hs/cell_introduce1.h + src/trunnel/hs/cell_introduce1.h \ + src/trunnel/channelpadding_negotiation.h src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES) src_trunnel_libor_trunnel_a_CPPFLAGS = -DTRUNNEL_LOCAL_H $(AM_CPPFLAGS) diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index 0b09a5ca50..3ecf024829 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -218,7 +218,7 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.3.0.13-dev" +#define VERSION "0.3.1.9-dev" |