diff options
Diffstat (limited to 'src')
134 files changed, 14223 insertions, 4061 deletions
diff --git a/src/common/address.c b/src/common/address.c index 14a7b6bc9..b9f2d9315 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -14,6 +14,7 @@ #include "address.h" #include "torlog.h" #include "container.h" +#include "sandbox.h" #ifdef _WIN32 #include <process.h> @@ -234,7 +235,7 @@ tor_addr_lookup(const char *name, uint16_t family, tor_addr_t *addr) memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; - err = getaddrinfo(name, NULL, &hints, &res); + err = sandbox_getaddrinfo(name, NULL, &hints, &res); if (!err) { best = NULL; for (res_p = res; res_p; res_p = res_p->ai_next) { diff --git a/src/common/backtrace.c b/src/common/backtrace.c new file mode 100644 index 000000000..5049298a1 --- /dev/null +++ b/src/common/backtrace.c @@ -0,0 +1,205 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define __USE_GNU +#define _GNU_SOURCE 1 + +#include "orconfig.h" +#include "backtrace.h" +#include "compat.h" +#include "util.h" +#include "torlog.h" + +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif + +#ifdef HAVE_CYGWIN_SIGNAL_H +#include <cygwin/signal.h> +#elif defined(HAVE_SYS_UCONTEXT_H) +#include <sys/ucontext.h> +#elif defined(HAVE_UCONTEXT_H) +#include <ucontext.h> +#endif + +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \ + defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION) +#define USE_BACKTRACE +#endif + +#if !defined(USE_BACKTRACE) +#define NO_BACKTRACE_IMPL +#endif + +/** Version of Tor to report in backtrace messages. */ +static char *bt_version = NULL; + +#ifdef USE_BACKTRACE +/** Largest stack depth to try to dump. */ +#define MAX_DEPTH 256 +/** Static allocation of stack to dump. This is static so we avoid stack + * pressure. */ +static void *cb_buf[MAX_DEPTH]; + +/** Change a stacktrace in <b>stack</b> of depth <b>depth</b> so that it will + * log the correct function from which a signal was received with context + * <b>ctx</b>. (When we get a signal, the current function will not have + * called any other function, and will therefore have not pushed its address + * onto the stack. Fortunately, we usually have the program counter in the + * ucontext_t structure. + */ +static void +clean_backtrace(void **stack, int depth, const ucontext_t *ctx) +{ +#ifdef PC_FROM_UCONTEXT +#if defined(__linux__) + const int n = 1; +#elif defined(__darwin__) || defined(__APPLE__) || defined(__OpenBSD__) \ + || defined(__FreeBSD__) + const int n = 2; +#else + const int n = 1; +#endif + if (depth <= n) + return; + + stack[n] = (void*) ctx->PC_FROM_UCONTEXT; +#else + (void) depth; + (void) ctx; +#endif +} + +/** Log a message <b>msg</b> at <b>severity</b> in <b>domain</b>, and follow + * that with a backtrace log. */ +void +log_backtrace(int severity, int domain, const char *msg) +{ + int depth = backtrace(cb_buf, MAX_DEPTH); + char **symbols = backtrace_symbols(cb_buf, depth); + int i; + tor_log(severity, domain, "%s. Stack trace:", msg); + if (!symbols) { + tor_log(severity, domain, " Unable to generate backtrace."); + return; + } + for (i=0; i < depth; ++i) { + tor_log(severity, domain, " %s", symbols[i]); + } + free(symbols); +} + +static void crash_handler(int sig, siginfo_t *si, void *ctx_) + __attribute__((noreturn)); + +/** Signal handler: write a crash message with a stack trace, and die. */ +static void +crash_handler(int sig, siginfo_t *si, void *ctx_) +{ + char buf[40]; + int depth; + ucontext_t *ctx = (ucontext_t *) ctx_; + int n_fds, i; + const int *fds = NULL; + + (void) si; + + depth = backtrace(cb_buf, MAX_DEPTH); + /* Clean up the top stack frame so we get the real function + * name for the most recently failing function. */ + clean_backtrace(cb_buf, depth, ctx); + + format_dec_number_sigsafe((unsigned)sig, buf, sizeof(buf)); + + tor_log_err_sigsafe(bt_version, " died: Caught signal ", buf, "\n", + NULL); + + n_fds = tor_log_get_sigsafe_err_fds(&fds); + for (i=0; i < n_fds; ++i) + backtrace_symbols_fd(cb_buf, depth, fds[i]); + + abort(); +} + +/** Install signal handlers as needed so that when we crash, we produce a + * useful stack trace. Return 0 on success, -1 on failure. */ +static int +install_bt_handler(void) +{ + int trap_signals[] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGSYS, + SIGIO, -1 }; + int i, rv=0; + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_handler; + sa.sa_flags = SA_SIGINFO; + sigfillset(&sa.sa_mask); + + for (i = 0; trap_signals[i] >= 0; ++i) { + if (sigaction(trap_signals[i], &sa, NULL) == -1) { + log_warn(LD_BUG, "Sigaction failed: %s", strerror(errno)); + rv = -1; + } + } + return rv; +} + +/** Uninstall crash handlers. */ +static void +remove_bt_handler(void) +{ + /* We don't need to actually free anything at exit here. */ +} +#endif + +#ifdef NO_BACKTRACE_IMPL +void +log_backtrace(int severity, int domain, const char *msg) +{ + tor_log(severity, domain, "%s. (Stack trace not available)", msg); +} + +static int +install_bt_handler(void) +{ + return 0; +} + +static void +remove_bt_handler(void) +{ +} +#endif + +/** Set up code to handle generating error messages on crashes. */ +int +configure_backtrace_handler(const char *tor_version) +{ + tor_free(bt_version); + if (!tor_version) + tor_version = ""; + tor_asprintf(&bt_version, "Tor %s", tor_version); + + return install_bt_handler(); +} + +/** Perform end-of-process cleanup for code that generates error messages on + * crashes. */ +void +clean_up_backtrace_handler(void) +{ + remove_bt_handler(); + + tor_free(bt_version); +} + diff --git a/src/common/backtrace.h b/src/common/backtrace.h new file mode 100644 index 000000000..765436fee --- /dev/null +++ b/src/common/backtrace.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BACKTRACE_H +#define TOR_BACKTRACE_H + +void log_backtrace(int severity, int domain, const char *msg); +int configure_backtrace_handler(const char *tor_version); +void clean_up_backtrace_handler(void); + +#endif + diff --git a/src/common/compat.c b/src/common/compat.c index d88c5f92d..8e2619f84 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -23,6 +23,7 @@ * we can also take out the configure check. */ #define _GNU_SOURCE +#define COMPAT_PRIVATE #include "compat.h" #ifdef _WIN32 @@ -109,6 +110,7 @@ #include "util.h" #include "container.h" #include "address.h" +#include "sandbox.h" /* Inline the strl functions if the platform doesn't have them. */ #ifndef HAVE_STRLCPY @@ -125,6 +127,7 @@ tor_open_cloexec(const char *path, int flags, unsigned mode) { int fd; #ifdef O_CLOEXEC + path = sandbox_intern_string(path); fd = open(path, flags|O_CLOEXEC, mode); if (fd >= 0) return fd; @@ -948,24 +951,40 @@ socket_accounting_unlock(void) } /** As close(), but guaranteed to work for sockets across platforms (including - * Windows, where close()ing a socket doesn't work. Returns 0 on success, -1 - * on failure. */ + * Windows, where close()ing a socket doesn't work. Returns 0 on success and + * the socket error code on failure. */ int -tor_close_socket(tor_socket_t s) +tor_close_socket_simple(tor_socket_t s) { int r = 0; /* On Windows, you have to call close() on fds returned by open(), - * and closesocket() on fds returned by socket(). On Unix, everything - * gets close()'d. We abstract this difference by always using - * tor_close_socket to close sockets, and always using close() on - * files. - */ -#if defined(_WIN32) - r = closesocket(s); -#else - r = close(s); -#endif + * and closesocket() on fds returned by socket(). On Unix, everything + * gets close()'d. We abstract this difference by always using + * tor_close_socket to close sockets, and always using close() on + * files. + */ + #if defined(_WIN32) + r = closesocket(s); + #else + r = close(s); + #endif + + if (r != 0) { + int err = tor_socket_errno(-1); + log_info(LD_NET, "Close returned an error: %s", tor_socket_strerror(err)); + return err; + } + + return r; +} + +/** As tor_close_socket_simple(), but keeps track of the number + * of open sockets. Returns 0 on success, -1 on failure. */ +int +tor_close_socket(tor_socket_t s) +{ + int r = tor_close_socket_simple(s); socket_accounting_lock(); #ifdef DEBUG_SOCKET_COUNTING @@ -980,13 +999,11 @@ tor_close_socket(tor_socket_t s) if (r == 0) { --n_sockets_open; } else { - int err = tor_socket_errno(-1); - log_info(LD_NET, "Close returned an error: %s", tor_socket_strerror(err)); #ifdef _WIN32 - if (err != WSAENOTSOCK) + if (r != WSAENOTSOCK) --n_sockets_open; #else - if (err != EBADF) + if (r != EBADF) --n_sockets_open; #endif r = -1; @@ -1032,33 +1049,61 @@ mark_socket_open(tor_socket_t s) tor_socket_t tor_open_socket(int domain, int type, int protocol) { + return tor_open_socket_with_extensions(domain, type, protocol, 1, 0); +} + +/** As socket(), but creates a nonblocking socket and + * counts the number of open sockets. */ +tor_socket_t +tor_open_socket_nonblocking(int domain, int type, int protocol) +{ + return tor_open_socket_with_extensions(domain, type, protocol, 1, 1); +} + +/** As socket(), but counts the number of open sockets and handles + * socket creation with either of SOCK_CLOEXEC and SOCK_NONBLOCK specified. + * <b>cloexec</b> and <b>nonblock</b> should be either 0 or 1 to indicate + * if the corresponding extension should be used.*/ +tor_socket_t +tor_open_socket_with_extensions(int domain, int type, int protocol, + int cloexec, int nonblock) +{ tor_socket_t s; -#ifdef SOCK_CLOEXEC - s = socket(domain, type|SOCK_CLOEXEC, protocol); +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + int ext_flags = (cloexec ? SOCK_CLOEXEC : 0) | + (nonblock ? SOCK_NONBLOCK : 0); + s = socket(domain, type|ext_flags, protocol); if (SOCKET_OK(s)) goto socket_ok; /* If we got an error, see if it is EINVAL. EINVAL might indicate that, - * even though we were built on a system with SOCK_CLOEXEC support, we - * are running on one without. */ + * even though we were built on a system with SOCK_CLOEXEC and SOCK_NONBLOCK + * support, we are running on one without. */ if (errno != EINVAL) return s; -#endif /* SOCK_CLOEXEC */ +#endif /* SOCK_CLOEXEC && SOCK_NONBLOCK */ s = socket(domain, type, protocol); if (! SOCKET_OK(s)) return s; #if defined(FD_CLOEXEC) - if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { - log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno)); -#if defined(_WIN32) - closesocket(s); + if (cloexec) { + if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { + log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno)); + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } + } #else - close(s); + (void)cloexec; #endif - return -1; + + if (nonblock) { + if (set_socket_nonblocking(s) == -1) { + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } } -#endif goto socket_ok; /* So that socket_ok will not be unused. */ @@ -1070,19 +1115,41 @@ tor_open_socket(int domain, int type, int protocol) return s; } -/** As socket(), but counts the number of open sockets. */ +/** As accept(), but counts the number of open sockets. */ tor_socket_t tor_accept_socket(tor_socket_t sockfd, struct sockaddr *addr, socklen_t *len) { + return tor_accept_socket_with_extensions(sockfd, addr, len, 1, 0); +} + +/** As accept(), but returns a nonblocking socket and + * counts the number of open sockets. */ +tor_socket_t +tor_accept_socket_nonblocking(tor_socket_t sockfd, struct sockaddr *addr, + socklen_t *len) +{ + return tor_accept_socket_with_extensions(sockfd, addr, len, 1, 1); +} + +/** As accept(), but counts the number of open sockets and handles + * socket creation with either of SOCK_CLOEXEC and SOCK_NONBLOCK specified. + * <b>cloexec</b> and <b>nonblock</b> should be either 0 or 1 to indicate + * if the corresponding extension should be used.*/ +tor_socket_t +tor_accept_socket_with_extensions(tor_socket_t sockfd, struct sockaddr *addr, + socklen_t *len, int cloexec, int nonblock) +{ tor_socket_t s; -#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) - s = accept4(sockfd, addr, len, SOCK_CLOEXEC); +#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + int ext_flags = (cloexec ? SOCK_CLOEXEC : 0) | + (nonblock ? SOCK_NONBLOCK : 0); + s = accept4(sockfd, addr, len, ext_flags); if (SOCKET_OK(s)) goto socket_ok; /* If we got an error, see if it is ENOSYS. ENOSYS indicates that, * even though we were built on a system with accept4 support, we * are running on one without. Also, check for EINVAL, which indicates that - * we are missing SOCK_CLOEXEC support. */ + * we are missing SOCK_CLOEXEC/SOCK_NONBLOCK support. */ if (errno != EINVAL && errno != ENOSYS) return s; #endif @@ -1092,13 +1159,24 @@ tor_accept_socket(tor_socket_t sockfd, struct sockaddr *addr, socklen_t *len) return s; #if defined(FD_CLOEXEC) - if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { - log_warn(LD_NET, "Couldn't set FD_CLOEXEC: %s", strerror(errno)); - close(s); - return TOR_INVALID_SOCKET; + if (cloexec) { + if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { + log_warn(LD_NET, "Couldn't set FD_CLOEXEC: %s", strerror(errno)); + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } } +#else + (void)cloexec; #endif + if (nonblock) { + if (set_socket_nonblocking(s) == -1) { + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } + } + goto socket_ok; /* So that socket_ok will not be unused. */ socket_ok: @@ -1220,6 +1298,18 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) return 0; #else + return tor_ersatz_socketpair(family, type, protocol, fd); +#endif +} + +#ifdef NEED_ERSATZ_SOCKETPAIR +/** + * Helper used to implement socketpair on systems that lack it, by + * making a direct connection to localhost. + */ +STATIC int +tor_ersatz_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) +{ /* This socketpair does not work when localhost is down. So * it's really not the same thing at all. But it's close enough * for now, and really, when localhost is down sometimes, we @@ -1230,7 +1320,7 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) tor_socket_t acceptor = TOR_INVALID_SOCKET; struct sockaddr_in listen_addr; struct sockaddr_in connect_addr; - int size; + socklen_t size; int saved_errno = -1; if (protocol @@ -1313,8 +1403,8 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) if (SOCKET_OK(acceptor)) tor_close_socket(acceptor); return -saved_errno; -#endif } +#endif /** Number of extra file descriptors to keep in reserve beyond those that we * tell Tor it's allowed to use. */ @@ -1746,6 +1836,15 @@ get_user_homedir(const char *username) * actually examine the filesystem; does a purely syntactic modification. * * The parent of the root director is considered to be iteself. + * + * Path separators are the forward slash (/) everywhere and additionally + * the backslash (\) on Win32. + * + * Cuts off any number of trailing path separators but otherwise ignores + * them for purposes of finding the parent directory. + * + * Returns 0 if a parent directory was successfully found, -1 otherwise (fname + * did not have any path separators or only had them at the end). * */ int get_parent_directory(char *fname) diff --git a/src/common/compat.h b/src/common/compat.h index 8ab719052..8e700a9a1 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -8,6 +8,7 @@ #include "orconfig.h" #include "torint.h" +#include "testsupport.h" #ifdef _WIN32 #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 @@ -84,13 +85,19 @@ /* ===== Compiler compatibility */ -/* GCC can check printf types on arbitrary functions. */ +/* GCC can check printf and scanf types on arbitrary functions. */ #ifdef __GNUC__ #define CHECK_PRINTF(formatIdx, firstArg) \ __attribute__ ((format(printf, formatIdx, firstArg))) #else #define CHECK_PRINTF(formatIdx, firstArg) #endif +#ifdef __GNUC__ +#define CHECK_SCANF(formatIdx, firstArg) \ + __attribute__ ((format(scanf, formatIdx, firstArg))) +#else +#define CHECK_SCANF(formatIdx, firstArg) +#endif /* inline is __inline on windows. */ #ifdef _WIN32 @@ -444,10 +451,22 @@ typedef int socklen_t; #define TOR_INVALID_SOCKET (-1) #endif +int tor_close_socket_simple(tor_socket_t s); int tor_close_socket(tor_socket_t s); +tor_socket_t tor_open_socket_with_extensions( + int domain, int type, int protocol, + int cloexec, int nonblock); tor_socket_t tor_open_socket(int domain, int type, int protocol); +tor_socket_t tor_open_socket_nonblocking(int domain, int type, int protocol); tor_socket_t tor_accept_socket(tor_socket_t sockfd, struct sockaddr *addr, socklen_t *len); +tor_socket_t tor_accept_socket_nonblocking(tor_socket_t sockfd, + struct sockaddr *addr, + socklen_t *len); +tor_socket_t tor_accept_socket_with_extensions(tor_socket_t sockfd, + struct sockaddr *addr, + socklen_t *len, + int cloexec, int nonblock); int get_n_open_sockets(void); #define tor_socket_send(s, buf, len, flags) send(s, buf, len, flags) @@ -720,5 +739,13 @@ char *format_win32_error(DWORD err); #endif +#ifdef COMPAT_PRIVATE +#if !defined(HAVE_SOCKETPAIR) || defined(_WIN32) || defined(TOR_UNIT_TESTS) +#define NEED_ERSATZ_SOCKETPAIR +STATIC int tor_ersatz_socketpair(int family, int type, int protocol, + tor_socket_t fd[2]); +#endif +#endif + #endif diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index 200a7c65f..4d0fff833 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -415,6 +415,14 @@ tor_check_libevent_version(const char *m, int server, #define HEADER_VERSION _EVENT_VERSION #endif +/** Return a string representation of the version of Libevent that was used +* at compilation time. */ +const char * +tor_libevent_get_header_version_str(void) +{ + return HEADER_VERSION; +} + /** See whether the headers we were built against differ from the library we * linked against so much that we're likely to crash. If so, warn the * user. */ diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index 2472e2c49..fda873359 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -78,6 +78,7 @@ void tor_check_libevent_version(const char *m, int server, const char **badness_out); void tor_check_libevent_header_compatibility(void); const char *tor_libevent_get_version_str(void); +const char *tor_libevent_get_header_version_str(void); #ifdef USE_BUFFEREVENTS const struct timeval *tor_libevent_get_one_tick_timeout(void); diff --git a/src/common/container.c b/src/common/container.c index eec497a3e..476dc8291 100644 --- a/src/common/container.c +++ b/src/common/container.c @@ -243,6 +243,25 @@ smartlist_strings_eq(const smartlist_t *sl1, const smartlist_t *sl2) return 1; } +/** Return true iff the two lists contain the same int pointer values in + * the same order, or if they are both NULL. */ +int +smartlist_ints_eq(const smartlist_t *sl1, const smartlist_t *sl2) +{ + if (sl1 == NULL) + return sl2 == NULL; + if (sl2 == NULL) + return 0; + if (smartlist_len(sl1) != smartlist_len(sl2)) + return 0; + SMARTLIST_FOREACH(sl1, int *, cp1, { + int *cp2 = smartlist_get(sl2, cp1_sl_idx); + if (*cp1 != *cp2) + return 0; + }); + return 1; +} + /** Return true iff <b>sl</b> has some element E such that * tor_memeq(E,<b>element</b>,DIGEST_LEN) */ diff --git a/src/common/container.h b/src/common/container.h index 1a68b8f67..1bcc54066 100644 --- a/src/common/container.h +++ b/src/common/container.h @@ -42,6 +42,7 @@ int smartlist_contains_string_case(const smartlist_t *sl, const char *element); int smartlist_contains_int_as_string(const smartlist_t *sl, int num); int smartlist_strings_eq(const smartlist_t *sl1, const smartlist_t *sl2); int smartlist_contains_digest(const smartlist_t *sl, const char *element); +int smartlist_ints_eq(const smartlist_t *sl1, const smartlist_t *sl2); int smartlist_overlap(const smartlist_t *sl1, const smartlist_t *sl2); void smartlist_intersect(smartlist_t *sl1, const smartlist_t *sl2); void smartlist_subtract(smartlist_t *sl1, const smartlist_t *sl2); diff --git a/src/common/crypto.c b/src/common/crypto.c index 940a756f6..347f27e13 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -56,6 +56,7 @@ #include "../common/util.h" #include "container.h" #include "compat.h" +#include "sandbox.h" #if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(0,9,8) #error "We require OpenSSL >= 0.9.8" @@ -114,7 +115,6 @@ crypto_get_rsa_padding_overhead(int padding) switch (padding) { case RSA_PKCS1_OAEP_PADDING: return PKCS1_OAEP_PADDING_OVERHEAD; - case RSA_PKCS1_PADDING: return PKCS1_PADDING_OVERHEAD; default: tor_assert(0); return -1; } } @@ -126,7 +126,6 @@ crypto_get_rsa_padding(int padding) { switch (padding) { - case PK_PKCS1_PADDING: return RSA_PKCS1_PADDING; case PK_PKCS1_OAEP_PADDING: return RSA_PKCS1_OAEP_PADDING; default: tor_assert(0); return -1; } @@ -197,6 +196,27 @@ try_load_engine(const char *path, const char *engine) } #endif +/* Returns a trimmed and human-readable version of an openssl version string +* <b>raw_version</b>. They are usually in the form of 'OpenSSL 1.0.0b 10 +* May 2012' and this will parse them into a form similar to '1.0.0b' */ +static char * +parse_openssl_version_str(const char *raw_version) +{ + const char *end_of_version = NULL; + /* The output should be something like "OpenSSL 1.0.0b 10 May 2012. Let's + trim that down. */ + if (!strcmpstart(raw_version, "OpenSSL ")) { + raw_version += strlen("OpenSSL "); + end_of_version = strchr(raw_version, ' '); + } + + if (end_of_version) + return tor_strndup(raw_version, + end_of_version-raw_version); + else + return tor_strdup(raw_version); +} + static char *crypto_openssl_version_str = NULL; /* Return a human-readable version of the run-time openssl version number. */ const char * @@ -204,23 +224,24 @@ crypto_openssl_get_version_str(void) { if (crypto_openssl_version_str == NULL) { const char *raw_version = SSLeay_version(SSLEAY_VERSION); - const char *end_of_version = NULL; - /* The output should be something like "OpenSSL 1.0.0b 10 May 2012. Let's - trim that down. */ - if (!strcmpstart(raw_version, "OpenSSL ")) { - raw_version += strlen("OpenSSL "); - end_of_version = strchr(raw_version, ' '); - } - - if (end_of_version) - crypto_openssl_version_str = tor_strndup(raw_version, - end_of_version-raw_version); - else - crypto_openssl_version_str = tor_strdup(raw_version); + crypto_openssl_version_str = parse_openssl_version_str(raw_version); } return crypto_openssl_version_str; } +static char *crypto_openssl_header_version_str = NULL; +/* Return a human-readable version of the compile-time openssl version +* number. */ +const char * +crypto_openssl_get_header_version_str(void) +{ + if (crypto_openssl_header_version_str == NULL) { + crypto_openssl_header_version_str = + parse_openssl_version_str(OPENSSL_VERSION_TEXT); + } + return crypto_openssl_header_version_str; +} + /** Initialize the crypto library. Return 0 on success, -1 on failure. */ int @@ -286,12 +307,29 @@ crypto_global_init(int useAccel, const char *accelName, const char *accelDir) " setting default ciphers."); ENGINE_set_default(e, ENGINE_METHOD_ALL); } + /* Log, if available, the intersection of the set of algorithms + used by Tor and the set of algorithms available in the engine */ log_engine("RSA", ENGINE_get_default_RSA()); log_engine("DH", ENGINE_get_default_DH()); + log_engine("ECDH", ENGINE_get_default_ECDH()); + log_engine("ECDSA", ENGINE_get_default_ECDSA()); + log_engine("RAND", ENGINE_get_default_RAND()); log_engine("RAND (which we will not use)", ENGINE_get_default_RAND()); log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1)); - log_engine("3DES", ENGINE_get_cipher_engine(NID_des_ede3_ecb)); - log_engine("AES", ENGINE_get_cipher_engine(NID_aes_128_ecb)); + log_engine("3DES-CBC", ENGINE_get_cipher_engine(NID_des_ede3_cbc)); + log_engine("AES-128-ECB", ENGINE_get_cipher_engine(NID_aes_128_ecb)); + log_engine("AES-128-CBC", ENGINE_get_cipher_engine(NID_aes_128_cbc)); +#ifdef NID_aes_128_ctr + log_engine("AES-128-CTR", ENGINE_get_cipher_engine(NID_aes_128_ctr)); +#endif +#ifdef NID_aes_128_gcm + log_engine("AES-128-GCM", ENGINE_get_cipher_engine(NID_aes_128_gcm)); +#endif + log_engine("AES-256-CBC", ENGINE_get_cipher_engine(NID_aes_256_cbc)); +#ifdef NID_aes_256_gcm + log_engine("AES-256-GCM", ENGINE_get_cipher_engine(NID_aes_256_gcm)); +#endif + #endif } else { log_info(LD_CRYPTO, "NOT using OpenSSL engine support."); @@ -1161,22 +1199,21 @@ int crypto_pk_asn1_encode(crypto_pk_t *pk, char *dest, size_t dest_len) { int len; - unsigned char *buf, *cp; - len = i2d_RSAPublicKey(pk->key, NULL); - if (len < 0 || (size_t)len > dest_len || dest_len > SIZE_T_CEILING) + unsigned char *buf = NULL; + + len = i2d_RSAPublicKey(pk->key, &buf); + if (len < 0 || buf == NULL) return -1; - cp = buf = tor_malloc(len+1); - len = i2d_RSAPublicKey(pk->key, &cp); - if (len < 0) { - crypto_log_errors(LOG_WARN,"encoding public key"); - tor_free(buf); + + if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { + OPENSSL_free(buf); return -1; } /* We don't encode directly into 'dest', because that would be illegal * type-punning. (C99 is smarter than me, C99 is smarter than me...) */ memcpy(dest,buf,len); - tor_free(buf); + OPENSSL_free(buf); return len; } @@ -1207,24 +1244,17 @@ crypto_pk_asn1_decode(const char *str, size_t len) int crypto_pk_get_digest(crypto_pk_t *pk, char *digest_out) { - unsigned char *buf, *bufp; + unsigned char *buf = NULL; int len; - len = i2d_RSAPublicKey(pk->key, NULL); - if (len < 0) - return -1; - buf = bufp = tor_malloc(len+1); - len = i2d_RSAPublicKey(pk->key, &bufp); - if (len < 0) { - crypto_log_errors(LOG_WARN,"encoding public key"); - tor_free(buf); + len = i2d_RSAPublicKey(pk->key, &buf); + if (len < 0 || buf == NULL) return -1; - } if (crypto_digest(digest_out, (char*)buf, len) < 0) { - tor_free(buf); + OPENSSL_free(buf); return -1; } - tor_free(buf); + OPENSSL_free(buf); return 0; } @@ -1233,31 +1263,24 @@ crypto_pk_get_digest(crypto_pk_t *pk, char *digest_out) int crypto_pk_get_all_digests(crypto_pk_t *pk, digests_t *digests_out) { - unsigned char *buf, *bufp; + unsigned char *buf = NULL; int len; - len = i2d_RSAPublicKey(pk->key, NULL); - if (len < 0) - return -1; - buf = bufp = tor_malloc(len+1); - len = i2d_RSAPublicKey(pk->key, &bufp); - if (len < 0) { - crypto_log_errors(LOG_WARN,"encoding public key"); - tor_free(buf); + len = i2d_RSAPublicKey(pk->key, &buf); + if (len < 0 || buf == NULL) return -1; - } if (crypto_digest_all(digests_out, (char*)buf, len) < 0) { - tor_free(buf); + OPENSSL_free(buf); return -1; } - tor_free(buf); + OPENSSL_free(buf); return 0; } /** Copy <b>in</b> to the <b>outlen</b>-byte buffer <b>out</b>, adding spaces * every four spaces. */ -/* static */ void -add_spaces_to_fp(char *out, size_t outlen, const char *in) +void +crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in) { int n = 0; char *end = out+outlen; @@ -1294,7 +1317,7 @@ crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out, int add_space) } base16_encode(hexdigest,sizeof(hexdigest),digest,DIGEST_LEN); if (add_space) { - add_spaces_to_fp(fp_out, FINGERPRINT_LEN+1, hexdigest); + crypto_add_spaces_to_fp(fp_out, FINGERPRINT_LEN+1, hexdigest); } else { strncpy(fp_out, hexdigest, HEX_DIGEST_LEN+1); } @@ -1644,21 +1667,6 @@ crypto_digest_smartlist(char *digest_out, size_t len_out, crypto_digest_free(d); } -/** Compute the HMAC-SHA-1 of the <b>msg_len</b> bytes in <b>msg</b>, using - * the <b>key</b> of length <b>key_len</b>. Store the DIGEST_LEN-byte result - * in <b>hmac_out</b>. - */ -void -crypto_hmac_sha1(char *hmac_out, - const char *key, size_t key_len, - const char *msg, size_t msg_len) -{ - tor_assert(key_len < INT_MAX); - tor_assert(msg_len < INT_MAX); - HMAC(EVP_sha1(), key, (int)key_len, (unsigned char*)msg, (int)msg_len, - (unsigned char*)hmac_out, NULL); -} - /** Compute the HMAC-SHA-256 of the <b>msg_len</b> bytes in <b>msg</b>, using * the <b>key</b> of length <b>key_len</b>. Store the DIGEST256_LEN-byte * result in <b>hmac_out</b>. @@ -1727,7 +1735,7 @@ crypto_store_dynamic_dh_modulus(const char *fname) { int len, new_len; DH *dh = NULL; - unsigned char *dh_string_repr = NULL, *cp = NULL; + unsigned char *dh_string_repr = NULL; char *base64_encoded_dh = NULL; char *file_string = NULL; int retval = -1; @@ -1751,15 +1759,8 @@ crypto_store_dynamic_dh_modulus(const char *fname) if (!BN_set_word(dh->g, DH_GENERATOR)) goto done; - len = i2d_DHparams(dh, NULL); - if (len < 0) { - log_warn(LD_CRYPTO, "Error occured while DER encoding DH modulus (1)."); - goto done; - } - - cp = dh_string_repr = tor_malloc_zero(len+1); - len = i2d_DHparams(dh, &cp); - if ((len < 0) || ((cp - dh_string_repr) != len)) { + len = i2d_DHparams(dh, &dh_string_repr); + if ((len < 0) || (dh_string_repr == NULL)) { log_warn(LD_CRYPTO, "Error occured while DER encoding DH modulus (2)."); goto done; } @@ -1786,7 +1787,8 @@ crypto_store_dynamic_dh_modulus(const char *fname) done: if (dh) DH_free(dh); - tor_free(dh_string_repr); + if (dh_string_repr) + OPENSSL_free(dh_string_repr); tor_free(base64_encoded_dh); tor_free(file_string); @@ -2394,7 +2396,7 @@ crypto_strongest_rand(uint8_t *out, size_t out_len) return 0; #else for (i = 0; filenames[i]; ++i) { - fd = open(filenames[i], O_RDONLY, 0); + fd = open(sandbox_intern_string(filenames[i]), O_RDONLY, 0); if (fd<0) continue; log_info(LD_CRYPTO, "Reading entropy from \"%s\"", filenames[i]); n = read_all(fd, (char*)out, out_len, 0); @@ -2449,8 +2451,8 @@ crypto_seed_rng(int startup) /** Write <b>n</b> bytes of strong random data to <b>to</b>. Return 0 on * success, -1 on failure. */ -int -crypto_rand(char *to, size_t n) +MOCK_IMPL(int, +crypto_rand, (char *to, size_t n)) { int r; tor_assert(n < INT_MAX); @@ -3144,6 +3146,7 @@ crypto_global_cleanup(void) } #endif tor_free(crypto_openssl_version_str); + tor_free(crypto_openssl_header_version_str); return 0; } diff --git a/src/common/crypto.h b/src/common/crypto.h index 2fbca4c26..6ce3697c9 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -15,6 +15,7 @@ #include <stdio.h> #include "torint.h" +#include "testsupport.h" /* Macro to create an arbitrary OpenSSL version number as used by @@ -69,13 +70,9 @@ * signs removed. */ #define BASE64_DIGEST256_LEN 43 -/** Constant used to indicate PKCS1 padding for public-key encryption */ -#define PK_PKCS1_PADDING 60001 /** Constant used to indicate OAEP padding for public-key encryption */ #define PK_PKCS1_OAEP_PADDING 60002 -/** Number of bytes added for PKCS1 padding. */ -#define PKCS1_PADDING_OVERHEAD 11 /** Number of bytes added for PKCS1-OAEP padding. */ #define PKCS1_OAEP_PADDING_OVERHEAD 42 @@ -112,6 +109,7 @@ typedef struct crypto_dh_t crypto_dh_t; /* global state */ const char * crypto_openssl_get_version_str(void); +const char * crypto_openssl_get_header_version_str(void); int crypto_global_init(int hardwareAccel, const char *accelName, const char *accelPath); @@ -221,9 +219,6 @@ void crypto_digest_get_digest(crypto_digest_t *digest, crypto_digest_t *crypto_digest_dup(const crypto_digest_t *digest); void crypto_digest_assign(crypto_digest_t *into, const crypto_digest_t *from); -void crypto_hmac_sha1(char *hmac_out, - const char *key, size_t key_len, - const char *msg, size_t msg_len); void crypto_hmac_sha256(char *hmac_out, const char *key, size_t key_len, const char *msg, size_t msg_len); @@ -254,7 +249,7 @@ int crypto_expand_key_material_rfc5869_sha256( /* random numbers */ int crypto_seed_rng(int startup); -int crypto_rand(char *to, size_t n); +MOCK_DECL(int,crypto_rand,(char *to, size_t n)); int crypto_strongest_rand(uint8_t *out, size_t out_len); int crypto_rand_int(unsigned int max); uint64_t crypto_rand_uint64(uint64_t max); @@ -290,7 +285,6 @@ void secret_to_key(char *key_out, size_t key_out_len, const char *secret, /** OpenSSL-based utility functions. */ void memwipe(void *mem, uint8_t byte, size_t sz); -#ifdef CRYPTO_PRIVATE /* Prototypes for private functions only used by tortls.c, crypto.c, and the * unit tests. */ struct rsa_st; @@ -301,9 +295,8 @@ crypto_pk_t *crypto_new_pk_from_rsa_(struct rsa_st *rsa); struct evp_pkey_st *crypto_pk_get_evp_pkey_(crypto_pk_t *env, int private); struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh); -/* Prototypes for private functions only used by crypto.c and test.c*/ -void add_spaces_to_fp(char *out, size_t outlen, const char *in); -#endif + +void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in); #endif diff --git a/src/common/crypto_curve25519.c b/src/common/crypto_curve25519.c index 88c723f37..9e83440e1 100644 --- a/src/common/crypto_curve25519.c +++ b/src/common/crypto_curve25519.c @@ -29,7 +29,7 @@ int curve25519_donna(uint8_t *mypublic, #endif #endif -int +STATIC int curve25519_impl(uint8_t *output, const uint8_t *secret, const uint8_t *basepoint) { diff --git a/src/common/crypto_curve25519.h b/src/common/crypto_curve25519.h index 652f1883c..f9d533ba2 100644 --- a/src/common/crypto_curve25519.h +++ b/src/common/crypto_curve25519.h @@ -4,6 +4,7 @@ #ifndef TOR_CRYPTO_CURVE25519_H #define TOR_CRYPTO_CURVE25519_H +#include "testsupport.h" #include "torint.h" /** Length of a curve25519 public key when encoded. */ @@ -52,8 +53,8 @@ int curve25519_keypair_read_from_file(curve25519_keypair_t *keypair_out, const char *fname); #ifdef CRYPTO_CURVE25519_PRIVATE -int curve25519_impl(uint8_t *output, const uint8_t *secret, - const uint8_t *basepoint); +STATIC int curve25519_impl(uint8_t *output, const uint8_t *secret, + const uint8_t *basepoint); #endif #endif diff --git a/src/common/crypto_format.c b/src/common/crypto_format.c index 93932f839..be669c8d2 100644 --- a/src/common/crypto_format.c +++ b/src/common/crypto_format.c @@ -3,7 +3,6 @@ /* Formatting and parsing code for crypto-related data structures. */ -#define CRYPTO_CURVE25519_PRIVATE #include "orconfig.h" #ifdef HAVE_SYS_STAT_H #include <sys/stat.h> diff --git a/src/common/include.am b/src/common/include.am index b796ebfae..814786b77 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -1,5 +1,15 @@ -noinst_LIBRARIES+= src/common/libor.a src/common/libor-crypto.a src/common/libor-event.a +noinst_LIBRARIES += \ + src/common/libor.a \ + src/common/libor-crypto.a \ + src/common/libor-event.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += \ + src/common/libor-testing.a \ + src/common/libor-crypto-testing.a \ + src/common/libor-event-testing.a +endif EXTRA_DIST+= \ src/common/common_sha1.i \ @@ -14,9 +24,13 @@ else libor_extra_source= endif +src_common_libcurve25519_donna_a_CFLAGS= + if BUILD_CURVE25519_DONNA src_common_libcurve25519_donna_a_SOURCES=\ src/ext/curve25519_donna/curve25519-donna.c +src_common_libcurve25519_donna_a_CFLAGS+=\ + @F_OMIT_FRAME_POINTER@ noinst_LIBRARIES+=src/common/libcurve25519_donna.a LIBDONNA=src/common/libcurve25519_donna.a else @@ -30,14 +44,13 @@ LIBDONNA= endif endif -src_common_libcurve25519_donna_a_CFLAGS = - if CURVE25519_ENABLED libcrypto_extra_source=src/common/crypto_curve25519.c endif -src_common_libor_a_SOURCES = \ +LIBOR_A_SOURCES = \ src/common/address.c \ + src/common/backtrace.c \ src/common/compat.c \ src/common/container.c \ src/common/di_ops.c \ @@ -47,9 +60,10 @@ src_common_libor_a_SOURCES = \ src/common/procmon.c \ src/common/util.c \ src/common/util_codedigest.c \ + src/common/sandbox.c \ $(libor_extra_source) -src_common_libor_crypto_a_SOURCES = \ +LIBOR_CRYPTO_A_SOURCES = \ src/common/aes.c \ src/common/crypto.c \ src/common/crypto_format.c \ @@ -57,10 +71,27 @@ src_common_libor_crypto_a_SOURCES = \ src/common/tortls.c \ $(libcrypto_extra_source) -src_common_libor_event_a_SOURCES = src/common/compat_libevent.c +LIBOR_EVENT_A_SOURCES = src/common/compat_libevent.c + +src_common_libor_a_SOURCES = $(LIBOR_A_SOURCES) +src_common_libor_crypto_a_SOURCES = $(LIBOR_CRYPTO_A_SOURCES) +src_common_libor_event_a_SOURCES = $(LIBOR_EVENT_A_SOURCES) + +src_common_libor_testing_a_SOURCES = $(LIBOR_A_SOURCES) +src_common_libor_crypto_testing_a_SOURCES = $(LIBOR_CRYPTO_A_SOURCES) +src_common_libor_event_testing_a_SOURCES = $(LIBOR_EVENT_A_SOURCES) + +src_common_libor_testing_a_CPPFLAGS = -DTOR_UNIT_TESTS $(AM_CPPFLAGS) +src_common_libor_crypto_testing_a_CPPFLAGS = -DTOR_UNIT_TESTS $(AM_CPPFLAGS) +src_common_libor_event_testing_a_CPPFLAGS = -DTOR_UNIT_TESTS $(AM_CPPFLAGS) +src_common_libor_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_common_libor_crypto_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_common_libor_event_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + COMMONHEADERS = \ src/common/address.h \ + src/common/backtrace.h \ src/common/aes.h \ src/common/ciphers.inc \ src/common/compat.h \ @@ -72,6 +103,8 @@ COMMONHEADERS = \ src/common/memarea.h \ src/common/mempool.h \ src/common/procmon.h \ + src/common/sandbox.h \ + src/common/testsupport.h \ src/common/torgzip.h \ src/common/torint.h \ src/common/torlog.h \ diff --git a/src/common/log.c b/src/common/log.c index e196a1128..9c67de320 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -36,6 +36,10 @@ #include "torlog.h" #include "container.h" +/** Given a severity, yields an index into log_severity_list_t.masks to use + * for that severity. */ +#define SEVERITY_MASK_IDX(sev) ((sev) - LOG_ERR) + /** @{ */ /** The string we stick at the end of a log message when it is too long, * and its length. */ @@ -83,12 +87,12 @@ should_log_function_name(log_domain_mask_t domain, int severity) case LOG_DEBUG: case LOG_INFO: /* All debugging messages occur in interesting places. */ - return 1; + return (domain & LD_NOFUNCNAME) == 0; case LOG_NOTICE: case LOG_WARN: case LOG_ERR: /* We care about places where bugs occur. */ - return (domain == LD_BUG); + return (domain & (LD_BUG|LD_NOFUNCNAME)) == LD_BUG; default: /* Call assert, not tor_assert, since tor_assert calls log on failure. */ assert(0); return 0; @@ -439,6 +443,128 @@ tor_log(int severity, log_domain_mask_t domain, const char *format, ...) va_end(ap); } +/** Maximum number of fds that will get notifications if we crash */ +#define MAX_SIGSAFE_FDS 8 +/** Array of fds to log crash-style warnings to. */ +static int sigsafe_log_fds[MAX_SIGSAFE_FDS] = { STDERR_FILENO }; +/** The number of elements used in sigsafe_log_fds */ +static int n_sigsafe_log_fds = 1; + +/** Write <b>s</b> to each element of sigsafe_log_fds. Return 0 on success, -1 + * on failure. */ +static int +tor_log_err_sigsafe_write(const char *s) +{ + int i; + ssize_t r; + size_t len = strlen(s); + int err = 0; + for (i=0; i < n_sigsafe_log_fds; ++i) { + r = write(sigsafe_log_fds[i], s, len); + err += (r != (ssize_t)len); + } + return err ? -1 : 0; +} + +/** Given a list of string arguments ending with a NULL, writes them + * to our logs and to stderr (if possible). This function is safe to call + * from within a signal handler. */ +void +tor_log_err_sigsafe(const char *m, ...) +{ + va_list ap; + const char *x; + char timebuf[33]; + time_t now = time(NULL); + + if (!m) + return; + if (log_time_granularity >= 2000) { + int g = log_time_granularity / 1000; + now -= now % g; + } + timebuf[0] = now < 0 ? '-' : ' '; + if (now < 0) now = -now; + timebuf[1] = '\0'; + format_dec_number_sigsafe(now, timebuf+1, sizeof(timebuf)-1); + tor_log_err_sigsafe_write("\n==========================================" + "================== T="); + tor_log_err_sigsafe_write(timebuf); + tor_log_err_sigsafe_write("\n"); + tor_log_err_sigsafe_write(m); + va_start(ap, m); + while ((x = va_arg(ap, const char*))) { + tor_log_err_sigsafe_write(x); + } + va_end(ap); +} + +/** Set *<b>out</b> to a pointer to an array of the fds to log errors to from + * inside a signal handler. Return the number of elements in the array. */ +int +tor_log_get_sigsafe_err_fds(const int **out) +{ + *out = sigsafe_log_fds; + return n_sigsafe_log_fds; +} + +/** Helper function; return true iff the <b>n</b>-element array <b>array</b> + * contains <b>item</b>. */ +static int +int_array_contains(const int *array, int n, int item) +{ + int j; + for (j = 0; j < n; ++j) { + if (array[j] == item) + return 1; + } + return 0; +} + +/** Function to call whenever the list of logs changes to get ready to log + * from signal handlers. */ +void +tor_log_update_sigsafe_err_fds(void) +{ + const logfile_t *lf; + int found_real_stderr = 0; + + LOCK_LOGS(); + /* Reserve the first one for stderr. This is safe because when we daemonize, + * we dup2 /dev/null to stderr, */ + sigsafe_log_fds[0] = STDERR_FILENO; + n_sigsafe_log_fds = 1; + + for (lf = logfiles; lf; lf = lf->next) { + /* Don't try callback to the control port, or syslogs: We can't + * do them from a signal handler. Don't try stdout: we always do stderr. + */ + if (lf->is_temporary || lf->is_syslog || + lf->callback || lf->seems_dead || lf->fd < 0) + continue; + if (lf->severities->masks[SEVERITY_MASK_IDX(LOG_ERR)] & + (LD_BUG|LD_GENERAL)) { + if (lf->fd == STDERR_FILENO) + found_real_stderr = 1; + /* Avoid duplicates */ + if (int_array_contains(sigsafe_log_fds, n_sigsafe_log_fds, lf->fd)) + continue; + sigsafe_log_fds[n_sigsafe_log_fds++] = lf->fd; + if (n_sigsafe_log_fds == MAX_SIGSAFE_FDS) + break; + } + } + + if (!found_real_stderr && + int_array_contains(sigsafe_log_fds, n_sigsafe_log_fds, STDOUT_FILENO)) { + /* Don't use a virtual stderr when we're also logging to stdout. */ + assert(n_sigsafe_log_fds >= 2); /* Don't use assert inside log functions*/ + sigsafe_log_fds[0] = sigsafe_log_fds[--n_sigsafe_log_fds]; + } + + UNLOCK_LOGS(); +} + /** Output a message to the log, prefixed with a function name <b>fn</b>. */ #ifdef __GNUC__ /** GCC-based implementation of the log_fn backend, used when we have diff --git a/src/common/sandbox.c b/src/common/sandbox.c new file mode 100644 index 000000000..7ef577dbe --- /dev/null +++ b/src/common/sandbox.c @@ -0,0 +1,1493 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file sandbox.c + * \brief Code to enable sandboxing. + **/ + +#include "orconfig.h" + +#ifndef _LARGEFILE64_SOURCE +/** + * Temporarily required for O_LARGEFILE flag. Needs to be removed + * with the libevent fix. + */ +#define _LARGEFILE64_SOURCE +#endif + +/** Malloc mprotect limit in bytes. */ +#define MALLOC_MP_LIM 1048576 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "sandbox.h" +#include "torlog.h" +#include "torint.h" +#include "util.h" +#include "tor_queue.h" + +#define DEBUGGING_CLOSE + +#if defined(USE_LIBSECCOMP) + +#define _GNU_SOURCE + +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/epoll.h> +#include <sys/prctl.h> +#include <linux/futex.h> +#include <bits/signum.h> +#include <event2/event.h> + +#include <stdarg.h> +#include <seccomp.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <poll.h> + +/**Determines if at least one sandbox is active.*/ +static int sandbox_active = 0; +/** Holds the parameter list configuration for the sandbox.*/ +static sandbox_cfg_t *filter_dynamic = NULL; +/** Holds a list of pre-recorded results from getaddrinfo().*/ +static sb_addr_info_t *sb_addr_info = NULL; + +#undef SCMP_CMP +#define SCMP_CMP(a,b,c) ((struct scmp_arg_cmp){(a),(b),(c),0}) + +/** Variable used for storing all syscall numbers that will be allowed with the + * stage 1 general Tor sandbox. + */ +static int filter_nopar_gen[] = { + SCMP_SYS(access), + SCMP_SYS(brk), + SCMP_SYS(clock_gettime), + SCMP_SYS(close), + SCMP_SYS(clone), + SCMP_SYS(epoll_create), + SCMP_SYS(epoll_wait), + SCMP_SYS(fcntl), + SCMP_SYS(fstat), +#ifdef __NR_fstat64 + SCMP_SYS(fstat64), +#endif + SCMP_SYS(getdents64), + SCMP_SYS(getegid), +#ifdef __NR_getegid32 + SCMP_SYS(getegid32), +#endif + SCMP_SYS(geteuid), +#ifdef __NR_geteuid32 + SCMP_SYS(geteuid32), +#endif + SCMP_SYS(getgid), +#ifdef __NR_getgid32 + SCMP_SYS(getgid32), +#endif + SCMP_SYS(getrlimit), + SCMP_SYS(gettimeofday), + SCMP_SYS(getuid), +#ifdef __NR_getuid32 + SCMP_SYS(getuid32), +#endif + SCMP_SYS(lseek), +#ifdef __NR__llseek + SCMP_SYS(_llseek), +#endif + SCMP_SYS(mkdir), + SCMP_SYS(mlockall), + SCMP_SYS(mmap), + SCMP_SYS(munmap), + SCMP_SYS(read), + SCMP_SYS(rename), + SCMP_SYS(rt_sigreturn), + SCMP_SYS(set_robust_list), +#ifdef __NR_sigreturn + SCMP_SYS(sigreturn), +#endif + SCMP_SYS(stat), + SCMP_SYS(uname), + SCMP_SYS(write), + SCMP_SYS(exit_group), + SCMP_SYS(exit), + + SCMP_SYS(madvise), +#ifdef __NR_stat64 + // getaddrinfo uses this.. + SCMP_SYS(stat64), +#endif + + /* + * These socket syscalls are not required on x86_64 and not supported with + * some libseccomp versions (eg: 1.0.1) + */ +#if defined(__i386) + SCMP_SYS(recv), + SCMP_SYS(send), +#endif + + // socket syscalls + SCMP_SYS(bind), + SCMP_SYS(connect), + SCMP_SYS(getsockname), + SCMP_SYS(recvmsg), + SCMP_SYS(recvfrom), + SCMP_SYS(sendto), + SCMP_SYS(unlink) +}; + +/** + * Function responsible for setting up the rt_sigaction syscall for + * the seccomp filter sandbox. + */ +static int +sb_rt_sigaction(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + unsigned i; + int rc; + int param[] = { SIGINT, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, SIGHUP, SIGCHLD, +#ifdef SIGXFSZ + SIGXFSZ +#endif + }; + (void) filter; + + for (i = 0; i < ARRAY_LENGTH(param); i++) { + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 1, + SCMP_CMP(0, SCMP_CMP_EQ, param[i])); + if (rc) + break; + } + + return rc; +} + +/** + * Function responsible for setting up the execve syscall for + * the seccomp filter sandbox. + */ +static int +sb_execve(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = (smp_param_t*) elem->param; + + if (param != NULL && param->prot == 1 && param->syscall + == SCMP_SYS(execve)) { + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(execve), 1, + SCMP_CMP(0, SCMP_CMP_EQ, param->value)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add execve syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} + +/** + * Function responsible for setting up the time syscall for + * the seccomp filter sandbox. + */ +static int +sb_time(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + (void) filter; + return seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(time), 1, + SCMP_CMP(0, SCMP_CMP_EQ, 0)); +} + +/** + * Function responsible for setting up the accept4 syscall for + * the seccomp filter sandbox. + */ +static int +sb_accept4(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void)filter; + +#ifdef __i386__ + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketcall), 1, + SCMP_CMP(0, SCMP_CMP_EQ, 18)); + if (rc) { + return rc; + } +#endif + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept4), 1, + SCMP_CMP(3, SCMP_CMP_EQ, SOCK_CLOEXEC)); + if (rc) { + return rc; + } + + return 0; +} + +#ifdef __NR_mmap2 +/** + * Function responsible for setting up the mmap2 syscall for + * the seccomp filter sandbox. + */ +static int +sb_mmap2(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 2, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 2, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_NONE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 2, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_ANONYMOUS)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 2, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ,MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 2, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 2, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 2, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_EXEC), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_DENYWRITE)); + if (rc) { + return rc; + } + + return 0; +} +#endif + +/** + * Function responsible for setting up the open syscall for + * the seccomp filter sandbox. + */ +static int +sb_open(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && param->syscall + == SCMP_SYS(open)) { + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1, + SCMP_CMP(0, SCMP_CMP_EQ, param->value)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(-1), SCMP_SYS(open), 1, + SCMP_CMP(1, SCMP_CMP_EQ, O_RDONLY|O_CLOEXEC)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add open syscall, received libseccomp " + "error %d", rc); + return rc; + } + + return 0; +} + +/** + * Function responsible for setting up the openat syscall for + * the seccomp filter sandbox. + */ +static int +sb_openat(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && param->syscall + == SCMP_SYS(openat)) { + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 1, + SCMP_CMP(0, SCMP_CMP_EQ, AT_FDCWD), + SCMP_CMP(1, SCMP_CMP_EQ, param->value), + SCMP_CMP(2, SCMP_CMP_EQ, O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY| + O_CLOEXEC)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} + +/** + * Function responsible for setting up the socket syscall for + * the seccomp filter sandbox. + */ +static int +sb_socket(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 0); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 3, + SCMP_CMP(0, SCMP_CMP_EQ, PF_FILE), + SCMP_CMP(1, SCMP_CMP_EQ, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK), + SCMP_CMP(2, SCMP_CMP_EQ, IPPROTO_IP)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 3, + SCMP_CMP(0, SCMP_CMP_EQ, PF_INET), + SCMP_CMP(1, SCMP_CMP_EQ, SOCK_STREAM|SOCK_CLOEXEC), + SCMP_CMP(2, SCMP_CMP_EQ, IPPROTO_TCP)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 3, + SCMP_CMP(0, SCMP_CMP_EQ, PF_INET), + SCMP_CMP(1, SCMP_CMP_EQ, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK), + SCMP_CMP(2, SCMP_CMP_EQ, IPPROTO_IP)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 3, + SCMP_CMP(0, SCMP_CMP_EQ, PF_NETLINK), + SCMP_CMP(1, SCMP_CMP_EQ, SOCK_RAW), + SCMP_CMP(2, SCMP_CMP_EQ, 0)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the socketpair syscall for + * the seccomp filter sandbox. + */ +static int +sb_socketpair(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketpair), 0); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketpair), 2, + SCMP_CMP(0, SCMP_CMP_EQ, PF_FILE), + SCMP_CMP(1, SCMP_CMP_EQ, SOCK_STREAM|SOCK_CLOEXEC)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the setsockopt syscall for + * the seccomp filter sandbox. + */ +static int +sb_setsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 0); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 2, + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_REUSEADDR)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the getsockopt syscall for + * the seccomp filter sandbox. + */ +static int +sb_getsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), 0); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), 2, + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_ERROR)); + if (rc) + return rc; + + return 0; +} + +#ifdef __NR_fcntl64 +/** + * Function responsible for setting up the fcntl64 syscall for + * the seccomp filter sandbox. + */ +static int +sb_fcntl64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 1, + SCMP_CMP(1, SCMP_CMP_EQ, F_GETFL)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 2, + SCMP_CMP(1, SCMP_CMP_EQ, F_SETFL), + SCMP_CMP(2, SCMP_CMP_EQ, O_RDWR|O_NONBLOCK)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 1, + SCMP_CMP(1, SCMP_CMP_EQ, F_GETFD)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 2, + SCMP_CMP(1, SCMP_CMP_EQ, F_SETFD), + SCMP_CMP(2, SCMP_CMP_EQ, FD_CLOEXEC)); + if (rc) + return rc; + + return 0; +} +#endif + +/** + * Function responsible for setting up the epoll_ctl syscall for + * the seccomp filter sandbox. + * + * Note: basically allows everything but will keep for now.. + */ +static int +sb_epoll_ctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), 1, + SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_ADD)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), 1, + SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_MOD)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), 1, + SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_DEL)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the fcntl64 syscall for + * the seccomp filter sandbox. + * + * NOTE: if multiple filters need to be added, the PR_SECCOMP parameter needs + * to be whitelisted in this function. + */ +static int +sb_prctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 1, + SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_DUMPABLE)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the fcntl64 syscall for + * the seccomp filter sandbox. + * + * NOTE: does not NEED to be here.. currently only occurs before filter; will + * keep just in case for the future. + */ +static int +sb_mprotect(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 1, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 1, + SCMP_CMP(2, SCMP_CMP_EQ, PROT_NONE)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the rt_sigprocmask syscall for + * the seccomp filter sandbox. + */ +static int +sb_rt_sigprocmask(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 1, + SCMP_CMP(0, SCMP_CMP_EQ, SIG_UNBLOCK)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 1, + SCMP_CMP(0, SCMP_CMP_EQ, SIG_SETMASK)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the flock syscall for + * the seccomp filter sandbox. + * + * NOTE: does not need to be here, occurs before filter is applied. + */ +static int +sb_flock(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(flock), 1, + SCMP_CMP(1, SCMP_CMP_EQ, LOCK_EX|LOCK_NB)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(flock), 1, + SCMP_CMP(1, SCMP_CMP_EQ, LOCK_UN)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the futex syscall for + * the seccomp filter sandbox. + */ +static int +sb_futex(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + // can remove + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 1, + SCMP_CMP(1, SCMP_CMP_EQ, + FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 1, + SCMP_CMP(1, SCMP_CMP_EQ, FUTEX_WAKE_PRIVATE)); + if (rc) + return rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 1, + SCMP_CMP(1, SCMP_CMP_EQ, FUTEX_WAIT_PRIVATE)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the mremap syscall for + * the seccomp filter sandbox. + * + * NOTE: so far only occurs before filter is applied. + */ +static int +sb_mremap(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mremap), 1, + SCMP_CMP(3, SCMP_CMP_EQ, MREMAP_MAYMOVE)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the poll syscall for + * the seccomp filter sandbox. + */ +static int +sb_poll(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(poll), 2, + SCMP_CMP(1, SCMP_CMP_EQ, 1), + SCMP_CMP(2, SCMP_CMP_EQ, 10)); + if (rc) + return rc; + + return 0; +} + +#ifdef __NR_stat64 +/** + * Function responsible for setting up the stat64 syscall for + * the seccomp filter sandbox. + */ +static int +sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && (param->syscall == SCMP_SYS(open) + || param->syscall == SCMP_SYS(stat64))) { + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat64), 1, + SCMP_CMP(0, SCMP_CMP_EQ, param->value)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} +#endif + +/** + * Array of function pointers responsible for filtering different syscalls at + * a parameter level. + */ +static sandbox_filter_func_t filter_func[] = { + sb_rt_sigaction, + sb_rt_sigprocmask, + sb_execve, + sb_time, + sb_accept4, +#ifdef __NR_mmap2 + sb_mmap2, +#endif + sb_open, + sb_openat, +#ifdef __NR_fcntl64 + sb_fcntl64, +#endif + sb_epoll_ctl, + sb_prctl, + sb_mprotect, + sb_flock, + sb_futex, + sb_mremap, + sb_poll, +#ifdef __NR_stat64 + sb_stat64, +#endif + + sb_socket, + sb_setsockopt, + sb_getsockopt, + sb_socketpair +}; + +const char* +sandbox_intern_string(const char *str) +{ + sandbox_cfg_t *elem; + + if (str == NULL) + return NULL; + + for (elem = filter_dynamic; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param->prot && !strcmp(str, (char*)(param->value))) { + return (char*)(param->value); + } + } + + log_info(LD_GENERAL, "(Sandbox) Parameter %s not found", str); + return str; +} + +/** + * Protects all the strings in the sandbox's parameter list configuration. It + * works by calculating the total amount of memory required by the parameter + * list, allocating the memory using mmap, and protecting it from writes with + * mprotect(). + */ +static int +prot_strings(scmp_filter_ctx ctx, sandbox_cfg_t* cfg) +{ + int ret = 0; + size_t pr_mem_size = 0, pr_mem_left = 0; + char *pr_mem_next = NULL, *pr_mem_base; + sandbox_cfg_t *el = NULL; + + // get total number of bytes required to mmap + for (el = cfg; el != NULL; el = el->next) { + pr_mem_size += strlen((char*) ((smp_param_t*)el->param)->value) + 1; + } + + // allocate protected memory with MALLOC_MP_LIM canary + pr_mem_base = (char*) mmap(NULL, MALLOC_MP_LIM + pr_mem_size, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (pr_mem_base == MAP_FAILED) { + log_err(LD_BUG,"(Sandbox) failed allocate protected memory! mmap: %s", + strerror(errno)); + ret = -1; + goto out; + } + + pr_mem_next = pr_mem_base + MALLOC_MP_LIM; + pr_mem_left = pr_mem_size; + + // change el value pointer to protected + for (el = cfg; el != NULL; el = el->next) { + char *param_val = (char*)((smp_param_t *)el->param)->value; + size_t param_size = strlen(param_val) + 1; + + if (pr_mem_left >= param_size) { + // copy to protected + memcpy(pr_mem_next, param_val, param_size); + + // re-point el parameter to protected + { + void *old_val = (void *) ((smp_param_t*)el->param)->value; + tor_free(old_val); + } + ((smp_param_t*)el->param)->value = (intptr_t) pr_mem_next; + ((smp_param_t*)el->param)->prot = 1; + + // move next available protected memory + pr_mem_next += param_size; + pr_mem_left -= param_size; + } else { + log_err(LD_BUG,"(Sandbox) insufficient protected memory!"); + ret = -2; + goto out; + } + } + + // protecting from writes + if (mprotect(pr_mem_base, MALLOC_MP_LIM + pr_mem_size, PROT_READ)) { + log_err(LD_BUG,"(Sandbox) failed to protect memory! mprotect: %s", + strerror(errno)); + ret = -3; + goto out; + } + + /* + * Setting sandbox restrictions so the string memory cannot be tampered with + */ + // no mremap of the protected base address + ret = seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(mremap), 1, + SCMP_CMP(0, SCMP_CMP_EQ, (intptr_t) pr_mem_base)); + if (ret) { + log_err(LD_BUG,"(Sandbox) mremap protected memory filter fail!"); + return ret; + } + + // no munmap of the protected base address + ret = seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(munmap), 1, + SCMP_CMP(0, SCMP_CMP_EQ, (intptr_t) pr_mem_base)); + if (ret) { + log_err(LD_BUG,"(Sandbox) munmap protected memory filter fail!"); + return ret; + } + + /* + * Allow mprotect with PROT_READ|PROT_WRITE because openssl uses it, but + * never over the memory region used by the protected strings. + * + * PROT_READ|PROT_WRITE was originally fully allowed in sb_mprotect(), but + * had to be removed due to limitation of libseccomp regarding intervals. + * + * There is a restriction on how much you can mprotect with R|W up to the + * size of the canary. + */ + ret = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 2, + SCMP_CMP(0, SCMP_CMP_LT, (intptr_t) pr_mem_base), + SCMP_CMP(1, SCMP_CMP_LE, MALLOC_MP_LIM), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE)); + if (ret) { + log_err(LD_BUG,"(Sandbox) mprotect protected memory filter fail (LT)!"); + return ret; + } + + ret = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 2, + SCMP_CMP(0, SCMP_CMP_GT, (intptr_t) pr_mem_base + pr_mem_size + + MALLOC_MP_LIM), + SCMP_CMP(1, SCMP_CMP_LE, MALLOC_MP_LIM), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE)); + if (ret) { + log_err(LD_BUG,"(Sandbox) mprotect protected memory filter fail (GT)!"); + return ret; + } + + out: + return ret; +} + +/** + * Auxiliary function used in order to allocate a sandbox_cfg_t element and set + * it's values according the the parameter list. All elements are initialised + * with the 'prot' field set to false, as the pointer is not protected at this + * point. + */ +static sandbox_cfg_t* +new_element(int syscall, int index, intptr_t value) +{ + smp_param_t *param = NULL; + + sandbox_cfg_t *elem = tor_malloc(sizeof(sandbox_cfg_t)); + elem->param = tor_malloc(sizeof(smp_param_t)); + + param = elem->param; + param->syscall = syscall; + param->pindex = index; + param->value = value; + param->prot = 0; + + return elem; +} + +#ifdef __NR_stat64 +#define SCMP_stat SCMP_SYS(stat64) +#else +#define SCMP_stat SCMP_SYS(stat) +#endif + +int +sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file, int fr) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_stat, 0, (intptr_t)(void*) tor_strdup(file)); + if (!elem) { + log_err(LD_BUG,"(Sandbox) failed to register parameter!"); + return -1; + } + + elem->next = *cfg; + *cfg = elem; + + if (fr) tor_free(file); + return 0; +} + +int +sandbox_cfg_allow_stat_filename_array(sandbox_cfg_t **cfg, ...) +{ + int rc = 0; + char *fn = NULL; + + va_list ap; + va_start(ap, cfg); + + while ((fn = va_arg(ap, char*)) != NULL) { + int fr = va_arg(ap, int); + + rc = sandbox_cfg_allow_stat_filename(cfg, fn, fr); + if (rc) { + log_err(LD_BUG,"(Sandbox) sandbox_cfg_allow_stat_filename_array fail"); + goto end; + } + } + + end: + va_end(ap); + return 0; +} + +int +sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file, int fr) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_SYS(open), 0, (intptr_t)(void *)tor_strdup(file)); + if (!elem) { + log_err(LD_BUG,"(Sandbox) failed to register parameter!"); + return -1; + } + + elem->next = *cfg; + *cfg = elem; + + if (fr) tor_free(file); + + return 0; +} + +int +sandbox_cfg_allow_open_filename_array(sandbox_cfg_t **cfg, ...) +{ + int rc = 0; + char *fn = NULL; + + va_list ap; + va_start(ap, cfg); + + while ((fn = va_arg(ap, char*)) != NULL) { + int fr = va_arg(ap, int); + + rc = sandbox_cfg_allow_open_filename(cfg, fn, fr); + if (rc) { + log_err(LD_BUG,"(Sandbox) sandbox_cfg_allow_open_filename_array fail"); + goto end; + } + } + + end: + va_end(ap); + return 0; +} + +int +sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file, int fr) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_SYS(openat), 1, (intptr_t)(void *)tor_strdup(file)); + if (!elem) { + log_err(LD_BUG,"(Sandbox) failed to register parameter!"); + return -1; + } + + elem->next = *cfg; + *cfg = elem; + + if (fr) tor_free(file); + + return 0; +} + +int +sandbox_cfg_allow_openat_filename_array(sandbox_cfg_t **cfg, ...) +{ + int rc = 0; + char *fn = NULL; + + va_list ap; + va_start(ap, cfg); + + while ((fn = va_arg(ap, char*)) != NULL) { + int fr = va_arg(ap, int); + + rc = sandbox_cfg_allow_openat_filename(cfg, fn, fr); + if (rc) { + log_err(LD_BUG,"(Sandbox) sandbox_cfg_allow_openat_filename_array fail"); + goto end; + } + } + + end: + va_end(ap); + return 0; +} + +int +sandbox_cfg_allow_execve(sandbox_cfg_t **cfg, const char *com) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_SYS(execve), 1, (intptr_t)(void *)tor_strdup(com)); + if (!elem) { + log_err(LD_BUG,"(Sandbox) failed to register parameter!"); + return -1; + } + + elem->next = *cfg; + *cfg = elem; + + return 0; +} + +int +sandbox_cfg_allow_execve_array(sandbox_cfg_t **cfg, ...) +{ + int rc = 0; + char *fn = NULL; + + va_list ap; + va_start(ap, cfg); + + while ((fn = va_arg(ap, char*)) != NULL) { + + rc = sandbox_cfg_allow_execve(cfg, fn); + if (rc) { + log_err(LD_BUG,"(Sandbox) sandbox_cfg_allow_execve_array failed"); + goto end; + } + } + + end: + va_end(ap); + return 0; +} + +int +sandbox_getaddrinfo(const char *name, const char *servname, + const struct addrinfo *hints, + struct addrinfo **res) +{ + sb_addr_info_t *el; + + if (servname != NULL) + return -1; + + *res = NULL; + + for (el = sb_addr_info; el; el = el->next) { + if (!strcmp(el->name, name)) { + *res = tor_malloc(sizeof(struct addrinfo)); + + memcpy(*res, el->info, sizeof(struct addrinfo)); + /* XXXX What if there are multiple items in the list? */ + return 0; + } + } + + if (!sandbox_active) { + if (getaddrinfo(name, NULL, hints, res)) { + log_err(LD_BUG,"(Sandbox) getaddrinfo failed!"); + return -1; + } + + return 0; + } + + // getting here means something went wrong + log_err(LD_BUG,"(Sandbox) failed to get address %s!", name); + if (*res) { + tor_free(*res); + res = NULL; + } + return -1; +} + +int +sandbox_add_addrinfo(const char* name) +{ + int ret; + struct addrinfo hints; + sb_addr_info_t *el = NULL; + + el = tor_malloc(sizeof(sb_addr_info_t)); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + ret = getaddrinfo(name, NULL, &hints, &(el->info)); + if (ret) { + log_err(LD_BUG,"(Sandbox) failed to getaddrinfo"); + ret = -2; + tor_free(el); + goto out; + } + + el->name = tor_strdup(name); + el->next = sb_addr_info; + sb_addr_info = el; + + out: + return ret; +} + +/** + * Function responsible for going through the parameter syscall filters and + * call each function pointer in the list. + */ +static int +add_param_filter(scmp_filter_ctx ctx, sandbox_cfg_t* cfg) +{ + unsigned i; + int rc = 0; + + // function pointer + for (i = 0; i < ARRAY_LENGTH(filter_func); i++) { + if ((filter_func[i])(ctx, cfg)) { + log_err(LD_BUG,"(Sandbox) failed to add syscall %d, received libseccomp " + "error %d", i, rc); + return rc; + } + } + + return 0; +} + +/** + * Function responsible of loading the libseccomp syscall filters which do not + * have parameter filtering. + */ +static int +add_noparam_filter(scmp_filter_ctx ctx) +{ + unsigned i; + int rc = 0; + + // add general filters + for (i = 0; i < ARRAY_LENGTH(filter_nopar_gen); i++) { + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, filter_nopar_gen[i], 0); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add syscall index %d (NR=%d), " + "received libseccomp error %d", i, filter_nopar_gen[i], rc); + return rc; + } + } + + return 0; +} + +/** + * Function responsible for setting up and enabling a global syscall filter. + * The function is a prototype developed for stage 1 of sandboxing Tor. + * Returns 0 on success. + */ +static int +install_syscall_filter(sandbox_cfg_t* cfg) +{ + int rc = 0; + scmp_filter_ctx ctx; + + ctx = seccomp_init(SCMP_ACT_TRAP); + if (ctx == NULL) { + log_err(LD_BUG,"(Sandbox) failed to initialise libseccomp context"); + rc = -1; + goto end; + } + + // protectign sandbox parameter strings + if ((rc = prot_strings(ctx, cfg))) { + goto end; + } + + // add parameter filters + if ((rc = add_param_filter(ctx, cfg))) { + log_err(LD_BUG, "(Sandbox) failed to add param filters!"); + goto end; + } + + // adding filters with no parameters + if ((rc = add_noparam_filter(ctx))) { + log_err(LD_BUG, "(Sandbox) failed to add param filters!"); + goto end; + } + + // loading the seccomp2 filter + if ((rc = seccomp_load(ctx))) { + log_err(LD_BUG, "(Sandbox) failed to load!"); + goto end; + } + + // marking the sandbox as active + sandbox_active = 1; + + end: + seccomp_release(ctx); + return (rc < 0 ? -rc : rc); +} + +/** + * Function called when a SIGSYS is caught by the application. It notifies the + * user that an error has occurred and either terminates or allows the + * application to continue execution, based on the DEBUGGING_CLOSE symbol. + */ +static void +sigsys_debugging(int nr, siginfo_t *info, void *void_context) +{ + ucontext_t *ctx = (ucontext_t *) (void_context); + char number[32]; + int syscall; + (void) nr; + + if (info->si_code != SYS_SECCOMP) + return; + + if (!ctx) + return; + + syscall = ctx->uc_mcontext.gregs[REG_SYSCALL]; + + format_dec_number_sigsafe(syscall, number, sizeof(number)); + tor_log_err_sigsafe("(Sandbox) Caught a bad syscall attempt (syscall ", + number, + ")\n", + NULL); + +#if defined(DEBUGGING_CLOSE) + _exit(1); +#endif // DEBUGGING_CLOSE +} + +/** + * Function that adds a handler for SIGSYS, which is the signal thrown + * when the application is issuing a syscall which is not allowed. The + * main purpose of this function is to help with debugging by identifying + * filtered syscalls. + */ +static int +install_sigsys_debugging(void) +{ + struct sigaction act; + sigset_t mask; + + memset(&act, 0, sizeof(act)); + sigemptyset(&mask); + sigaddset(&mask, SIGSYS); + + act.sa_sigaction = &sigsys_debugging; + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGSYS, &act, NULL) < 0) { + log_err(LD_BUG,"(Sandbox) Failed to register SIGSYS signal handler"); + return -1; + } + + if (sigprocmask(SIG_UNBLOCK, &mask, NULL)) { + log_err(LD_BUG,"(Sandbox) Failed call to sigprocmask()"); + return -2; + } + + return 0; +} + +/** + * Function responsible of registering the sandbox_cfg_t list of parameter + * syscall filters to the existing parameter list. This is used for incipient + * multiple-sandbox support. + */ +static int +register_cfg(sandbox_cfg_t* cfg) +{ + sandbox_cfg_t *elem = NULL; + + if (filter_dynamic == NULL) { + filter_dynamic = cfg; + return 0; + } + + for (elem = filter_dynamic; elem->next != NULL; elem = elem->next); + + elem->next = cfg; + + return 0; +} + +#endif // USE_LIBSECCOMP + +#ifdef USE_LIBSECCOMP +/** + * Initialises the syscall sandbox filter for any linux architecture, taking + * into account various available features for different linux flavours. + */ +static int +initialise_libseccomp_sandbox(sandbox_cfg_t* cfg) +{ + if (install_sigsys_debugging()) + return -1; + + if (install_syscall_filter(cfg)) + return -2; + + if (register_cfg(cfg)) + return -3; + + return 0; +} + +#endif // USE_LIBSECCOMP + +sandbox_cfg_t* +sandbox_cfg_new(void) +{ + return NULL; +} + +int +sandbox_init(sandbox_cfg_t *cfg) +{ +#if defined(USE_LIBSECCOMP) + return initialise_libseccomp_sandbox(cfg); + +#elif defined(_WIN32) + (void)cfg; + log_warn(LD_BUG,"Windows sandboxing is not implemented. The feature is " + "currently disabled."); + return 0; + +#elif defined(TARGET_OS_MAC) + (void)cfg; + log_warn(LD_BUG,"Mac OSX sandboxing is not implemented. The feature is " + "currently disabled"); + return 0; +#else + (void)cfg; + log_warn(LD_BUG,"Sandboxing is not implemented for your platform. The " + "feature is currently disabled"); + return 0; +#endif +} + +#ifndef USE_LIBSECCOMP +int +sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file, + int fr) +{ + (void)cfg; (void)file; (void)fr; + return 0; +} + +int +sandbox_cfg_allow_open_filename_array(sandbox_cfg_t **cfg, ...) +{ + (void)cfg; + return 0; +} + +int +sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file, + int fr) +{ + (void)cfg; (void)file; (void)fr; + return 0; +} + +int +sandbox_cfg_allow_openat_filename_array(sandbox_cfg_t **cfg, ...) +{ + (void)cfg; + return 0; +} + +int +sandbox_cfg_allow_execve(sandbox_cfg_t **cfg, const char *com) +{ + (void)cfg; (void)com; + return 0; +} + +int +sandbox_cfg_allow_execve_array(sandbox_cfg_t **cfg, ...) +{ + (void)cfg; + return 0; +} + +int +sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file, + int fr) +{ + (void)cfg; (void)file; (void)fr; + return 0; +} + +int +sandbox_cfg_allow_stat_filename_array(sandbox_cfg_t **cfg, ...) +{ + (void)cfg; + return 0; +} +#endif + diff --git a/src/common/sandbox.h b/src/common/sandbox.h new file mode 100644 index 000000000..d64d427d3 --- /dev/null +++ b/src/common/sandbox.h @@ -0,0 +1,247 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file sandbox.h + * \brief Header file for sandbox.c. + **/ + +#ifndef SANDBOX_H_ +#define SANDBOX_H_ + +#include "orconfig.h" +#include "torint.h" + +#ifndef SYS_SECCOMP + +/** + * Used by SIGSYS signal handler to check if the signal was issued due to a + * seccomp2 filter violation. + */ +#define SYS_SECCOMP 1 + +#endif + +#if defined(HAVE_SECCOMP_H) && defined(__linux__) +#define USE_LIBSECCOMP +#endif + +struct sandbox_cfg_elem; + +/** Typedef to structure used to manage a sandbox configuration. */ +typedef struct sandbox_cfg_elem sandbox_cfg_t; + +/** + * Linux definitions + */ +#ifdef USE_LIBSECCOMP + +#ifndef __USE_GNU +#define __USE_GNU +#endif +#include <sys/ucontext.h> +#include <seccomp.h> +#include <netdb.h> + +#define PARAM_PTR 0 +#define PARAM_NUM 1 + +/** + * Enum used to manage the type of the implementation for general purpose. + */ +typedef enum { + /** Libseccomp implementation based on seccomp2*/ + LIBSECCOMP2 = 0 +} SB_IMPL; + +/** + * Configuration parameter structure associated with the LIBSECCOMP2 + * implementation. + */ +typedef struct smp_param { + /** syscall associated with parameter. */ + int syscall; + + /** parameter index. */ + int pindex; + /** parameter value. */ + intptr_t value; + + /** parameter flag (0 = not protected, 1 = protected). */ + int prot; +} smp_param_t; + +/** + * Structure used to manage a sandbox configuration. + * + * It is implemented as a linked list of parameters. Currently only controls + * parameters for open, openat, execve, stat64. + */ +struct sandbox_cfg_elem { + /** Sandbox implementation which dictates the parameter type. */ + SB_IMPL implem; + + /** Configuration parameter. */ + void *param; + + /** Next element of the configuration*/ + struct sandbox_cfg_elem *next; +}; + +/** + * Structure used for keeping a linked list of getaddrinfo pre-recorded + * results. + */ +struct sb_addr_info_el { + /** Name of the address info result. */ + char *name; + /** Pre-recorded getaddrinfo result. */ + struct addrinfo *info; + /** Next element in the list. */ + struct sb_addr_info_el *next; +}; +/** Typedef to structure used to manage an addrinfo list. */ +typedef struct sb_addr_info_el sb_addr_info_t; + +/** Function pointer defining the prototype of a filter function.*/ +typedef int (*sandbox_filter_func_t)(scmp_filter_ctx ctx, + sandbox_cfg_t *filter); + +/** Type that will be used in step 3 in order to manage multiple sandboxes.*/ +typedef struct { + /** function pointers associated with the filter */ + sandbox_filter_func_t *filter_func; + + /** filter function pointer parameters */ + sandbox_cfg_t *filter_dynamic; +} sandbox_t; + +/** + * Linux 32 bit definitions + */ +#if defined(__i386__) + +#define REG_SYSCALL REG_EAX + +/** + * Linux 64 bit definitions + */ +#elif defined(__x86_64__) + +#define REG_SYSCALL REG_RAX + +#endif + +#endif // USE_LIBSECCOMP + +#ifdef USE_LIBSECCOMP +/** Pre-calls getaddrinfo in order to pre-record result. */ +int sandbox_add_addrinfo(const char *addr); + +struct addrinfo; +/** Replacement for getaddrinfo(), using pre-recorded results. */ +int sandbox_getaddrinfo(const char *name, const char *servname, + const struct addrinfo *hints, + struct addrinfo **res); +#else +#define sandbox_getaddrinfo(name, servname, hints, res) \ + getaddrinfo((name),(servname), (hints),(res)) +#define sandbox_add_addrinfo(name) \ + ((void)(name)) +#endif + +#ifdef USE_LIBSECCOMP +/** Returns a registered protected string used with the sandbox, given that + * it matches the parameter. + */ +const char* sandbox_intern_string(const char *param); +#else +#define sandbox_intern_string(s) (s) +#endif + +/** Creates an empty sandbox configuration file.*/ +sandbox_cfg_t * sandbox_cfg_new(void); + +/** + * Function used to add a open allowed filename to a supplied configuration. + * The (char*) specifies the path to the allowed file, fr = 1 tells the + * function that the char* needs to be free-ed, 0 means the pointer does not + * need to be free-ed. + */ +int sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file, + int fr); + +/** Function used to add a series of open allowed filenames to a supplied + * configuration. + * @param cfg sandbox configuration. + * @param ... all future parameters are specified as pairs of <(char*), 1 / 0> + * the char* specifies the path to the allowed file, 1 tells the function + * that the char* needs to be free-ed, 0 means the pointer does not need to + * be free-ed; the final parameter needs to be <NULL, 0>. + */ +int sandbox_cfg_allow_open_filename_array(sandbox_cfg_t **cfg, ...); + +/** + * Function used to add a openat allowed filename to a supplied configuration. + * The (char*) specifies the path to the allowed file, fr = 1 tells the + * function that the char* needs to be free-ed, 0 means the pointer does not + * need to be free-ed. + */ +int sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file, + int fr); + +/** Function used to add a series of openat allowed filenames to a supplied + * configuration. + * @param cfg sandbox configuration. + * @param ... all future parameters are specified as pairs of <(char*), 1 / 0> + * the char* specifies the path to the allowed file, 1 tells the function + * that the char* needs to be free-ed, 0 means the pointer does not need to + * be free-ed; the final parameter needs to be <NULL, 0>. + */ +int sandbox_cfg_allow_openat_filename_array(sandbox_cfg_t **cfg, ...); + +/** + * Function used to add a execve allowed filename to a supplied configuration. + * The (char*) specifies the path to the allowed file, fr = 1 tells the + * function that the char* needs to be free-ed, 0 means the pointer does not + * need to be free-ed. + */ +int sandbox_cfg_allow_execve(sandbox_cfg_t **cfg, const char *com); + +/** Function used to add a series of execve allowed filenames to a supplied + * configuration. + * @param cfg sandbox configuration. + * @param ... all future parameters are specified as pairs of <(char*), 1 / 0> + * the char* specifies the path to the allowed file, 1 tells the function + * that the char* needs to be free-ed, 0 means the pointer does not need to + * be free-ed; the final parameter needs to be <NULL, 0>. + */ +int sandbox_cfg_allow_execve_array(sandbox_cfg_t **cfg, ...); + +/** + * Function used to add a stat/stat64 allowed filename to a configuration. + * The (char*) specifies the path to the allowed file, fr = 1 tells the + * function that the char* needs to be free-ed, 0 means the pointer does not + * need to be free-ed. + */ +int sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file, + int fr); + +/** Function used to add a series of stat64 allowed filenames to a supplied + * configuration. + * @param cfg sandbox configuration. + * @param ... all future parameters are specified as pairs of <(char*), 1 / 0> + * the char* specifies the path to the allowed file, 1 tells the function + * that the char* needs to be free-ed, 0 means the pointer does not need to + * be free-ed; the final parameter needs to be <NULL, 0>. + */ +int sandbox_cfg_allow_stat_filename_array(sandbox_cfg_t **cfg, ...); + +/** Function used to initialise a sandbox configuration.*/ +int sandbox_init(sandbox_cfg_t* cfg); + +#endif /* SANDBOX_H_ */ + diff --git a/src/common/testsupport.h b/src/common/testsupport.h new file mode 100644 index 000000000..4a4f50b69 --- /dev/null +++ b/src/common/testsupport.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_TESTSUPPORT_H +#define TOR_TESTSUPPORT_H + +#ifdef TOR_UNIT_TESTS +#define STATIC +#else +#define STATIC static +#endif + +/** Quick and dirty macros to implement test mocking. + * + * To use them, suppose that you have a function you'd like to mock + * with the signature "void writebuf(size_t n, char *buf)". You can then + * declare the function as: + * + * MOCK_DECL(void, writebuf, (size_t n, char *buf)); + * + * and implement it as: + * + * MOCK_IMPL(void + * writebuf,(size_t n, char *buf) + * { + * ... + * } + * + * For the non-testing build, this will expand simply into: + * + * void writebuf(size_t n, char *buf); + * void + * writebuf(size_t n, char *buf) + * { + * ... + * } + * + * But for the testing case, it will expand into: + * + * void writebuf__real(size_t n, char *buf); + * extern void (*writebuf)(size_t n, char *buf); + * + * void (*writebuf)(size_t n, char *buf) = writebuf__real; + * void + * writebuf__real(size_t n, char *buf) + * { + * ... + * } + * + * This is not a great mocking system! It is deliberately "the simplest + * thing that could work", and pays for its simplicity in its lack of + * features, and in its uglification of the Tor code. Replacing it with + * something clever would be a fine thing. + * + * @{ */ +#ifdef TOR_UNIT_TESTS +#define MOCK_DECL(rv, funcname, arglist) \ + rv funcname ##__real arglist; \ + extern rv(*funcname) arglist +#define MOCK_IMPL(rv, funcname, arglist) \ + rv(*funcname) arglist = funcname ##__real; \ + rv funcname ##__real arglist +#define MOCK(func, replacement) \ + do { \ + (func) = (replacement); \ + } while (0) +#define UNMOCK(func) \ + do { \ + func = func ##__real; \ + } while (0) +#else +#define MOCK_DECL(rv, funcname, arglist) \ + rv funcname arglist +#define MOCK_IMPL(rv, funcname, arglist) \ + rv funcname arglist +#endif +/** @} */ + +#endif + diff --git a/src/common/torgzip.c b/src/common/torgzip.c index 4328c63c8..15451ee30 100644 --- a/src/common/torgzip.c +++ b/src/common/torgzip.c @@ -68,6 +68,22 @@ is_gzip_supported(void) return gzip_is_supported; } +/** 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) diff --git a/src/common/torgzip.h b/src/common/torgzip.h index be1016445..5db03fe6e 100644 --- a/src/common/torgzip.h +++ b/src/common/torgzip.h @@ -32,6 +32,12 @@ tor_gzip_uncompress(char **out, size_t *out_len, 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 diff --git a/src/common/torlog.h b/src/common/torlog.h index 8675d7b6e..d210c8b24 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -102,6 +102,9 @@ /** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */ #define LD_NOCB (1u<<31) +/** This log message should not include a function name, even if it otherwise + * would. Used as a flag, not a log domain. */ +#define LD_NOFUNCNAME (1u<<30) /** Mask of zero or more log domains, OR'd together. */ typedef uint32_t log_domain_mask_t; @@ -114,12 +117,6 @@ typedef struct log_severity_list_t { log_domain_mask_t masks[LOG_DEBUG-LOG_ERR+1]; } log_severity_list_t; -#ifdef LOG_PRIVATE -/** Given a severity, yields an index into log_severity_list_t.masks to use - * for that severity. */ -#define SEVERITY_MASK_IDX(sev) ((sev) - LOG_ERR) -#endif - /** Callback type used for add_callback_log. */ typedef void (*log_callback)(int severity, uint32_t domain, const char *msg); @@ -154,6 +151,10 @@ void set_log_time_granularity(int granularity_msec); void tor_log(int severity, log_domain_mask_t domain, const char *format, ...) CHECK_PRINTF(3,4); +void tor_log_err_sigsafe(const char *m, ...); +int tor_log_get_sigsafe_err_fds(const int **out); +void tor_log_update_sigsafe_err_fds(void); + #if defined(__GNUC__) || defined(RUNNING_DOXYGEN) extern int log_global_min_severity_; diff --git a/src/common/tortls.c b/src/common/tortls.c index 60444f1b8..3d6efeba0 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -48,9 +48,6 @@ #include "compat_libevent.h" #endif -#define CRYPTO_PRIVATE /* to import prototypes from crypto.h */ -#define TORTLS_PRIVATE - #include "crypto.h" #include "tortls.h" #include "util.h" @@ -806,24 +803,24 @@ tor_cert_new(X509 *x509_cert) tor_cert_t *cert; EVP_PKEY *pkey; RSA *rsa; - int length, length2; - unsigned char *cp; + int length; + unsigned char *buf = NULL; if (!x509_cert) return NULL; - length = i2d_X509(x509_cert, NULL); + length = i2d_X509(x509_cert, &buf); cert = tor_malloc_zero(sizeof(tor_cert_t)); - if (length <= 0) { + if (length <= 0 || buf == NULL) { tor_free(cert); log_err(LD_CRYPTO, "Couldn't get length of encoded x509 certificate"); X509_free(x509_cert); return NULL; } cert->encoded_len = (size_t) length; - cp = cert->encoded = tor_malloc(length); - length2 = i2d_X509(x509_cert, &cp); - tor_assert(length2 == length); + cert->encoded = tor_malloc(length); + memcpy(cert->encoded, buf, length); + OPENSSL_free(buf); cert->cert = x509_cert; @@ -979,31 +976,6 @@ tor_tls_cert_get_key(tor_cert_t *cert) return result; } -/** Return true iff <b>a</b> and <b>b</b> represent the same public key. */ -static int -pkey_eq(EVP_PKEY *a, EVP_PKEY *b) -{ - /* We'd like to do this, but openssl 0.9.7 doesn't have it: - return EVP_PKEY_cmp(a,b) == 1; - */ - unsigned char *a_enc=NULL, *b_enc=NULL, *a_ptr, *b_ptr; - int a_len1, b_len1, a_len2, b_len2, result; - a_len1 = i2d_PublicKey(a, NULL); - b_len1 = i2d_PublicKey(b, NULL); - if (a_len1 != b_len1) - return 0; - a_ptr = a_enc = tor_malloc(a_len1); - b_ptr = b_enc = tor_malloc(b_len1); - a_len2 = i2d_PublicKey(a, &a_ptr); - b_len2 = i2d_PublicKey(b, &b_ptr); - tor_assert(a_len2 == a_len1); - tor_assert(b_len2 == b_len1); - result = tor_memeq(a_enc, b_enc, a_len1); - tor_free(a_enc); - tor_free(b_enc); - return result; -} - /** Return true iff the other side of <b>tls</b> has authenticated to us, and * the key certified in <b>cert</b> is the same as the key they used to do it. */ @@ -1019,7 +991,7 @@ tor_tls_cert_matches_key(const tor_tls_t *tls, const tor_cert_t *cert) link_key = X509_get_pubkey(peercert); cert_key = X509_get_pubkey(cert->cert); - result = link_key && cert_key && pkey_eq(cert_key, link_key); + result = link_key && cert_key && EVP_PKEY_cmp(cert_key, link_key) == 1; X509_free(peercert); if (link_key) @@ -1418,6 +1390,21 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, return NULL; } +/** Invoked when a TLS state changes: log the change at severity 'debug' */ +static void +tor_tls_debug_state_callback(const SSL *ssl, int type, int val) +{ + log_debug(LD_HANDSHAKE, "SSL %p is now in state %s [type=%d,val=%d].", + ssl, SSL_state_string_long(ssl), type, val); +} + +/* Return the name of the negotiated ciphersuite in use on <b>tls</b> */ +const char * +tor_tls_get_ciphersuite_name(tor_tls_t *tls) +{ + return SSL_get_cipher(tls->ssl); +} + #ifdef V2_HANDSHAKE_SERVER /* Here's the old V2 cipher list we sent from 0.2.1.1-alpha up to @@ -1486,13 +1473,6 @@ prune_v2_cipher_list(void) v2_cipher_list_pruned = 1; } -/* Return the name of the negotiated ciphersuite in use on <b>tls</b> */ -const char * -tor_tls_get_ciphersuite_name(tor_tls_t *tls) -{ - return SSL_get_cipher(tls->ssl); -} - /** Examine the client cipher list in <b>ssl</b>, and determine what kind of * client it is. Return one of CIPHERS_ERR, CIPHERS_V1, CIPHERS_V2, * CIPHERS_UNRESTRICTED. @@ -1591,56 +1571,6 @@ tor_tls_client_is_using_v2_ciphers(const SSL *ssl) return tor_tls_classify_client_ciphers(ssl, session->ciphers) >= CIPHERS_V2; } -#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,0) -/** Callback to get invoked on a server after we've read the list of ciphers - * the client supports, but before we pick our own ciphersuite. - * - * We can't abuse an info_cb for this, since by the time one of the - * client_hello info_cbs is called, we've already picked which ciphersuite to - * use. - * - * Technically, this function is an abuse of this callback, since the point of - * a session_secret_cb is to try to set up and/or verify a shared-secret for - * authentication on the fly. But as long as we return 0, we won't actually be - * setting up a shared secret, and all will be fine. - */ -static int -tor_tls_session_secret_cb(SSL *ssl, void *secret, int *secret_len, - STACK_OF(SSL_CIPHER) *peer_ciphers, - SSL_CIPHER **cipher, void *arg) -{ - (void) secret; - (void) secret_len; - (void) peer_ciphers; - (void) cipher; - (void) arg; - - if (tor_tls_classify_client_ciphers(ssl, peer_ciphers) == - CIPHERS_UNRESTRICTED) { - SSL_set_cipher_list(ssl, UNRESTRICTED_SERVER_CIPHER_LIST); - } - - SSL_set_session_secret_cb(ssl, NULL, NULL); - - return 0; -} -static void -tor_tls_setup_session_secret_cb(tor_tls_t *tls) -{ - SSL_set_session_secret_cb(tls->ssl, tor_tls_session_secret_cb, NULL); -} -#else -#define tor_tls_setup_session_secret_cb(tls) STMT_NIL -#endif - -/** Invoked when a TLS state changes: log the change at severity 'debug' */ -static void -tor_tls_debug_state_callback(const SSL *ssl, int type, int val) -{ - log_debug(LD_HANDSHAKE, "SSL %p is now in state %s [type=%d,val=%d].", - ssl, SSL_state_string_long(ssl), type, val); -} - /** Invoked when we're accepting a connection on <b>ssl</b>, and the connection * changes state. We use this: * <ul><li>To alter the state of the handshake partway through, so we @@ -1700,6 +1630,48 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) } #endif +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,0) +/** Callback to get invoked on a server after we've read the list of ciphers + * the client supports, but before we pick our own ciphersuite. + * + * We can't abuse an info_cb for this, since by the time one of the + * client_hello info_cbs is called, we've already picked which ciphersuite to + * use. + * + * Technically, this function is an abuse of this callback, since the point of + * a session_secret_cb is to try to set up and/or verify a shared-secret for + * authentication on the fly. But as long as we return 0, we won't actually be + * setting up a shared secret, and all will be fine. + */ +static int +tor_tls_session_secret_cb(SSL *ssl, void *secret, int *secret_len, + STACK_OF(SSL_CIPHER) *peer_ciphers, + SSL_CIPHER **cipher, void *arg) +{ + (void) secret; + (void) secret_len; + (void) peer_ciphers; + (void) cipher; + (void) arg; + + if (tor_tls_classify_client_ciphers(ssl, peer_ciphers) == + CIPHERS_UNRESTRICTED) { + SSL_set_cipher_list(ssl, UNRESTRICTED_SERVER_CIPHER_LIST); + } + + SSL_set_session_secret_cb(ssl, NULL, NULL); + + return 0; +} +static void +tor_tls_setup_session_secret_cb(tor_tls_t *tls) +{ + SSL_set_session_secret_cb(tls->ssl, tor_tls_session_secret_cb, NULL); +} +#else +#define tor_tls_setup_session_secret_cb(tls) STMT_NIL +#endif + /** Explain which ciphers we're missing. */ static void log_unsupported_ciphers(smartlist_t *unsupported) diff --git a/src/common/util.c b/src/common/util.c index 5eb0f9a69..054de3dbe 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -24,6 +24,8 @@ #include "torint.h" #include "container.h" #include "address.h" +#include "sandbox.h" +#include "backtrace.h" #ifdef _WIN32 #include <io.h> @@ -94,6 +96,23 @@ #endif /* ===== + * Assertion helper. + * ===== */ +/** Helper for tor_assert: report the assertion failure. */ +void +tor_assertion_failed_(const char *fname, unsigned int line, + const char *func, const char *expr) +{ + char buf[256]; + log_err(LD_BUG, "%s:%u: %s: Assertion %s failed; aborting.", + fname, line, func, expr); + tor_snprintf(buf, sizeof(buf), + "Assertion %s failed in %s at %s:%u", + expr, func, fname, line); + log_backtrace(LOG_ERR, LD_BUG, buf); +} + +/* ===== * Memory management * ===== */ #ifdef USE_DMALLOC @@ -879,6 +898,39 @@ tor_digest_is_zero(const char *digest) return tor_memeq(digest, ZERO_DIGEST, DIGEST_LEN); } +/** Return true if <b>string</b> is a valid '<key>=[<value>]' string. + * <value> is optional, to indicate the empty string. Log at logging + * <b>severity</b> if something ugly happens. */ +int +string_is_key_value(int severity, const char *string) +{ + /* position of equal sign in string */ + const char *equal_sign_pos = NULL; + + tor_assert(string); + + if (strlen(string) < 2) { /* "x=" is shortest args string */ + tor_log(severity, LD_GENERAL, "'%s' is too short to be a k=v value.", + escaped(string)); + return 0; + } + + equal_sign_pos = strchr(string, '='); + if (!equal_sign_pos) { + tor_log(severity, LD_GENERAL, "'%s' is not a k=v value.", escaped(string)); + return 0; + } + + /* validate that the '=' is not in the beginning of the string. */ + if (equal_sign_pos == string) { + tor_log(severity, LD_GENERAL, "'%s' is not a valid k=v value.", + escaped(string)); + return 0; + } + + return 1; +} + /** Return true iff the DIGEST256_LEN bytes in digest are all zero. */ int tor_digest256_is_zero(const char *digest) @@ -1190,6 +1242,43 @@ escaped(const char *s) return escaped_val_; } +/** Return a newly allocated string equal to <b>string</b>, except that every + * character in <b>chars_to_escape</b> is preceded by a backslash. */ +char * +tor_escape_str_for_pt_args(const char *string, const char *chars_to_escape) +{ + char *new_string = NULL; + char *new_cp = NULL; + size_t length, new_length; + + tor_assert(string); + + length = strlen(string); + + if (!length) /* If we were given the empty string, return the same. */ + return tor_strdup(""); + /* (new_length > SIZE_MAX) => ((length * 2) + 1 > SIZE_MAX) => + (length*2 > SIZE_MAX - 1) => (length > (SIZE_MAX - 1)/2) */ + if (length > (SIZE_MAX - 1)/2) /* check for overflow */ + return NULL; + + /* this should be enough even if all characters must be escaped */ + new_length = (length * 2) + 1; + + new_string = new_cp = tor_malloc(new_length); + + while (*string) { + if (strchr(chars_to_escape, *string)) + *new_cp++ = '\\'; + + *new_cp++ = *string++; + } + + *new_cp = '\0'; /* NUL-terminate the new string */ + + return new_string; +} + /* ===== * Time * ===== */ @@ -1741,7 +1830,7 @@ file_status(const char *fname) int r; f = tor_strdup(fname); clean_name_for_stat(f); - r = stat(f, &st); + r = stat(sandbox_intern_string(f), &st); tor_free(f); if (r) { if (errno == ENOENT) { @@ -1791,7 +1880,7 @@ check_private_dir(const char *dirname, cpd_check_t check, tor_assert(dirname); f = tor_strdup(dirname); clean_name_for_stat(f); - r = stat(f, &st); + r = stat(sandbox_intern_string(f), &st); tor_free(f); if (r) { if (errno != ENOENT) { @@ -1977,8 +2066,10 @@ start_writing_to_file(const char *fname, int open_flags, int mode, open_flags &= ~O_EXCL; new_file->rename_on_close = 1; } +#if O_BINARY != 0 if (open_flags & O_BINARY) new_file->binary = 1; +#endif new_file->fd = tor_open_cloexec(open_name, open_flags, mode); if (new_file->fd < 0) { @@ -2132,12 +2223,20 @@ write_chunks_to_file_impl(const char *fname, const smartlist_t *chunks, return -1; } -/** Given a smartlist of sized_chunk_t, write them atomically to a file - * <b>fname</b>, overwriting or creating the file as necessary. */ +/** Given a smartlist of sized_chunk_t, write them to a file + * <b>fname</b>, overwriting or creating the file as necessary. + * If <b>no_tempfile</b> is 0 then the file will be written + * atomically. */ int -write_chunks_to_file(const char *fname, const smartlist_t *chunks, int bin) +write_chunks_to_file(const char *fname, const smartlist_t *chunks, int bin, + int no_tempfile) { int flags = OPEN_FLAGS_REPLACE|(bin?O_BINARY:O_TEXT); + + if (no_tempfile) { + /* O_APPEND stops write_chunks_to_file from using tempfiles */ + flags |= O_APPEND; + } return write_chunks_to_file_impl(fname, chunks, flags); } @@ -2158,9 +2257,9 @@ write_bytes_to_file_impl(const char *fname, const char *str, size_t len, /** As write_str_to_file, but does not assume a NUL-terminated * string. Instead, we write <b>len</b> bytes, starting at <b>str</b>. */ -int -write_bytes_to_file(const char *fname, const char *str, size_t len, - int bin) +MOCK_IMPL(int, +write_bytes_to_file,(const char *fname, const char *str, size_t len, + int bin)) { return write_bytes_to_file_impl(fname, str, len, OPEN_FLAGS_REPLACE|(bin?O_BINARY:O_TEXT)); @@ -3022,9 +3121,10 @@ tor_listdir(const char *dirname) FindClose(handle); tor_free(pattern); #else + const char *prot_dname = sandbox_intern_string(dirname); DIR *d; struct dirent *de; - if (!(d = opendir(dirname))) + if (!(d = opendir(prot_dname))) return NULL; result = smartlist_new(); @@ -3320,14 +3420,59 @@ tor_join_win_cmdline(const char *argv[]) return joined_argv; } +/* As format_{hex,dex}_number_sigsafe, but takes a <b>radix</b> argument + * in range 2..16 inclusive. */ +static int +format_number_sigsafe(unsigned long x, char *buf, int buf_len, + unsigned int radix) +{ + unsigned long tmp; + int len; + char *cp; + + /* NOT tor_assert. This needs to be safe to run from within a signal handler, + * and from within the 'tor_assert() has failed' code. */ + if (radix < 2 || radix > 16) + return 0; + + /* Count how many digits we need. */ + tmp = x; + len = 1; + while (tmp >= radix) { + tmp /= radix; + ++len; + } + + /* Not long enough */ + if (!buf || len >= buf_len) + return 0; + + cp = buf + len; + *cp = '\0'; + do { + unsigned digit = x % radix; + tor_assert(cp > buf); + --cp; + *cp = "0123456789ABCDEF"[digit]; + x /= radix; + } while (x); + + /* NOT tor_assert; see above. */ + if (cp != buf) { + abort(); + } + + return len; +} + /** - * Helper function to output hex numbers, called by - * format_helper_exit_status(). This writes the hexadecimal digits of x into - * buf, up to max_len digits, and returns the actual number of digits written. - * If there is insufficient space, it will write nothing and return 0. + * Helper function to output hex numbers from within a signal handler. + * + * Writes the nul-terminated hexadecimal digits of <b>x</b> into a buffer + * <b>buf</b> of size <b>buf_len</b>, and return the actual number of digits + * written, not counting the terminal NUL. * - * This function DOES NOT add a terminating NUL character to its output: be - * careful! + * If there is insufficient space, write nothing and return 0. * * This accepts an unsigned int because format_helper_exit_status() needs to * call it with a signed int and an unsigned char, and since the C standard @@ -3342,46 +3487,19 @@ tor_join_win_cmdline(const char *argv[]) * arbitrary C functions. */ int -format_hex_number_for_helper_exit_status(unsigned int x, char *buf, - int max_len) +format_hex_number_sigsafe(unsigned long x, char *buf, int buf_len) { - int len; - unsigned int tmp; - char *cur; - - /* Sanity check */ - if (!buf || max_len <= 0) - return 0; - - /* How many chars do we need for x? */ - if (x > 0) { - len = 0; - tmp = x; - while (tmp > 0) { - tmp >>= 4; - ++len; - } - } else { - len = 1; - } - - /* Bail if we would go past the end of the buffer */ - if (len > max_len) - return 0; - - /* Point to last one */ - cur = buf + len - 1; - - /* Convert x to hex */ - do { - *cur-- = "0123456789ABCDEF"[x & 0xf]; - x >>= 4; - } while (x != 0 && cur >= buf); + return format_number_sigsafe(x, buf, buf_len, 16); +} - /* Return len */ - return len; +/** As format_hex_number_sigsafe, but format the number in base 10. */ +int +format_dec_number_sigsafe(unsigned long x, char *buf, int buf_len) +{ + return format_number_sigsafe(x, buf, buf_len, 10); } +#ifndef _WIN32 /** Format <b>child_state</b> and <b>saved_errno</b> as a hex string placed in * <b>hex_errno</b>. Called between fork and _exit, so must be signal-handler * safe. @@ -3397,7 +3515,7 @@ format_hex_number_for_helper_exit_status(unsigned int x, char *buf, * On success return the number of characters added to hex_errno, not counting * the terminating NUL; return -1 on error. */ -int +STATIC int format_helper_exit_status(unsigned char child_state, int saved_errno, char *hex_errno) { @@ -3428,8 +3546,8 @@ format_helper_exit_status(unsigned char child_state, int saved_errno, cur = hex_errno; /* Emit child_state */ - written = format_hex_number_for_helper_exit_status(child_state, - cur, left); + written = format_hex_number_sigsafe(child_state, cur, left); + if (written <= 0) goto err; @@ -3458,8 +3576,7 @@ format_helper_exit_status(unsigned char child_state, int saved_errno, } /* Emit unsigned_errno */ - written = format_hex_number_for_helper_exit_status(unsigned_errno, - cur, left); + written = format_hex_number_sigsafe(unsigned_errno, cur, left); if (written <= 0) goto err; @@ -3490,6 +3607,7 @@ format_helper_exit_status(unsigned char child_state, int saved_errno, done: return res; } +#endif /* Maximum number of file descriptors, if we cannot get it via sysconf() */ #define DEFAULT_MAX_FD 256 @@ -3685,7 +3803,7 @@ tor_spawn_background(const char *const filename, const char **argv, TRUE, // handles are inherited /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess() * work?) */ - 0, // creation flags + CREATE_NO_WINDOW, // creation flags (env==NULL) ? NULL : env->windows_environment_block, NULL, // use parent's current directory &siStartInfo, // STARTUPINFO pointer @@ -3906,9 +4024,9 @@ tor_spawn_background(const char *const filename, const char **argv, * <b>process_handle</b>. * If <b>also_terminate_process</b> is true, also terminate the * process of the process handle. */ -void -tor_process_handle_destroy(process_handle_t *process_handle, - int also_terminate_process) +MOCK_IMPL(void, +tor_process_handle_destroy,(process_handle_t *process_handle, + int also_terminate_process)) { if (!process_handle) return; @@ -4417,9 +4535,9 @@ stream_status_to_string(enum stream_status stream_status) /** Return a smartlist containing lines outputted from * <b>handle</b>. Return NULL on error, and set * <b>stream_status_out</b> appropriately. */ -smartlist_t * -tor_get_lines_from_handle(HANDLE *handle, - enum stream_status *stream_status_out) +MOCK_IMPL(smartlist_t *, +tor_get_lines_from_handle, (HANDLE *handle, + enum stream_status *stream_status_out)) { int pos; char stdout_buf[600] = {0}; @@ -4507,8 +4625,9 @@ log_from_handle(HANDLE *pipe, int severity) /** Return a smartlist containing lines outputted from * <b>handle</b>. Return NULL on error, and set * <b>stream_status_out</b> appropriately. */ -smartlist_t * -tor_get_lines_from_handle(FILE *handle, enum stream_status *stream_status_out) +MOCK_IMPL(smartlist_t *, +tor_get_lines_from_handle, (FILE *handle, + enum stream_status *stream_status_out)) { enum stream_status stream_status; char stdout_buf[400]; diff --git a/src/common/util.h b/src/common/util.h index 73daa6e2a..18dc20639 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -15,6 +15,7 @@ #include "torint.h" #include "compat.h" #include "di_ops.h" +#include "testsupport.h" #include <stdio.h> #include <stdlib.h> #ifdef _WIN32 @@ -47,13 +48,13 @@ /** Like assert(3), but send assertion failures to the log as well as to * stderr. */ #define tor_assert(expr) STMT_BEGIN \ - if (PREDICT_UNLIKELY(!(expr))) { \ - log_err(LD_BUG, "%s:%d: %s: Assertion %s failed; aborting.", \ - SHORT_FILE__, __LINE__, __func__, #expr); \ - fprintf(stderr,"%s:%d %s: Assertion %s failed; aborting.\n", \ - SHORT_FILE__, __LINE__, __func__, #expr); \ - abort(); \ - } STMT_END + if (PREDICT_UNLIKELY(!(expr))) { \ + tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, #expr); \ + abort(); \ + } STMT_END + +void tor_assertion_failed_(const char *fname, unsigned int line, + const char *func, const char *expr); /* If we're building with dmalloc, we want all of our memory allocation * functions to take an extra file/line pair of arguments. If not, not. @@ -222,23 +223,22 @@ const char *find_whitespace_eos(const char *s, const char *eos); const char *find_str_at_start_of_line(const char *haystack, const char *needle); int string_is_C_identifier(const char *string); +int string_is_key_value(int severity, const char *string); int tor_mem_is_zero(const char *mem, size_t len); int tor_digest_is_zero(const char *digest); int tor_digest256_is_zero(const char *digest); char *esc_for_log(const char *string) ATTR_MALLOC; const char *escaped(const char *string); + +char *tor_escape_str_for_pt_args(const char *string, + const char *chars_to_escape); + struct smartlist_t; -int tor_vsscanf(const char *buf, const char *pattern, va_list ap) -#ifdef __GNUC__ - __attribute__((format(scanf, 2, 0))) -#endif - ; +int tor_vsscanf(const char *buf, const char *pattern, va_list ap) \ + CHECK_SCANF(2, 0); int tor_sscanf(const char *buf, const char *pattern, ...) -#ifdef __GNUC__ - __attribute__((format(scanf, 2, 3))) -#endif - ; + CHECK_SCANF(2, 3); void smartlist_add_asprintf(struct smartlist_t *sl, const char *pattern, ...) CHECK_PRINTF(2, 3); @@ -356,8 +356,9 @@ FILE *fdopen_file(open_file_t *file_data); int finish_writing_to_file(open_file_t *file_data); int abort_writing_to_file(open_file_t *file_data); int write_str_to_file(const char *fname, const char *str, int bin); -int write_bytes_to_file(const char *fname, const char *str, size_t len, - int bin); +MOCK_DECL(int, +write_bytes_to_file,(const char *fname, const char *str, size_t len, + int bin)); /** An ad-hoc type to hold a string of characters and a count; used by * write_chunks_to_file. */ typedef struct sized_chunk_t { @@ -365,7 +366,7 @@ typedef struct sized_chunk_t { size_t len; } sized_chunk_t; int write_chunks_to_file(const char *fname, const struct smartlist_t *chunks, - int bin); + int bin, int no_tempfile); int append_bytes_to_file(const char *fname, const char *str, size_t len, int bin); int write_bytes_to_new_file(const char *fname, const char *str, size_t len, @@ -493,18 +494,21 @@ FILE *tor_process_get_stdout_pipe(process_handle_t *process_handle); #endif #ifdef _WIN32 -struct smartlist_t * -tor_get_lines_from_handle(HANDLE *handle, - enum stream_status *stream_status); +MOCK_DECL(struct smartlist_t *, +tor_get_lines_from_handle,(HANDLE *handle, + enum stream_status *stream_status)); #else -struct smartlist_t * -tor_get_lines_from_handle(FILE *handle, - enum stream_status *stream_status); +MOCK_DECL(struct smartlist_t *, +tor_get_lines_from_handle,(FILE *handle, + enum stream_status *stream_status)); #endif -int tor_terminate_process(process_handle_t *process_handle); -void tor_process_handle_destroy(process_handle_t *process_handle, - int also_terminate_process); +int +tor_terminate_process(process_handle_t *process_handle); + +MOCK_DECL(void, +tor_process_handle_destroy,(process_handle_t *process_handle, + int also_terminate_process)); /* ===== Insecure rng */ typedef struct tor_weak_rng_t { @@ -520,12 +524,14 @@ int32_t tor_weak_random_range(tor_weak_rng_t *rng, int32_t top); * <b>n</b> */ #define tor_weak_random_one_in_n(rng, n) (0==tor_weak_random_range((rng),(n))) +int format_hex_number_sigsafe(unsigned long x, char *buf, int max_len); +int format_dec_number_sigsafe(unsigned long x, char *buf, int max_len); + #ifdef UTIL_PRIVATE /* Prototypes for private functions only used by util.c (and unit tests) */ -int format_hex_number_for_helper_exit_status(unsigned int x, char *buf, - int max_len); -int format_helper_exit_status(unsigned char child_state, +#ifndef _WIN32 +STATIC int format_helper_exit_status(unsigned char child_state, int saved_errno, char *hex_errno); /* Space for hex values of child state, a slash, saved_errno (with @@ -534,7 +540,11 @@ int format_helper_exit_status(unsigned char child_state, 1 + sizeof(int) * 2 + 1) #endif +#endif + const char *libor_get_digests(void); +#define ARRAY_LENGTH(x) (sizeof(x)) / sizeof(x[0]) + #endif diff --git a/src/config/deanonymind.py b/src/config/deanonymind.py index c86dadca9..31d0658ee 100755 --- a/src/config/deanonymind.py +++ b/src/config/deanonymind.py @@ -156,23 +156,34 @@ def apply_manual_changes(assignments, manual_assignments): entry['end_num'] == manual_entry['end_num']: if len(manual_entry['country_code']) != 2: print '-%s' % (line, ) # only remove, don't replace - else: + del manual_dict[start_num] + elif entry['country_code'] != \ + manual_entry['country_code']: new_line = format_line_with_other_country(entry, manual_entry) print '-%s\n+%s' % (line, new_line, ) result.append(new_line) - del manual_dict[start_num] + del manual_dict[start_num] + else: + print ('Warning: not applying ineffective manual ' + 'change:\n %s\n %s' % (line, manual_line, )) + result.append(line) else: - print ('Warning: only partial match between ' - 'original/automatically replaced assignment and ' - 'manual assignment:\n %s\n %s\nNot applying ' - 'manual change.' % (line, manual_line, )) + print ('Warning: not applying manual change that is only ' + 'a partial match:\n %s\n %s' % + (line, manual_line, )) result.append(line) + elif 'country_code' in entry and \ + entry['country_code'] == 'A1': + print ('Warning: no manual replacement for A1 entry:\n %s' + % (line, )) + result.append(line) else: result.append(line) if len(manual_dict) > 0: - print ('Warning: could not apply all manual assignments: %s' % - ('\n '.join(manual_dict.values())), ) + print 'Warning: could not apply all manual assignments:' + for line in manual_dict.values(): + print ' %s' % (line, ) return result def write_file(path, assignments, long_format=True): diff --git a/src/config/geoip-manual b/src/config/geoip-manual index 99c897ff4..d51a12a1c 100644 --- a/src/config/geoip-manual +++ b/src/config/geoip-manual @@ -3,22 +3,11 @@ # directory to process this file when producing a new geoip file. See # README.geoip in the same directory for details. -# Remove MaxMind entry 0.116.0.0-0.119.255.255 which MaxMind says is AT, -# but which is part of reserved range 0.0.0.0/8. -KL 2012-06-13 -# Disabled, because MaxMind apparently removed this range from their -# database. -KL 2013-02-08 -#"0.116.0.0","0.119.255.255","7602176","7864319","","" - -# NL, because previous MaxMind entry 31.171.128.0-31.171.133.255 is NL, -# and RIR delegation files say 31.171.128.0-31.171.135.255 is NL. -# -KL 2012-11-27 -"31.171.134.0","31.171.135.255","531334656","531335167","NL","Netherlands" - -# EU, because next MaxMind entry 37.139.64.1-37.139.64.9 is EU, because -# RIR delegation files say 37.139.64.0-37.139.71.255 is EU, and because it -# just makes more sense for the next entry to start at .0 and not .1. -# -KL 2012-11-27 -"37.139.64.0","37.139.64.0","629882880","629882880","EU","Europe" +# GB, because RIR delegation files say exactly this range +# 46.16.32.0-46.16.39.255 is GB, even though neither previous nor next +# MaxMind range is GB. Both previous and next MaxMind ranges match RIR +# delegation files, too. -KL 2013-03-07 +"46.16.32.0","46.16.39.255","772808704","772810751","GB","United Kingdom" # CH, because previous MaxMind entry 46.19.141.0-46.19.142.255 is CH, and # RIR delegation files say 46.19.136.0-46.19.143.255 is CH. @@ -30,23 +19,6 @@ # -KL 2012-11-27 "46.166.128.0","46.166.128.255","782663680","782663935","GB","United Kingdom" -# US, though could as well be CA. Previous MaxMind entry -# 64.237.32.52-64.237.34.127 is US, next MaxMind entry -# 64.237.34.144-64.237.34.151 is CA, and RIR delegation files say the -# entire block 64.237.32.0-64.237.63.255 is US. -KL 2012-11-27 -"64.237.34.128","64.237.34.143","1089282688","1089282703","US","United States" - -# US, though could as well be UY. Previous MaxMind entry -# 67.15.170.0-67.15.182.255 is US, next MaxMind entry -# 67.15.183.128-67.15.183.159 is UY, and RIR delegation files say the -# entire block 67.15.0.0-67.15.255.255 is US. -KL 2012-11-27 -"67.15.183.0","67.15.183.127","1125103360","1125103487","US","United States" - -# US, because next MaxMind entry 67.43.145.0-67.43.155.255 is US, and RIR -# delegation files say 67.43.144.0-67.43.159.255 is US. -# -KL 2012-11-27 -"67.43.144.0","67.43.144.255","1126928384","1126928639","US","United States" - # US, because previous MaxMind entry 70.159.21.51-70.232.244.255 is US, # because next MaxMind entry 70.232.245.58-70.232.245.59 is A2 ("Satellite # Provider") which is a country information about as useless as A1, and @@ -66,45 +38,37 @@ # MaxMind entry and say GB for 91.228.0.0-91.228.3.255. -KL 2012-11-27 "91.228.0.0","91.228.3.255","1541668864","1541669887","GB","United Kingdom" -# GB, because next MaxMind entry 91.232.125.0-91.232.125.255 is GB, and -# RIR delegation files say 91.232.124.0-91.232.125.255 is GB. -# -KL 2012-11-27 -"91.232.124.0","91.232.124.255","1541962752","1541963007","GB","United Kingdom" - -# GB, despite neither previous (RU) nor next (PL) MaxMind entry being GB, -# but because RIR delegation files agree with both previous and next -# MaxMind entry and say GB for 91.238.214.0-91.238.215.255. -# -KL 2012-11-27 -"91.238.214.0","91.238.215.255","1542379008","1542379519","GB","United Kingdom" - -# US, because next MaxMind entry 173.0.16.0-173.0.65.255 is US, and RIR -# delegation files say 173.0.0.0-173.0.15.255 is US. -KL 2012-11-27 -"173.0.0.0","173.0.15.255","2902458368","2902462463","US","United States" - -# US, because next MaxMind entry 176.67.84.0-176.67.84.79 is US, and RIR -# delegation files say 176.67.80.0-176.67.87.255 is US. -KL 2012-11-27 -"176.67.80.0","176.67.83.255","2957201408","2957202431","US","United States" - -# US, because previous MaxMind entry 176.67.84.192-176.67.85.255 is US, -# and RIR delegation files say 176.67.80.0-176.67.87.255 is US. -# -KL 2012-11-27 -"176.67.86.0","176.67.87.255","2957202944","2957203455","US","United States" - -# EU, despite neither previous (RU) nor next (UA) MaxMind entry being EU, -# but because RIR delegation files agree with both previous and next -# MaxMind entry and say EU for 193.200.150.0-193.200.150.255. -# -KL 2012-11-27 -"193.200.150.0","193.200.150.255","3251148288","3251148543","EU","Europe" - -# US, because previous MaxMind entry 199.96.68.0-199.96.87.127 is US, and -# RIR delegation files say 199.96.80.0-199.96.87.255 is US. -# -KL 2012-11-27 -"199.96.87.128","199.96.87.255","3344979840","3344979967","US","United States" - -# US, because previous MaxMind entry 209.58.176.144-209.59.31.255 is US, -# and RIR delegation files say 209.59.32.0-209.59.63.255 is US. -# -KL 2012-11-27 -"209.59.32.0","209.59.63.255","3510312960","3510321151","US","United States" +# NL, because next MaxMind entry 176.56.173.0-176.56.173.63 is NL, and RIR +# delegation files say 176.56.160.0-176.56.191.255 is NL. -KL 2013-05-13 +"176.56.172.0","176.56.172.255","2956504064","2956504319","NL","Netherlands" + +# NL, despite neither previous (RU) nor next (GB) MaxMind entry being NL, +# but because RIR delegation files say entire range +# 176.56.160.0-176.56.191.255 is NL. -KL 2013-05-13 +"176.56.174.0","176.56.174.255","2956504576","2956504831","NL","Netherlands" + +# GB, because RIR delegation files say exactly this range +# 185.25.84.0-185.25.87.255 is GB, even though neither previous nor next +# MaxMind range is GB. Both previous and next MaxMind ranges match RIR +# delegation files, too. -KL 2013-05-13 +"185.25.84.0","185.25.87.255","3105444864","3105445887","GB","United Kingdom" + +# US, because next MaxMind entry 199.101.193.0-199.101.195.255 is US, and, +# together with next entries, matches RIR delegation file entry +# 199.101.192.0-199.101.199.255 which is US. -KL 2013-05-13 +"199.101.192.0","199.101.192.255","3345334272","3345334527","US","United States" + +# US, because ARIN says 199.255.208.0-199.255.215.255 is US. +# Changed entry start from 199.255.213.0 to 199.255.208.0 on 2013-08-12. +# Split up into 199.255.208.0-199.255.209.127 and +# 199.255.210.0-199.255.215.255 on 2013-10-11. -KL 2013-10-11 +"199.255.208.0","199.255.209.127","3355430912","3355431295","US","United States" +"199.255.210.0","199.255.215.255","3355431424","3355432959","US","United States" + +# EU, despite neither previous (RU) nor next (SE) MaxMind entry being EU, +# but because RIR delegation files agree with previous MaxMind entry and +# say EU for 217.15.160.0-217.15.175.255. -KL 2013-05-13 +"217.15.160.0","217.15.164.255","3641679872","3641681151","EU","Europe" # FR, because previous MaxMind entry 217.15.166.0-217.15.166.255 is FR, # and RIR delegation files contain a block 217.15.160.0-217.15.175.255 diff --git a/src/config/torrc.sample.in b/src/config/torrc.sample.in index c667efc5c..d842fbcaf 100644 --- a/src/config/torrc.sample.in +++ b/src/config/torrc.sample.in @@ -1,5 +1,5 @@ ## Configuration file for a typical Tor user -## Last updated 12 September 2012 for Tor 0.2.4.3-alpha. +## Last updated 9 October 2013 for Tor 0.2.5.2-alpha. ## (may or may not work for much older or much newer versions of Tor.) ## ## Lines that begin with "## " try to explain what's going on. Lines @@ -120,9 +120,12 @@ ## is per month) #AccountingStart month 3 15:00 -## Contact info to be published in the directory, so we can contact you -## if your relay is misconfigured or something else goes wrong. Google -## indexes this, so spammers might also collect it. +## Administrative contact information for this relay or bridge. This line +## can be used to contact you if your relay or bridge is misconfigured or +## something else goes wrong. Note that we archive and publish all +## descriptors containing these lines and that Google indexes them, so +## spammers might also collect them. You may want to obscure the fact that +## it's an email address and/or generate a new address for this purpose. #ContactInfo Random Person <nobody AT example dot com> ## You might also include your PGP or GPG fingerprint if you have one: #ContactInfo 0xFFFFFFFF Random Person <nobody AT example dot com> diff --git a/src/ext/eventdns.c b/src/ext/eventdns.c index 66280cccd..8b934c443 100644 --- a/src/ext/eventdns.c +++ b/src/ext/eventdns.c @@ -2298,6 +2298,10 @@ _evdns_nameserver_add_impl(const struct sockaddr *address, evtimer_set(&ns->timeout_event, nameserver_prod_callback, ns); +#if 1 + ns->socket = tor_open_socket_nonblocking(address->sa_family, SOCK_DGRAM, 0); + if (!SOCKET_OK(ns->socket)) { err = 1; goto out1; } +#else ns->socket = tor_open_socket(address->sa_family, SOCK_DGRAM, 0); if (ns->socket < 0) { err = 1; goto out1; } #ifdef _WIN32 @@ -2314,6 +2318,7 @@ _evdns_nameserver_add_impl(const struct sockaddr *address, } #endif +#endif /* 1 */ if (global_bind_addr_is_set && !sockaddr_is_loopback((struct sockaddr*)&global_bind_address)) { if (bind(ns->socket, (struct sockaddr *)&global_bind_address, @@ -3473,8 +3478,12 @@ main(int c, char **v) { if (servertest) { int sock; struct sockaddr_in my_addr; +#if 1 + sock = tor_open_socket_nonblocking(PF_INET, SOCK_DGRAM, 0) +#else sock = tor_open_socket(PF_INET, SOCK_DGRAM, 0); fcntl(sock, F_SETFL, O_NONBLOCK); +#endif my_addr.sin_family = AF_INET; my_addr.sin_port = htons(10053); my_addr.sin_addr.s_addr = INADDR_ANY; diff --git a/src/or/addressmap.c b/src/or/addressmap.c index 79e4b7c5e..9bc79bd84 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -798,7 +798,7 @@ address_is_in_virtual_range(const char *address) /** Return a random address conforming to the virtual address configuration * in <b>conf</b>. */ -/* private */ void +STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, tor_addr_t *addr_out) { uint8_t tmp[4]; diff --git a/src/or/addressmap.h b/src/or/addressmap.h index 40210ee99..417832b31 100644 --- a/src/or/addressmap.h +++ b/src/or/addressmap.h @@ -7,6 +7,8 @@ #ifndef TOR_ADDRESSMAP_H #define TOR_ADDRESSMAP_H +#include "testsupport.h" + void addressmap_init(void); void addressmap_clear_excluded_trackexithosts(const or_options_t *options); void addressmap_clear_invalid_automaps(const or_options_t *options); @@ -52,8 +54,8 @@ typedef struct virtual_addr_conf_t { maskbits_t bits; } virtual_addr_conf_t; -void get_random_virtual_addr(const virtual_addr_conf_t *conf, - tor_addr_t *addr_out); +STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, + tor_addr_t *addr_out); #endif #endif diff --git a/src/or/buffers.c b/src/or/buffers.c index c4c847ec8..50016d3a8 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -19,6 +19,7 @@ #include "connection_or.h" #include "control.h" #include "reasons.h" +#include "ext_orport.h" #include "../common/util.h" #include "../common/torlog.h" #ifdef HAVE_UNISTD_H @@ -1294,7 +1295,7 @@ buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) /** Return the first position in <b>buf</b> at which the <b>n</b>-character * string <b>s</b> occurs, or -1 if it does not occur. */ -/*private*/ int +STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n) { buf_pos_t pos; @@ -1702,6 +1703,64 @@ fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req, } #endif +/** The size of the header of an Extended ORPort message: 2 bytes for + * COMMAND, 2 bytes for BODYLEN */ +#define EXT_OR_CMD_HEADER_SIZE 4 + +/** Read <b>buf</b>, which should contain an Extended ORPort message + * from a transport proxy. If well-formed, create and populate + * <b>out</b> with the Extended ORport message. Return 0 if the + * buffer was incomplete, 1 if it was well-formed and -1 if we + * encountered an error while parsing it. */ +int +fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) +{ + char hdr[EXT_OR_CMD_HEADER_SIZE]; + uint16_t len; + + check(); + if (buf->datalen < EXT_OR_CMD_HEADER_SIZE) + return 0; + peek_from_buf(hdr, sizeof(hdr), buf); + len = ntohs(get_uint16(hdr+2)); + if (buf->datalen < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) + return 0; + *out = ext_or_cmd_new(len); + (*out)->cmd = ntohs(get_uint16(hdr)); + (*out)->len = len; + buf_remove_from_front(buf, EXT_OR_CMD_HEADER_SIZE); + fetch_from_buf((*out)->body, len, buf); + return 1; +} + +#ifdef USE_BUFFEREVENTS +/** Read <b>buf</b>, which should contain an Extended ORPort message + * from a transport proxy. If well-formed, create and populate + * <b>out</b> with the Extended ORport message. Return 0 if the + * buffer was incomplete, 1 if it was well-formed and -1 if we + * encountered an error while parsing it. */ +int +fetch_ext_or_command_from_evbuffer(struct evbuffer *buf, ext_or_cmd_t **out) +{ + char hdr[EXT_OR_CMD_HEADER_SIZE]; + uint16_t len; + size_t buf_len = evbuffer_get_length(buf); + + if (buf_len < EXT_OR_CMD_HEADER_SIZE) + return 0; + evbuffer_copyout(buf, hdr, EXT_OR_CMD_HEADER_SIZE); + len = ntohs(get_uint16(hdr+2)); + if (buf_len < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) + return 0; + *out = ext_or_cmd_new(len); + (*out)->cmd = ntohs(get_uint16(hdr)); + (*out)->len = len; + evbuffer_drain(buf, EXT_OR_CMD_HEADER_SIZE); + evbuffer_remove(buf, (*out)->body, len); + return 1; +} +#endif + /** Implementation helper to implement fetch_from_*_socks. Instead of looking * at a buffer's contents, we look at the <b>datalen</b> bytes of data in * <b>data</b>. Instead of removing data from the buffer, we set diff --git a/src/or/buffers.h b/src/or/buffers.h index c947f0ba9..48b118520 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -12,6 +12,8 @@ #ifndef TOR_BUFFERS_H #define TOR_BUFFERS_H +#include "testsupport.h" + buf_t *buf_new(void); buf_t *buf_new_with_capacity(size_t size); void buf_free(buf_t *buf); @@ -51,6 +53,8 @@ int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); int peek_buf_has_control0_command(buf_t *buf); +int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out); + #ifdef USE_BUFFEREVENTS int fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out, int linkproto); @@ -66,6 +70,8 @@ int peek_evbuffer_has_control0_command(struct evbuffer *buf); int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, const char *data, size_t data_len, int done); +int fetch_ext_or_command_from_evbuffer(struct evbuffer *buf, + ext_or_cmd_t **out); #endif #ifdef USE_BUFFEREVENTS @@ -75,6 +81,8 @@ int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, #define generic_buffer_get(b,buf,buflen) evbuffer_remove((b),(buf),(buflen)) #define generic_buffer_clear(b) evbuffer_drain((b), evbuffer_get_length((b))) #define generic_buffer_free(b) evbuffer_free((b)) +#define generic_buffer_fetch_ext_or_cmd(b, out) \ + fetch_ext_or_command_from_evbuffer((b), (out)) #else #define generic_buffer_new() buf_new() #define generic_buffer_len(b) buf_datalen((b)) @@ -82,6 +90,8 @@ int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, #define generic_buffer_get(b,buf,buflen) fetch_from_buf((buf),(buflen),(b)) #define generic_buffer_clear(b) buf_clear((b)) #define generic_buffer_free(b) buf_free((b)) +#define generic_buffer_fetch_ext_or_cmd(b, out) \ + fetch_ext_or_command_from_buf((b), (out)) #endif int generic_buffer_set_to_copy(generic_buffer_t **output, const generic_buffer_t *input); @@ -89,7 +99,7 @@ int generic_buffer_set_to_copy(generic_buffer_t **output, void assert_buf_ok(buf_t *buf); #ifdef BUFFERS_PRIVATE -int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); +STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); #endif #endif diff --git a/src/or/channel.c b/src/or/channel.c index 1270eace7..a345bab20 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -122,6 +122,8 @@ static cell_queue_entry_t * cell_queue_entry_new_fixed(cell_t *cell); static cell_queue_entry_t * cell_queue_entry_new_var(var_cell_t *var_cell); +static int is_destroy_cell(channel_t *chan, + const cell_queue_entry_t *q, circid_t *circid_out); /* Functions to maintain the digest map */ static void channel_add_to_digest_map(channel_t *chan); @@ -804,6 +806,7 @@ channel_free(channel_t *chan) /* Get rid of cmux */ if (chan->cmux) { circuitmux_detach_all_circuits(chan->cmux); + circuitmux_mark_destroyed_circids_usable(chan->cmux, chan); circuitmux_free(chan->cmux); chan->cmux = NULL; } @@ -1688,6 +1691,13 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) chan->timestamp_last_added_nonpadding = approx_time(); } + { + circid_t circ_id; + if (is_destroy_cell(chan, q, &circ_id)) { + channel_note_destroy_not_pending(chan, circ_id); + } + } + /* Can we send it right out? If so, try */ if (TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue) && chan->state == CHANNEL_STATE_OPEN) { @@ -2355,7 +2365,7 @@ channel_do_open_actions(channel_t *chan) started_here = channel_is_outgoing(chan); if (started_here) { - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); rep_hist_note_connect_succeeded(chan->identity_digest, now); if (entry_guard_register_connect_status( chan->identity_digest, 1, 0, now) < 0) { @@ -2373,8 +2383,14 @@ channel_do_open_actions(channel_t *chan) /* only report it to the geoip module if it's not a known router */ if (!router_get_by_id_digest(chan->identity_digest)) { if (channel_get_addr_if_possible(chan, &remote_addr)) { - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &remote_addr, + char *transport_name = NULL; + if (chan->get_transport_name(chan, &transport_name) < 0) + transport_name = NULL; + + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, + &remote_addr, transport_name, now); + tor_free(transport_name); } /* Otherwise the underlying transport can't tell us this, so skip it */ } @@ -2611,6 +2627,54 @@ channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) } } +/** If <b>packed_cell</b> on <b>chan</b> is a destroy cell, then set + * *<b>circid_out</b> to its circuit ID, and return true. Otherwise, return + * false. */ +/* XXXX Move this function. */ +int +packed_cell_is_destroy(channel_t *chan, + const packed_cell_t *packed_cell, + circid_t *circid_out) +{ + if (chan->wide_circ_ids) { + if (packed_cell->body[4] == CELL_DESTROY) { + *circid_out = ntohl(get_uint32(packed_cell->body)); + return 1; + } + } else { + if (packed_cell->body[2] == CELL_DESTROY) { + *circid_out = ntohs(get_uint16(packed_cell->body)); + return 1; + } + } + return 0; +} + +/** DOCDOC */ +static int +is_destroy_cell(channel_t *chan, + const cell_queue_entry_t *q, circid_t *circid_out) +{ + *circid_out = 0; + switch (q->type) { + case CELL_QUEUE_FIXED: + if (q->u.fixed.cell->command == CELL_DESTROY) { + *circid_out = q->u.fixed.cell->circ_id; + return 1; + } + break; + case CELL_QUEUE_VAR: + if (q->u.var.var_cell->command == CELL_DESTROY) { + *circid_out = q->u.var.var_cell->circ_id; + return 1; + } + break; + case CELL_QUEUE_PACKED: + return packed_cell_is_destroy(chan, q->u.packed.packed_cell, circid_out); + } + return 0; +} + /** * Send destroy cell on a channel * @@ -2622,25 +2686,20 @@ channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) int channel_send_destroy(circid_t circ_id, channel_t *chan, int reason) { - cell_t cell; - tor_assert(chan); /* Check to make sure we can send on this channel first */ if (!(chan->state == CHANNEL_STATE_CLOSING || chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR)) { - memset(&cell, 0, sizeof(cell_t)); - cell.circ_id = circ_id; - cell.command = CELL_DESTROY; - cell.payload[0] = (uint8_t) reason; + chan->state == CHANNEL_STATE_ERROR) && + chan->cmux) { + channel_note_destroy_pending(chan, circ_id); + circuitmux_append_destroy_cell(chan, chan->cmux, circ_id, reason); log_debug(LD_OR, "Sending destroy (circID %u) on channel %p " "(global ID " U64_FORMAT ")", (unsigned)circ_id, chan, U64_PRINTF_ARG(chan->global_identifier)); - - channel_write_cell(chan, &cell); } else { log_warn(LD_BUG, "Someone called channel_send_destroy() for circID %u " diff --git a/src/or/channel.h b/src/or/channel.h index 2dca81705..7e3f5ad07 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -10,7 +10,6 @@ #define TOR_CHANNEL_H #include "or.h" -#include "tor_queue.h" #include "circuitmux.h" /* Channel handler function pointer typedefs */ @@ -87,6 +86,8 @@ struct channel_s { * available. */ int (*get_remote_addr)(channel_t *, tor_addr_t *); + int (*get_transport_name)(channel_t *chan, char **transport_out); + #define GRD_FLAG_ORIGINAL 1 #define GRD_FLAG_ADDR_ONLY 2 /* @@ -480,5 +481,9 @@ uint64_t channel_count_xmitted(channel_t *chan); uint64_t channel_listener_count_accepted(channel_listener_t *chan_l); +int packed_cell_is_destroy(channel_t *chan, + const packed_cell_t *packed_cell, + circid_t *circid_out); + #endif diff --git a/src/or/channeltls.c b/src/or/channeltls.c index f751c0da9..4943054f5 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -55,6 +55,8 @@ static void channel_tls_close_method(channel_t *chan); static const char * channel_tls_describe_transport_method(channel_t *chan); static int channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out); +static int +channel_tls_get_transport_name_method(channel_t *chan, char **transport_out); static const char * channel_tls_get_remote_descr_method(channel_t *chan, int flags); static int channel_tls_has_queued_writes_method(channel_t *chan); @@ -114,6 +116,7 @@ channel_tls_common_init(channel_tls_t *tlschan) chan->describe_transport = channel_tls_describe_transport_method; chan->get_remote_addr = channel_tls_get_remote_addr_method; chan->get_remote_descr = channel_tls_get_remote_descr_method; + chan->get_transport_name = channel_tls_get_transport_name_method; chan->has_queued_writes = channel_tls_has_queued_writes_method; chan->is_canonical = channel_tls_is_canonical_method; chan->matches_extend_info = channel_tls_matches_extend_info_method; @@ -406,6 +409,30 @@ channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out) } /** + * Get the name of the pluggable transport used by a channel_tls_t. + * + * This implements the get_transport_name for channel_tls_t. If the + * channel uses a pluggable transport, copy its name to + * <b>transport_out</b> and return 0. If the channel did not use a + * pluggable transport, return -1. */ + +static int +channel_tls_get_transport_name_method(channel_t *chan, char **transport_out) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(transport_out); + tor_assert(tlschan->conn); + + if (!tlschan->conn->ext_or_transport) + return -1; + + *transport_out = tor_strdup(tlschan->conn->ext_or_transport); + return 0; +} + +/** * Get endpoint description of a channel_tls_t * * This implements the get_remote_descr method for channel_tls_t; it returns @@ -1408,12 +1435,14 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) my_addr_ptr = (uint8_t*) cell->payload + 6; end = cell->payload + CELL_PAYLOAD_SIZE; cp = cell->payload + 6 + my_addr_len; - if (cp >= end) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Addresses too long in netinfo cell; closing connection."); - connection_or_close_for_error(chan->conn, 0); - return; - } else if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { + + /* We used to check: + * if (my_addr_len >= CELL_PAYLOAD_SIZE - 6) { + * + * This is actually never going to happen, since my_addr_len is at most 255, + * and CELL_PAYLOAD_LEN - 6 is 503. So we know that cp is < end. */ + + if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { tor_addr_from_ipv4n(&my_apparent_addr, get_uint32(my_addr_ptr)); } 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); diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c new file mode 100644 index 000000000..51a75cf50 --- /dev/null +++ b/src/or/circpathbias.c @@ -0,0 +1,1538 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "channel.h" +#include "circpathbias.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "circuitstats.h" +#include "connection_edge.h" +#include "config.h" +#include "entrynodes.h" +#include "networkstatus.h" +#include "relay.h" + +static void pathbias_count_successful_close(origin_circuit_t *circ); +static void pathbias_count_collapse(origin_circuit_t *circ); +static void pathbias_count_use_failed(origin_circuit_t *circ); +static void pathbias_measure_use_rate(entry_guard_t *guard); +static void pathbias_measure_close_rate(entry_guard_t *guard); +static void pathbias_scale_use_rates(entry_guard_t *guard); +static void pathbias_scale_close_rates(entry_guard_t *guard); +static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard); + +/** Increment the number of times we successfully extended a circuit to + * <b>guard</b>, first checking if the failure rate is high enough that + * we should eliminate the guard. Return -1 if the guard looks no good; + * return 0 if the guard looks fine. + */ +static int +entry_guard_inc_circ_attempt_count(entry_guard_t *guard) +{ + entry_guards_changed(); + + pathbias_measure_close_rate(guard); + + if (guard->path_bias_disabled) + return -1; + + pathbias_scale_close_rates(guard); + guard->circ_attempts++; + + log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + return 0; +} + +/** The minimum number of circuit attempts before we start + * thinking about warning about path bias and dropping guards */ +static int +pathbias_get_min_circs(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_MIN_CIRC 150 + if (options->PathBiasCircThreshold >= 5) + return options->PathBiasCircThreshold; + else + return networkstatus_get_param(NULL, "pb_mincircs", + DFLT_PATH_BIAS_MIN_CIRC, + 5, INT32_MAX); +} + +/** The circuit success rate below which we issue a notice */ +static double +pathbias_get_notice_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_NOTICE_PCT 70 + if (options->PathBiasNoticeRate >= 0.0) + return options->PathBiasNoticeRate; + else + return networkstatus_get_param(NULL, "pb_noticepct", + DFLT_PATH_BIAS_NOTICE_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** The circuit success rate below which we issue a warn */ +static double +pathbias_get_warn_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_WARN_PCT 50 + if (options->PathBiasWarnRate >= 0.0) + return options->PathBiasWarnRate; + else + return networkstatus_get_param(NULL, "pb_warnpct", + DFLT_PATH_BIAS_WARN_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** + * The extreme rate is the rate at which we would drop the guard, + * if pb_dropguard is also set. Otherwise we just warn. + */ +double +pathbias_get_extreme_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_EXTREME_PCT 30 + if (options->PathBiasExtremeRate >= 0.0) + return options->PathBiasExtremeRate; + else + return networkstatus_get_param(NULL, "pb_extremepct", + DFLT_PATH_BIAS_EXTREME_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** + * If 1, we actually disable use of guards that fall below + * the extreme_pct. + */ +int +pathbias_get_dropguards(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_DROP_GUARDS 0 + if (options->PathBiasDropGuards >= 0) + return options->PathBiasDropGuards; + else + return networkstatus_get_param(NULL, "pb_dropguards", + DFLT_PATH_BIAS_DROP_GUARDS, 0, 1); +} + +/** + * This is the number of circuits at which we scale our + * counts by mult_factor/scale_factor. Note, this count is + * not exact, as we only perform the scaling in the event + * of no integer truncation. + */ +static int +pathbias_get_scale_threshold(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_SCALE_THRESHOLD 300 + if (options->PathBiasScaleThreshold >= 10) + return options->PathBiasScaleThreshold; + else + return networkstatus_get_param(NULL, "pb_scalecircs", + DFLT_PATH_BIAS_SCALE_THRESHOLD, 10, + INT32_MAX); +} + +/** + * Compute the path bias scaling ratio from the consensus + * parameters pb_multfactor/pb_scalefactor. + * + * Returns a value in (0, 1.0] which we multiply our pathbias + * counts with to scale them down. + */ +static double +pathbias_get_scale_ratio(const or_options_t *options) +{ + /* + * The scale factor is the denominator for our scaling + * of circuit counts for our path bias window. + * + * Note that our use of doubles for the path bias state + * file means that powers of 2 work best here. + */ + int denominator = networkstatus_get_param(NULL, "pb_scalefactor", + 2, 2, INT32_MAX); + (void) options; + /** + * The mult factor is the numerator for our scaling + * of circuit counts for our path bias window. It + * allows us to scale by fractions. + */ + return networkstatus_get_param(NULL, "pb_multfactor", + 1, 1, denominator)/((double)denominator); +} + +/** The minimum number of circuit usage attempts before we start + * thinking about warning about path use bias and dropping guards */ +static int +pathbias_get_min_use(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_MIN_USE 20 + if (options->PathBiasUseThreshold >= 3) + return options->PathBiasUseThreshold; + else + return networkstatus_get_param(NULL, "pb_minuse", + DFLT_PATH_BIAS_MIN_USE, + 3, INT32_MAX); +} + +/** The circuit use success rate below which we issue a notice */ +static double +pathbias_get_notice_use_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_NOTICE_USE_PCT 80 + if (options->PathBiasNoticeUseRate >= 0.0) + return options->PathBiasNoticeUseRate; + else + return networkstatus_get_param(NULL, "pb_noticeusepct", + DFLT_PATH_BIAS_NOTICE_USE_PCT, + 0, 100)/100.0; +} + +/** + * The extreme use rate is the rate at which we would drop the guard, + * if pb_dropguard is also set. Otherwise we just warn. + */ +double +pathbias_get_extreme_use_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_EXTREME_USE_PCT 60 + if (options->PathBiasExtremeUseRate >= 0.0) + return options->PathBiasExtremeUseRate; + else + return networkstatus_get_param(NULL, "pb_extremeusepct", + DFLT_PATH_BIAS_EXTREME_USE_PCT, + 0, 100)/100.0; +} + +/** + * This is the number of circuits at which we scale our + * use counts by mult_factor/scale_factor. Note, this count is + * not exact, as we only perform the scaling in the event + * of no integer truncation. + */ +static int +pathbias_get_scale_use_threshold(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_SCALE_USE_THRESHOLD 100 + if (options->PathBiasScaleUseThreshold >= 10) + return options->PathBiasScaleUseThreshold; + else + return networkstatus_get_param(NULL, "pb_scaleuse", + DFLT_PATH_BIAS_SCALE_USE_THRESHOLD, + 10, INT32_MAX); +} + +/** + * Convert a Guard's path state to string. + */ +const char * +pathbias_state_to_string(path_state_t state) +{ + switch (state) { + case PATH_STATE_NEW_CIRC: + return "new"; + case PATH_STATE_BUILD_ATTEMPTED: + return "build attempted"; + case PATH_STATE_BUILD_SUCCEEDED: + return "build succeeded"; + case PATH_STATE_USE_ATTEMPTED: + return "use attempted"; + case PATH_STATE_USE_SUCCEEDED: + return "use succeeded"; + case PATH_STATE_USE_FAILED: + return "use failed"; + case PATH_STATE_ALREADY_COUNTED: + return "already counted"; + } + + return "unknown"; +} + +/** + * This function decides if a circuit has progressed far enough to count + * as a circuit "attempt". As long as end-to-end tagging is possible, + * we assume the adversary will use it over hop-to-hop failure. Therefore, + * we only need to account bias for the last hop. This should make us + * much more resilient to ambient circuit failure, and also make that + * failure easier to measure (we only need to measure Exit failure rates). + */ +static int +pathbias_is_new_circ_attempt(origin_circuit_t *circ) +{ +#define N2N_TAGGING_IS_POSSIBLE +#ifdef N2N_TAGGING_IS_POSSIBLE + /* cpath is a circular list. We want circs with more than one hop, + * and the second hop must be waiting for keys still (it's just + * about to get them). */ + return circ->cpath && + circ->cpath->next != circ->cpath && + circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; +#else + /* If tagging attacks are no longer possible, we probably want to + * count bias from the first hop. However, one could argue that + * timing-based tagging is still more useful than per-hop failure. + * In which case, we'd never want to use this. + */ + return circ->cpath && + circ->cpath->state == CPATH_STATE_AWAITING_KEYS; +#endif +} + +/** + * Decide if the path bias code should count a circuit. + * + * @returns 1 if we should count it, 0 otherwise. + */ +static int +pathbias_should_count(origin_circuit_t *circ) +{ +#define PATHBIAS_COUNT_INTERVAL (600) + static ratelim_t count_limit = + RATELIM_INIT(PATHBIAS_COUNT_INTERVAL); + char *rate_msg = NULL; + + /* We can't do path bias accounting without entry guards. + * Testing and controller circuits also have no guards. + * + * We also don't count server-side rends, because their + * endpoint could be chosen maliciously. + * Similarly, we can't count client-side intro attempts, + * because clients can be manipulated into connecting to + * malicious intro points. */ + if (get_options()->UseEntryGuards == 0 || + circ->base_.purpose == CIRCUIT_PURPOSE_TESTING || + circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER || + circ->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || + circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED || + (circ->base_.purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && + circ->base_.purpose <= CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { + + /* Check to see if the shouldcount result has changed due to a + * unexpected purpose change that would affect our results. + * + * The reason we check the path state too here is because for the + * cannibalized versions of these purposes, we count them as successful + * before their purpose change. + */ + if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED + && circ->path_state != PATH_STATE_ALREADY_COUNTED) { + log_info(LD_BUG, + "Circuit %d is now being ignored despite being counted " + "in the past. Purpose is %s, path state is %s", + circ->global_identifier, + circuit_purpose_to_string(circ->base_.purpose), + pathbias_state_to_string(circ->path_state)); + } + circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; + return 0; + } + + /* Completely ignore one hop circuits */ + if (circ->build_state->onehop_tunnel || + circ->build_state->desired_path_len == 1) { + /* Check for inconsistency */ + if (circ->build_state->desired_path_len != 1 || + !circ->build_state->onehop_tunnel) { + if ((rate_msg = rate_limit_log(&count_limit, approx_time()))) { + log_info(LD_BUG, + "One-hop circuit has length %d. Path state is %s. " + "Circuit is a %s currently %s.%s", + circ->build_state->desired_path_len, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + tor_fragile_assert(); + } + + /* Check to see if the shouldcount result has changed due to a + * unexpected change that would affect our results */ + if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED) { + log_info(LD_BUG, + "One-hop circuit %d is now being ignored despite being counted " + "in the past. Purpose is %s, path state is %s", + circ->global_identifier, + circuit_purpose_to_string(circ->base_.purpose), + pathbias_state_to_string(circ->path_state)); + } + circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; + return 0; + } + + /* Check to see if the shouldcount result has changed due to a + * unexpected purpose change that would affect our results */ + if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_IGNORED) { + log_info(LD_BUG, + "Circuit %d is now being counted despite being ignored " + "in the past. Purpose is %s, path state is %s", + circ->global_identifier, + circuit_purpose_to_string(circ->base_.purpose), + pathbias_state_to_string(circ->path_state)); + } + circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_COUNTED; + + return 1; +} + +/** + * Check our circuit state to see if this is a successful circuit attempt. + * If so, record it in the current guard's path bias circ_attempt count. + * + * Also check for several potential error cases for bug #6475. + */ +int +pathbias_count_build_attempt(origin_circuit_t *circ) +{ +#define CIRC_ATTEMPT_NOTICE_INTERVAL (600) + static ratelim_t circ_attempt_notice_limit = + RATELIM_INIT(CIRC_ATTEMPT_NOTICE_INTERVAL); + char *rate_msg = NULL; + + if (!pathbias_should_count(circ)) { + return 0; + } + + if (pathbias_is_new_circ_attempt(circ)) { + /* Help track down the real cause of bug #6475: */ + if (circ->has_opened && circ->path_state != PATH_STATE_BUILD_ATTEMPTED) { + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Opened circuit is in strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + + /* Don't re-count cannibalized circs.. */ + if (!circ->has_opened) { + entry_guard_t *guard = NULL; + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } else if (circ->base_.n_chan) { + guard = + entry_guard_get_by_id_digest(circ->base_.n_chan->identity_digest); + } + + if (guard) { + if (circ->path_state == PATH_STATE_NEW_CIRC) { + circ->path_state = PATH_STATE_BUILD_ATTEMPTED; + + if (entry_guard_inc_circ_attempt_count(guard) < 0) { + /* Bogus guard; we already warned. */ + return -END_CIRC_REASON_TORPROTOCOL; + } + } else { + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Unopened circuit has strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } else { + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, + approx_time()))) { + log_info(LD_CIRC, + "Unopened circuit has no known guard. " + "Circuit is a %s currently %s.%s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } + } + + return 0; +} + +/** + * Check our circuit state to see if this is a successful circuit + * completion. If so, record it in the current guard's path bias + * success count. + * + * Also check for several potential error cases for bug #6475. + */ +void +pathbias_count_build_success(origin_circuit_t *circ) +{ +#define SUCCESS_NOTICE_INTERVAL (600) + static ratelim_t success_notice_limit = + RATELIM_INIT(SUCCESS_NOTICE_INTERVAL); + char *rate_msg = NULL; + entry_guard_t *guard = NULL; + + if (!pathbias_should_count(circ)) { + return; + } + + /* Don't count cannibalized/reused circs for path bias + * "build" success, since they get counted under "use" success. */ + if (!circ->has_opened) { + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) { + circ->path_state = PATH_STATE_BUILD_SUCCEEDED; + guard->circ_successes++; + entry_guards_changed(); + + log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } else { + if ((rate_msg = rate_limit_log(&success_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Succeeded circuit is in strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + + if (guard->circ_attempts < guard->circ_successes) { + log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " + "for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + if ((rate_msg = rate_limit_log(&success_notice_limit, + approx_time()))) { + log_info(LD_CIRC, + "Completed circuit has no known guard. " + "Circuit is a %s currently %s.%s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } else { + if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { + if ((rate_msg = rate_limit_log(&success_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Opened circuit is in strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } +} + +/** + * Record an attempt to use a circuit. Changes the circuit's + * path state and update its guard's usage counter. + * + * Used for path bias usage accounting. + */ +void +pathbias_count_use_attempt(origin_circuit_t *circ) +{ + entry_guard_t *guard; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { + log_notice(LD_BUG, + "Used circuit is in strange path state %s. " + "Circuit is a %s currently %s.", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + if (guard) { + pathbias_measure_use_rate(guard); + pathbias_scale_use_rates(guard); + guard->use_attempts++; + entry_guards_changed(); + + log_debug(LD_CIRC, + "Marked circuit %d (%f/%f) as used for guard %s ($%s).", + circ->global_identifier, + guard->use_successes, guard->use_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } + + circ->path_state = PATH_STATE_USE_ATTEMPTED; + } else { + /* Harmless but educational log message */ + log_info(LD_CIRC, + "Used circuit %d is already in path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } + + return; +} + +/** + * Check the circuit's path state is appropriate and mark it as + * successfully used. Used for path bias usage accounting. + * + * We don't actually increment the guard's counters until + * pathbias_check_close(), because the circuit can still transition + * back to PATH_STATE_USE_ATTEMPTED if a stream fails later (this + * is done so we can probe the circuit for liveness at close). + */ +void +pathbias_mark_use_success(origin_circuit_t *circ) +{ + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { + log_notice(LD_BUG, + "Used circuit %d is in strange path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + + pathbias_count_use_attempt(circ); + } + + /* We don't do any accounting at the guard until actual circuit close */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; + + return; +} + +/** + * If a stream ever detatches from a circuit in a retriable way, + * we need to mark this circuit as still needing either another + * successful stream, or in need of a probe. + * + * An adversary could let the first stream request succeed (ie the + * resolve), but then tag and timeout the remainder (via cell + * dropping), forcing them on new circuits. + * + * Rolling back the state will cause us to probe such circuits, which + * should lead to probe failures in the event of such tagging due to + * either unrecognized cells coming in while we wait for the probe, + * or the cipher state getting out of sync in the case of dropped cells. + */ +void +pathbias_mark_use_rollback(origin_circuit_t *circ) +{ + if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { + log_info(LD_CIRC, + "Rolling back pathbias use state to 'attempted' for detached " + "circuit %d", circ->global_identifier); + circ->path_state = PATH_STATE_USE_ATTEMPTED; + } +} + +/** + * Actually count a circuit success towards a guard's usage counters + * if the path state is appropriate. + */ +static void +pathbias_count_use_success(origin_circuit_t *circ) +{ + entry_guard_t *guard; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state != PATH_STATE_USE_SUCCEEDED) { + log_notice(LD_BUG, + "Successfully used circuit %d is in strange path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } else { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + if (guard) { + guard->use_successes++; + entry_guards_changed(); + + if (guard->use_attempts < guard->use_successes) { + log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) " + "for guard %s=%s", + guard->use_successes, guard->use_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } + + log_debug(LD_CIRC, + "Marked circuit %d (%f/%f) as used successfully for guard " + "%s ($%s).", + circ->global_identifier, guard->use_successes, + guard->use_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + } + + return; +} + +/** + * Send a probe down a circuit that the client attempted to use, + * but for which the stream timed out/failed. The probe is a + * RELAY_BEGIN cell with a 0.a.b.c destination address, which + * the exit will reject and reply back, echoing that address. + * + * The reason for such probes is because it is possible to bias + * a user's paths simply by causing timeouts, and these timeouts + * are not possible to differentiate from unresponsive servers. + * + * The probe is sent at the end of the circuit lifetime for two + * reasons: to prevent cryptographic taggers from being able to + * drop cells to cause timeouts, and to prevent easy recognition + * of probes before any real client traffic happens. + * + * Returns -1 if we couldn't probe, 0 otherwise. + */ +static int +pathbias_send_usable_probe(circuit_t *circ) +{ + /* Based on connection_ap_handshake_send_begin() */ + char payload[CELL_PAYLOAD_SIZE]; + int payload_len; + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + crypt_path_t *cpath_layer = NULL; + char *probe_nonce = NULL; + + tor_assert(ocirc); + + cpath_layer = ocirc->cpath->prev; + + if (cpath_layer->state != CPATH_STATE_OPEN) { + /* This can happen for cannibalized circuits. Their + * last hop isn't yet open */ + log_info(LD_CIRC, + "Got pathbias probe request for unopened circuit %d. " + "Opened %d, len %d", ocirc->global_identifier, + ocirc->has_opened, ocirc->build_state->desired_path_len); + return -1; + } + + /* We already went down this road. */ + if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING && + ocirc->pathbias_probe_id) { + log_info(LD_CIRC, + "Got pathbias probe request for circuit %d with " + "outstanding probe", ocirc->global_identifier); + return -1; + } + + /* Can't probe if the channel isn't open */ + if (circ->n_chan == NULL || + (circ->n_chan->state != CHANNEL_STATE_OPEN + && circ->n_chan->state != CHANNEL_STATE_MAINT)) { + log_info(LD_CIRC, + "Skipping pathbias probe for circuit %d: Channel is not open.", + ocirc->global_identifier); + return -1; + } + + circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); + + /* Update timestamp for when circuit_expire_building() should kill us */ + tor_gettimeofday(&circ->timestamp_began); + + /* Generate a random address for the nonce */ + crypto_rand((char*)ô->pathbias_probe_nonce, + sizeof(ocirc->pathbias_probe_nonce)); + ocirc->pathbias_probe_nonce &= 0x00ffffff; + probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce); + + tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce); + payload_len = (int)strlen(payload)+1; + + // XXX: need this? Can we assume ipv4 will always be supported? + // If not, how do we tell? + //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { + // set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); + // payload_len += 4; + //} + + /* Generate+Store stream id, make sure it's non-zero */ + ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc); + + if (ocirc->pathbias_probe_id==0) { + log_warn(LD_CIRC, + "Ran out of stream IDs on circuit %u during " + "pathbias probe attempt.", ocirc->global_identifier); + tor_free(probe_nonce); + return -1; + } + + log_info(LD_CIRC, + "Sending pathbias testing cell to %s:25 on stream %d for circ %d.", + probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier); + tor_free(probe_nonce); + + /* Send a test relay cell */ + if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ, + RELAY_COMMAND_BEGIN, payload, + payload_len, cpath_layer) < 0) { + log_notice(LD_CIRC, + "Failed to send pathbias probe cell on circuit %d.", + ocirc->global_identifier); + return -1; + } + + /* Mark it freshly dirty so it doesn't get expired in the meantime */ + circ->timestamp_dirty = time(NULL); + + return 0; +} + +/** + * Check the response to a pathbias probe, to ensure the + * cell is recognized and the nonce and other probe + * characteristics are as expected. + * + * If the response is valid, return 0. Otherwise return < 0. + */ +int +pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) +{ + /* Based on connection_edge_process_relay_cell() */ + relay_header_t rh; + int reason; + uint32_t ipv4_host; + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + + tor_assert(cell); + tor_assert(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING); + + relay_header_unpack(&rh, cell->payload); + + reason = rh.length > 0 ? + get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC; + + if (rh.command == RELAY_COMMAND_END && + reason == END_STREAM_REASON_EXITPOLICY && + ocirc->pathbias_probe_id == rh.stream_id) { + + /* Check length+extract host: It is in network order after the reason code. + * See connection_edge_end(). */ + if (rh.length < 9) { /* reason+ipv4+dns_ttl */ + log_notice(LD_PROTOCOL, + "Short path bias probe response length field (%d).", rh.length); + return - END_CIRC_REASON_TORPROTOCOL; + } + + ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); + + /* Check nonce */ + if (ipv4_host == ocirc->pathbias_probe_nonce) { + pathbias_mark_use_success(ocirc); + circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); + log_info(LD_CIRC, + "Got valid path bias probe back for circ %d, stream %d.", + ocirc->global_identifier, ocirc->pathbias_probe_id); + return 0; + } else { + log_notice(LD_CIRC, + "Got strange probe value 0x%x vs 0x%x back for circ %d, " + "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce, + ocirc->global_identifier, ocirc->pathbias_probe_id); + return -1; + } + } + log_info(LD_CIRC, + "Got another cell back back on pathbias probe circuit %d: " + "Command: %d, Reason: %d, Stream-id: %d", + ocirc->global_identifier, rh.command, reason, rh.stream_id); + return -1; +} + +/** + * Check if a circuit was used and/or closed successfully. + * + * If we attempted to use the circuit to carry a stream but failed + * for whatever reason, or if the circuit mysteriously died before + * we could attach any streams, record these two cases. + * + * If we *have* successfully used the circuit, or it appears to + * have been closed by us locally, count it as a success. + * + * Returns 0 if we're done making decisions with the circ, + * or -1 if we want to probe it first. + */ +int +pathbias_check_close(origin_circuit_t *ocirc, int reason) +{ + circuit_t *circ = ô->base_; + + if (!pathbias_should_count(ocirc)) { + return 0; + } + + switch (ocirc->path_state) { + /* If the circuit was closed after building, but before use, we need + * to ensure we were the ones who tried to close it (and not a remote + * actor). */ + case PATH_STATE_BUILD_SUCCEEDED: + if (reason & END_CIRC_REASON_FLAG_REMOTE) { + /* Remote circ close reasons on an unused circuit all could be bias */ + log_info(LD_CIRC, + "Circuit %d remote-closed without successful use for reason %d. " + "Circuit purpose %d currently %d,%s. Len %d.", + ocirc->global_identifier, + reason, circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + pathbias_count_collapse(ocirc); + } else if ((reason & ~END_CIRC_REASON_FLAG_REMOTE) + == END_CIRC_REASON_CHANNEL_CLOSED && + circ->n_chan && + circ->n_chan->reason_for_closing + != CHANNEL_CLOSE_REQUESTED) { + /* If we didn't close the channel ourselves, it could be bias */ + /* XXX: Only count bias if the network is live? + * What about clock jumps/suspends? */ + log_info(LD_CIRC, + "Circuit %d's channel closed without successful use for reason " + "%d, channel reason %d. Circuit purpose %d currently %d,%s. Len " + "%d.", ocirc->global_identifier, + reason, circ->n_chan->reason_for_closing, + circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + pathbias_count_collapse(ocirc); + } else { + pathbias_count_successful_close(ocirc); + } + break; + + /* If we tried to use a circuit but failed, we should probe it to ensure + * it has not been tampered with. */ + case PATH_STATE_USE_ATTEMPTED: + /* XXX: Only probe and/or count failure if the network is live? + * What about clock jumps/suspends? */ + if (pathbias_send_usable_probe(circ) == 0) + return -1; + else + pathbias_count_use_failed(ocirc); + + /* Any circuit where there were attempted streams but no successful + * streams could be bias */ + log_info(LD_CIRC, + "Circuit %d closed without successful use for reason %d. " + "Circuit purpose %d currently %d,%s. Len %d.", + ocirc->global_identifier, + reason, circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + break; + + case PATH_STATE_USE_SUCCEEDED: + pathbias_count_successful_close(ocirc); + pathbias_count_use_success(ocirc); + break; + + case PATH_STATE_USE_FAILED: + pathbias_count_use_failed(ocirc); + break; + + case PATH_STATE_NEW_CIRC: + case PATH_STATE_BUILD_ATTEMPTED: + case PATH_STATE_ALREADY_COUNTED: + default: + // Other states are uninteresting. No stats to count. + break; + } + + ocirc->path_state = PATH_STATE_ALREADY_COUNTED; + + return 0; +} + +/** + * Count a successfully closed circuit. + */ +static void +pathbias_count_successful_close(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + /* In the long run: circuit_success ~= successful_circuit_close + + * circ_failure + stream_failure */ + guard->successful_circuits_closed++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + log_info(LD_CIRC, + "Successfully closed circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count a circuit that fails after it is built, but before it can + * carry any traffic. + * + * This is needed because there are ways to destroy a + * circuit after it has successfully completed. Right now, this is + * used for purely informational/debugging purposes. + */ +static void +pathbias_count_collapse(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->collapsed_circuits++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + log_info(LD_CIRC, + "Destroyed circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count a known failed circuit (because we could not probe it). + * + * This counter is informational. + */ +static void +pathbias_count_use_failed(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->unusable_circuits++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + /* XXX note cut-and-paste code in this function compared to nearby + * functions. Would be nice to refactor. -RD */ + log_info(LD_CIRC, + "Stream-failing circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count timeouts for path bias log messages. + * + * These counts are purely informational. + */ +void +pathbias_count_timeout(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + + if (!pathbias_should_count(circ)) { + return; + } + + /* For hidden service circs, they can actually be used + * successfully and then time out later (because + * the other side declines to use them). */ + if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->timeouts++; + entry_guards_changed(); + } +} + +/** + * Helper function to count all of the currently opened circuits + * for a guard that are in a given path state range. The state + * range is inclusive on both ends. + */ +static int +pathbias_count_circs_in_states(entry_guard_t *guard, + path_state_t from, + path_state_t to) +{ + circuit_t *circ; + int open_circuits = 0; + + /* Count currently open circuits. Give them the benefit of the doubt. */ + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { + origin_circuit_t *ocirc = NULL; + if (!CIRCUIT_IS_ORIGIN(circ) || /* didn't originate here */ + circ->marked_for_close) /* already counted */ + continue; + + ocirc = TO_ORIGIN_CIRCUIT(circ); + + if (!ocirc->cpath || !ocirc->cpath->extend_info) + continue; + + if (ocirc->path_state >= from && + ocirc->path_state <= to && + pathbias_should_count(ocirc) && + fast_memeq(guard->identity, + ocirc->cpath->extend_info->identity_digest, + DIGEST_LEN)) { + log_debug(LD_CIRC, "Found opened circuit %d in path_state %s", + ocirc->global_identifier, + pathbias_state_to_string(ocirc->path_state)); + open_circuits++; + } + } + + return open_circuits; +} + +/** + * Return the number of circuits counted as successfully closed for + * this guard. + * + * Also add in the currently open circuits to give them the benefit + * of the doubt. + */ +double +pathbias_get_close_success_count(entry_guard_t *guard) +{ + return guard->successful_circuits_closed + + pathbias_count_circs_in_states(guard, + PATH_STATE_BUILD_SUCCEEDED, + PATH_STATE_USE_SUCCEEDED); +} + +/** + * Return the number of circuits counted as successfully used + * this guard. + * + * Also add in the currently open circuits that we are attempting + * to use to give them the benefit of the doubt. + */ +double +pathbias_get_use_success_count(entry_guard_t *guard) +{ + return guard->use_successes + + pathbias_count_circs_in_states(guard, + PATH_STATE_USE_ATTEMPTED, + PATH_STATE_USE_SUCCEEDED); +} + +/** + * Check the path bias use rate against our consensus parameter limits. + * + * Emits a log message if the use success rates are too low. + * + * If pathbias_get_dropguards() is set, we also disable the use of + * very failure prone guards. + */ +static void +pathbias_measure_use_rate(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + if (guard->use_attempts > pathbias_get_min_use(options)) { + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_use_success_count(guard)/guard->use_attempts + < pathbias_get_extreme_use_rate(options)) { + /* Dropping is currently disabled by default. */ + if (pathbias_get_dropguards(options)) { + if (!guard->path_bias_disabled) { + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing to carry an extremely large " + "amount of stream on its circuits. " + "To avoid potential route manipulation attacks, Tor has " + "disabled use of this guard. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + guard->path_bias_disabled = 1; + guard->bad_since = approx_time(); + entry_guards_changed(); + return; + } + } else if (!guard->path_bias_use_extreme) { + guard->path_bias_use_extreme = 1; + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing to carry an extremely large " + "amount of streams on its circuits. " + "This could indicate a route manipulation attack, network " + "overload, bad local network connectivity, or a bug. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } else if (pathbias_get_use_success_count(guard)/guard->use_attempts + < pathbias_get_notice_use_rate(options)) { + if (!guard->path_bias_use_noticed) { + guard->path_bias_use_noticed = 1; + log_notice(LD_CIRC, + "Your Guard %s ($%s) is failing to carry more streams on its " + "circuits than usual. " + "Most likely this means the Tor network is overloaded " + "or your network connection is poor. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } + } +} + +/** + * Check the path bias circuit close status rates against our consensus + * parameter limits. + * + * Emits a log message if the use success rates are too low. + * + * If pathbias_get_dropguards() is set, we also disable the use of + * very failure prone guards. + * + * XXX: This function shares similar log messages and checks to + * pathbias_measure_use_rate(). It may be possible to combine them + * eventually, especially if we can ever remove the need for 3 + * levels of closure warns (if the overall circuit failure rate + * goes down with ntor). One way to do so would be to multiply + * the build rate with the use rate to get an idea of the total + * fraction of the total network paths the user is able to use. + * See ticket #8159. + */ +static void +pathbias_measure_close_rate(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + if (guard->circ_attempts > pathbias_get_min_circs(options)) { + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_close_success_count(guard)/guard->circ_attempts + < pathbias_get_extreme_rate(options)) { + /* Dropping is currently disabled by default. */ + if (pathbias_get_dropguards(options)) { + if (!guard->path_bias_disabled) { + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing an extremely large " + "amount of circuits. " + "To avoid potential route manipulation attacks, Tor has " + "disabled use of this guard. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + guard->path_bias_disabled = 1; + guard->bad_since = approx_time(); + entry_guards_changed(); + return; + } + } else if (!guard->path_bias_extreme) { + guard->path_bias_extreme = 1; + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing an extremely large " + "amount of circuits. " + "This could indicate a route manipulation attack, " + "extreme network overload, or a bug. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts + < pathbias_get_warn_rate(options)) { + if (!guard->path_bias_warned) { + guard->path_bias_warned = 1; + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing a very large " + "amount of circuits. " + "Most likely this means the Tor network is " + "overloaded, but it could also mean an attack against " + "you or potentially the guard itself. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts + < pathbias_get_notice_rate(options)) { + if (!guard->path_bias_noticed) { + guard->path_bias_noticed = 1; + log_notice(LD_CIRC, + "Your Guard %s ($%s) is failing more circuits than " + "usual. " + "Most likely this means the Tor network is overloaded. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } + } +} + +/** + * This function scales the path bias use rates if we have + * more data than the scaling threshold. This allows us to + * be more sensitive to recent measurements. + * + * XXX: The attempt count transfer stuff here might be done + * better by keeping separate pending counters that get + * transfered at circuit close. See ticket #8160. + */ +static void +pathbias_scale_close_rates(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + /* If we get a ton of circuits, just scale everything down */ + if (guard->circ_attempts > pathbias_get_scale_threshold(options)) { + double scale_ratio = pathbias_get_scale_ratio(options); + int opened_attempts = pathbias_count_circs_in_states(guard, + PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED); + int opened_built = pathbias_count_circs_in_states(guard, + PATH_STATE_BUILD_SUCCEEDED, + PATH_STATE_USE_FAILED); + /* Verify that the counts are sane before and after scaling */ + int counts_are_sane = (guard->circ_attempts >= guard->circ_successes); + + guard->circ_attempts -= (opened_attempts+opened_built); + guard->circ_successes -= opened_built; + + guard->circ_attempts *= scale_ratio; + guard->circ_successes *= scale_ratio; + guard->timeouts *= scale_ratio; + guard->successful_circuits_closed *= scale_ratio; + guard->collapsed_circuits *= scale_ratio; + guard->unusable_circuits *= scale_ratio; + + guard->circ_attempts += (opened_attempts+opened_built); + guard->circ_successes += opened_built; + + entry_guards_changed(); + + log_info(LD_CIRC, + "Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard " + "%s ($%s)", + guard->circ_successes, guard->successful_circuits_closed, + guard->circ_attempts, opened_built, opened_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + + /* Have the counts just become invalid by this scaling attempt? */ + if (counts_are_sane && guard->circ_attempts < guard->circ_successes) { + log_notice(LD_BUG, + "Scaling has mangled pathbias counts to %f/%f (%d/%d open) " + "for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, opened_built, + opened_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + } +} + +/** + * This function scales the path bias circuit close rates if we have + * more data than the scaling threshold. This allows us to be more + * sensitive to recent measurements. + * + * XXX: The attempt count transfer stuff here might be done + * better by keeping separate pending counters that get + * transfered at circuit close. See ticket #8160. + */ +void +pathbias_scale_use_rates(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + /* If we get a ton of circuits, just scale everything down */ + if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) { + double scale_ratio = pathbias_get_scale_ratio(options); + int opened_attempts = pathbias_count_circs_in_states(guard, + PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); + /* Verify that the counts are sane before and after scaling */ + int counts_are_sane = (guard->use_attempts >= guard->use_successes); + + guard->use_attempts -= opened_attempts; + + guard->use_attempts *= scale_ratio; + guard->use_successes *= scale_ratio; + + guard->use_attempts += opened_attempts; + + log_info(LD_CIRC, + "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)", + guard->use_successes, guard->use_attempts, opened_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + + /* Have the counts just become invalid by this scaling attempt? */ + if (counts_are_sane && guard->use_attempts < guard->use_successes) { + log_notice(LD_BUG, + "Scaling has mangled pathbias usage counts to %f/%f " + "(%d open) for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, + opened_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + + entry_guards_changed(); + } +} + diff --git a/src/or/circpathbias.h b/src/or/circpathbias.h new file mode 100644 index 000000000..c95d801a4 --- /dev/null +++ b/src/or/circpathbias.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitbuild.h + * \brief Header file for circuitbuild.c. + **/ + +#ifndef TOR_CIRCPATHBIAS_H +#define TOR_CIRCPATHBIAS_H + +double pathbias_get_extreme_rate(const or_options_t *options); +double pathbias_get_extreme_use_rate(const or_options_t *options); +int pathbias_get_dropguards(const or_options_t *options); +void pathbias_count_timeout(origin_circuit_t *circ); +void pathbias_count_build_success(origin_circuit_t *circ); +int pathbias_count_build_attempt(origin_circuit_t *circ); +int pathbias_check_close(origin_circuit_t *circ, int reason); +int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); +void pathbias_count_use_attempt(origin_circuit_t *circ); +void pathbias_mark_use_success(origin_circuit_t *circ); +void pathbias_mark_use_rollback(origin_circuit_t *circ); +const char *pathbias_state_to_string(path_state_t state); + +#endif + diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 43d2ffe4d..8e6bb59ef 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -11,6 +11,7 @@ #include "or.h" #include "channel.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" @@ -40,19 +41,11 @@ #include "routerparse.h" #include "routerset.h" #include "crypto.h" -#include "connection_edge.h" #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif -/********* START VARIABLES **********/ - -/** A global list of all circuits at this hop. */ -extern circuit_t *global_circuitlist; - -/********* END VARIABLES ************/ - static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port, const char *id_digest); @@ -64,14 +57,6 @@ 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); -static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard); -static void pathbias_count_build_success(origin_circuit_t *circ); -static void pathbias_count_successful_close(origin_circuit_t *circ); -static void pathbias_count_collapse(origin_circuit_t *circ); -static void pathbias_count_use_failed(origin_circuit_t *circ); -static void pathbias_measure_use_rate(entry_guard_t *guard); -static void pathbias_measure_close_rate(entry_guard_t *guard); -static void pathbias_scale_use_rates(entry_guard_t *guard); /** This function tries to get a channel to the specified endpoint, * and then calls command_setup_channel() to give it the right @@ -600,16 +585,18 @@ should_use_create_fast_for_circuit(origin_circuit_t *circ) if (!circ->cpath->extend_info->onion_key) return 1; /* our hand is forced: only a create_fast will work. */ - if (!options->FastFirstHopPK) - return 0; /* we prefer to avoid create_fast */ if (public_server_mode(options)) { /* We're a server, and we know an onion key. We can choose. * Prefer to blend our circuit into the other circuits we are * 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 1; + return options->FastFirstHopPK; } /** Return true if <b>circ</b> is the type of circuit we want to count @@ -779,20 +766,24 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) * it off at, we probably had a suspend event along this codepath, * and we should discard the value. */ - if (timediff < 0 || timediff > 2*circ_times.close_ms+1000) { + if (timediff < 0 || + timediff > 2*get_circuit_build_close_time_ms()+1000) { log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " "Assuming clock jump. Purpose %d (%s)", timediff, circ->base_.purpose, circuit_purpose_to_string(circ->base_.purpose)); } else if (!circuit_build_times_disabled()) { /* Only count circuit times if the network is live */ - if (circuit_build_times_network_check_live(&circ_times)) { - circuit_build_times_add_time(&circ_times, (build_time_t)timediff); - circuit_build_times_set_timeout(&circ_times); + if (circuit_build_times_network_check_live( + get_circuit_build_times())) { + circuit_build_times_add_time(get_circuit_build_times_mutable(), + (build_time_t)timediff); + circuit_build_times_set_timeout(get_circuit_build_times_mutable()); } if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - circuit_build_times_network_circ_success(&circ_times); + circuit_build_times_network_circ_success( + get_circuit_build_times_mutable()); } } } @@ -1087,1516 +1078,6 @@ circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, return 0; } -/** The minimum number of circuit attempts before we start - * thinking about warning about path bias and dropping guards */ -static int -pathbias_get_min_circs(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_MIN_CIRC 150 - if (options->PathBiasCircThreshold >= 5) - return options->PathBiasCircThreshold; - else - return networkstatus_get_param(NULL, "pb_mincircs", - DFLT_PATH_BIAS_MIN_CIRC, - 5, INT32_MAX); -} - -/** The circuit success rate below which we issue a notice */ -static double -pathbias_get_notice_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_NOTICE_PCT 70 - if (options->PathBiasNoticeRate >= 0.0) - return options->PathBiasNoticeRate; - else - return networkstatus_get_param(NULL, "pb_noticepct", - DFLT_PATH_BIAS_NOTICE_PCT, 0, 100)/100.0; -} - -/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ -/** The circuit success rate below which we issue a warn */ -static double -pathbias_get_warn_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_WARN_PCT 50 - if (options->PathBiasWarnRate >= 0.0) - return options->PathBiasWarnRate; - else - return networkstatus_get_param(NULL, "pb_warnpct", - DFLT_PATH_BIAS_WARN_PCT, 0, 100)/100.0; -} - -/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ -/** - * The extreme rate is the rate at which we would drop the guard, - * if pb_dropguard is also set. Otherwise we just warn. - */ -double -pathbias_get_extreme_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_EXTREME_PCT 30 - if (options->PathBiasExtremeRate >= 0.0) - return options->PathBiasExtremeRate; - else - return networkstatus_get_param(NULL, "pb_extremepct", - DFLT_PATH_BIAS_EXTREME_PCT, 0, 100)/100.0; -} - -/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ -/** - * If 1, we actually disable use of guards that fall below - * the extreme_pct. - */ -int -pathbias_get_dropguards(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_DROP_GUARDS 0 - if (options->PathBiasDropGuards >= 0) - return options->PathBiasDropGuards; - else - return networkstatus_get_param(NULL, "pb_dropguards", - DFLT_PATH_BIAS_DROP_GUARDS, 0, 1); -} - -/** - * This is the number of circuits at which we scale our - * counts by mult_factor/scale_factor. Note, this count is - * not exact, as we only perform the scaling in the event - * of no integer truncation. - */ -static int -pathbias_get_scale_threshold(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_SCALE_THRESHOLD 300 - if (options->PathBiasScaleThreshold >= 10) - return options->PathBiasScaleThreshold; - else - return networkstatus_get_param(NULL, "pb_scalecircs", - DFLT_PATH_BIAS_SCALE_THRESHOLD, 10, - INT32_MAX); -} - -/** - * Compute the path bias scaling ratio from the consensus - * parameters pb_multfactor/pb_scalefactor. - * - * Returns a value in (0, 1.0] which we multiply our pathbias - * counts with to scale them down. - */ -static double -pathbias_get_scale_ratio(const or_options_t *options) -{ - /* - * The scale factor is the denominator for our scaling - * of circuit counts for our path bias window. - * - * Note that our use of doubles for the path bias state - * file means that powers of 2 work best here. - */ - int denominator = networkstatus_get_param(NULL, "pb_scalefactor", - 2, 2, INT32_MAX); - (void) options; - /** - * The mult factor is the numerator for our scaling - * of circuit counts for our path bias window. It - * allows us to scale by fractions. - */ - return networkstatus_get_param(NULL, "pb_multfactor", - 1, 1, denominator)/((double)denominator); -} - -/** The minimum number of circuit usage attempts before we start - * thinking about warning about path use bias and dropping guards */ -static int -pathbias_get_min_use(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_MIN_USE 20 - if (options->PathBiasUseThreshold >= 3) - return options->PathBiasUseThreshold; - else - return networkstatus_get_param(NULL, "pb_minuse", - DFLT_PATH_BIAS_MIN_USE, - 3, INT32_MAX); -} - -/** The circuit use success rate below which we issue a notice */ -static double -pathbias_get_notice_use_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_NOTICE_USE_PCT 80 - if (options->PathBiasNoticeUseRate >= 0.0) - return options->PathBiasNoticeUseRate; - else - return networkstatus_get_param(NULL, "pb_noticeusepct", - DFLT_PATH_BIAS_NOTICE_USE_PCT, - 0, 100)/100.0; -} - -/** - * The extreme use rate is the rate at which we would drop the guard, - * if pb_dropguard is also set. Otherwise we just warn. - */ -double -pathbias_get_extreme_use_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_EXTREME_USE_PCT 60 - if (options->PathBiasExtremeUseRate >= 0.0) - return options->PathBiasExtremeUseRate; - else - return networkstatus_get_param(NULL, "pb_extremeusepct", - DFLT_PATH_BIAS_EXTREME_USE_PCT, - 0, 100)/100.0; -} - -/** - * This is the number of circuits at which we scale our - * use counts by mult_factor/scale_factor. Note, this count is - * not exact, as we only perform the scaling in the event - * of no integer truncation. - */ -static int -pathbias_get_scale_use_threshold(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_SCALE_USE_THRESHOLD 100 - if (options->PathBiasScaleUseThreshold >= 10) - return options->PathBiasScaleUseThreshold; - else - return networkstatus_get_param(NULL, "pb_scaleuse", - DFLT_PATH_BIAS_SCALE_USE_THRESHOLD, - 10, INT32_MAX); -} - -/** - * Convert a Guard's path state to string. - */ -const char * -pathbias_state_to_string(path_state_t state) -{ - switch (state) { - case PATH_STATE_NEW_CIRC: - return "new"; - case PATH_STATE_BUILD_ATTEMPTED: - return "build attempted"; - case PATH_STATE_BUILD_SUCCEEDED: - return "build succeeded"; - case PATH_STATE_USE_ATTEMPTED: - return "use attempted"; - case PATH_STATE_USE_SUCCEEDED: - return "use succeeded"; - case PATH_STATE_USE_FAILED: - return "use failed"; - case PATH_STATE_ALREADY_COUNTED: - return "already counted"; - } - - return "unknown"; -} - -/** - * This function decides if a circuit has progressed far enough to count - * as a circuit "attempt". As long as end-to-end tagging is possible, - * we assume the adversary will use it over hop-to-hop failure. Therefore, - * we only need to account bias for the last hop. This should make us - * much more resilient to ambient circuit failure, and also make that - * failure easier to measure (we only need to measure Exit failure rates). - */ -static int -pathbias_is_new_circ_attempt(origin_circuit_t *circ) -{ -#define N2N_TAGGING_IS_POSSIBLE -#ifdef N2N_TAGGING_IS_POSSIBLE - /* cpath is a circular list. We want circs with more than one hop, - * and the second hop must be waiting for keys still (it's just - * about to get them). */ - return circ->cpath && - circ->cpath->next != circ->cpath && - circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; -#else - /* If tagging attacks are no longer possible, we probably want to - * count bias from the first hop. However, one could argue that - * timing-based tagging is still more useful than per-hop failure. - * In which case, we'd never want to use this. - */ - return circ->cpath && - circ->cpath->state == CPATH_STATE_AWAITING_KEYS; -#endif -} - -/** - * Decide if the path bias code should count a circuit. - * - * @returns 1 if we should count it, 0 otherwise. - */ -static int -pathbias_should_count(origin_circuit_t *circ) -{ -#define PATHBIAS_COUNT_INTERVAL (600) - static ratelim_t count_limit = - RATELIM_INIT(PATHBIAS_COUNT_INTERVAL); - char *rate_msg = NULL; - - /* We can't do path bias accounting without entry guards. - * Testing and controller circuits also have no guards. - * - * We also don't count server-side rends, because their - * endpoint could be chosen maliciously. - * Similarly, we can't count client-side intro attempts, - * because clients can be manipulated into connecting to - * malicious intro points. */ - if (get_options()->UseEntryGuards == 0 || - circ->base_.purpose == CIRCUIT_PURPOSE_TESTING || - circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER || - circ->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || - circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED || - (circ->base_.purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && - circ->base_.purpose <= CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { - - /* Check to see if the shouldcount result has changed due to a - * unexpected purpose change that would affect our results. - * - * The reason we check the path state too here is because for the - * cannibalized versions of these purposes, we count them as successful - * before their purpose change. - */ - if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED - && circ->path_state != PATH_STATE_ALREADY_COUNTED) { - log_info(LD_BUG, - "Circuit %d is now being ignored despite being counted " - "in the past. Purpose is %s, path state is %s", - circ->global_identifier, - circuit_purpose_to_string(circ->base_.purpose), - pathbias_state_to_string(circ->path_state)); - } - circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; - return 0; - } - - /* Completely ignore one hop circuits */ - if (circ->build_state->onehop_tunnel || - circ->build_state->desired_path_len == 1) { - /* Check for inconsistency */ - if (circ->build_state->desired_path_len != 1 || - !circ->build_state->onehop_tunnel) { - if ((rate_msg = rate_limit_log(&count_limit, approx_time()))) { - log_info(LD_BUG, - "One-hop circuit has length %d. Path state is %s. " - "Circuit is a %s currently %s.%s", - circ->build_state->desired_path_len, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - tor_fragile_assert(); - } - - /* Check to see if the shouldcount result has changed due to a - * unexpected change that would affect our results */ - if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED) { - log_info(LD_BUG, - "One-hop circuit %d is now being ignored despite being counted " - "in the past. Purpose is %s, path state is %s", - circ->global_identifier, - circuit_purpose_to_string(circ->base_.purpose), - pathbias_state_to_string(circ->path_state)); - } - circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; - return 0; - } - - /* Check to see if the shouldcount result has changed due to a - * unexpected purpose change that would affect our results */ - if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_IGNORED) { - log_info(LD_BUG, - "Circuit %d is now being counted despite being ignored " - "in the past. Purpose is %s, path state is %s", - circ->global_identifier, - circuit_purpose_to_string(circ->base_.purpose), - pathbias_state_to_string(circ->path_state)); - } - circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_COUNTED; - - return 1; -} - -/** - * Check our circuit state to see if this is a successful circuit attempt. - * If so, record it in the current guard's path bias circ_attempt count. - * - * Also check for several potential error cases for bug #6475. - */ -static int -pathbias_count_build_attempt(origin_circuit_t *circ) -{ -#define CIRC_ATTEMPT_NOTICE_INTERVAL (600) - static ratelim_t circ_attempt_notice_limit = - RATELIM_INIT(CIRC_ATTEMPT_NOTICE_INTERVAL); - char *rate_msg = NULL; - - if (!pathbias_should_count(circ)) { - return 0; - } - - if (pathbias_is_new_circ_attempt(circ)) { - /* Help track down the real cause of bug #6475: */ - if (circ->has_opened && circ->path_state != PATH_STATE_BUILD_ATTEMPTED) { - if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Opened circuit is in strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - - /* Don't re-count cannibalized circs.. */ - if (!circ->has_opened) { - entry_guard_t *guard = NULL; - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } else if (circ->base_.n_chan) { - guard = - entry_guard_get_by_id_digest(circ->base_.n_chan->identity_digest); - } - - if (guard) { - if (circ->path_state == PATH_STATE_NEW_CIRC) { - circ->path_state = PATH_STATE_BUILD_ATTEMPTED; - - if (entry_guard_inc_circ_attempt_count(guard) < 0) { - /* Bogus guard; we already warned. */ - return -END_CIRC_REASON_TORPROTOCOL; - } - } else { - if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Unopened circuit has strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } else { - if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, - approx_time()))) { - log_info(LD_CIRC, - "Unopened circuit has no known guard. " - "Circuit is a %s currently %s.%s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } - } - - return 0; -} - -/** - * Check our circuit state to see if this is a successful circuit - * completion. If so, record it in the current guard's path bias - * success count. - * - * Also check for several potential error cases for bug #6475. - */ -static void -pathbias_count_build_success(origin_circuit_t *circ) -{ -#define SUCCESS_NOTICE_INTERVAL (600) - static ratelim_t success_notice_limit = - RATELIM_INIT(SUCCESS_NOTICE_INTERVAL); - char *rate_msg = NULL; - entry_guard_t *guard = NULL; - - if (!pathbias_should_count(circ)) { - return; - } - - /* Don't count cannibalized/reused circs for path bias - * "build" success, since they get counted under "use" success. */ - if (!circ->has_opened) { - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) { - circ->path_state = PATH_STATE_BUILD_SUCCEEDED; - guard->circ_successes++; - entry_guards_changed(); - - log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } else { - if ((rate_msg = rate_limit_log(&success_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Succeeded circuit is in strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - - if (guard->circ_attempts < guard->circ_successes) { - log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " - "for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - if ((rate_msg = rate_limit_log(&success_notice_limit, - approx_time()))) { - log_info(LD_CIRC, - "Completed circuit has no known guard. " - "Circuit is a %s currently %s.%s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } else { - if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { - if ((rate_msg = rate_limit_log(&success_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Opened circuit is in strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } -} - -/** - * Record an attempt to use a circuit. Changes the circuit's - * path state and update its guard's usage counter. - * - * Used for path bias usage accounting. - */ -void -pathbias_count_use_attempt(origin_circuit_t *circ) -{ - entry_guard_t *guard; - - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { - log_notice(LD_BUG, - "Used circuit is in strange path state %s. " - "Circuit is a %s currently %s.", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - if (guard) { - pathbias_measure_use_rate(guard); - pathbias_scale_use_rates(guard); - guard->use_attempts++; - entry_guards_changed(); - - log_debug(LD_CIRC, - "Marked circuit %d (%f/%f) as used for guard %s ($%s).", - circ->global_identifier, - guard->use_successes, guard->use_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } - - circ->path_state = PATH_STATE_USE_ATTEMPTED; - } else { - /* Harmless but educational log message */ - log_info(LD_CIRC, - "Used circuit %d is already in path state %s. " - "Circuit is a %s currently %s.", - circ->global_identifier, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } - - return; -} - -/** - * Check the circuit's path state is appropriate and mark it as - * successfully used. Used for path bias usage accounting. - * - * We don't actually increment the guard's counters until - * pathbias_check_close(), because the circuit can still transition - * back to PATH_STATE_USE_ATTEMPTED if a stream fails later (this - * is done so we can probe the circuit for liveness at close). - */ -void -pathbias_mark_use_success(origin_circuit_t *circ) -{ - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { - log_notice(LD_BUG, - "Used circuit %d is in strange path state %s. " - "Circuit is a %s currently %s.", - circ->global_identifier, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - - pathbias_count_use_attempt(circ); - } - - /* We don't do any accounting at the guard until actual circuit close */ - circ->path_state = PATH_STATE_USE_SUCCEEDED; - - return; -} - -/** - * If a stream ever detatches from a circuit in a retriable way, - * we need to mark this circuit as still needing either another - * successful stream, or in need of a probe. - * - * An adversary could let the first stream request succeed (ie the - * resolve), but then tag and timeout the remainder (via cell - * dropping), forcing them on new circuits. - * - * Rolling back the state will cause us to probe such circuits, which - * should lead to probe failures in the event of such tagging due to - * either unrecognized cells coming in while we wait for the probe, - * or the cipher state getting out of sync in the case of dropped cells. - */ -void -pathbias_mark_use_rollback(origin_circuit_t *circ) -{ - if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { - log_info(LD_CIRC, - "Rolling back pathbias use state to 'attempted' for detached " - "circuit %d", circ->global_identifier); - circ->path_state = PATH_STATE_USE_ATTEMPTED; - } -} - -/** - * Actually count a circuit success towards a guard's usage counters - * if the path state is appropriate. - */ -static void -pathbias_count_use_success(origin_circuit_t *circ) -{ - entry_guard_t *guard; - - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->path_state != PATH_STATE_USE_SUCCEEDED) { - log_notice(LD_BUG, - "Successfully used circuit %d is in strange path state %s. " - "Circuit is a %s currently %s.", - circ->global_identifier, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } else { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - if (guard) { - guard->use_successes++; - entry_guards_changed(); - - if (guard->use_attempts < guard->use_successes) { - log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) " - "for guard %s=%s", - guard->use_successes, guard->use_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } - - log_debug(LD_CIRC, - "Marked circuit %d (%f/%f) as used successfully for guard " - "%s ($%s).", - circ->global_identifier, guard->use_successes, - guard->use_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - } - } - - return; -} - -/** - * Send a probe down a circuit that the client attempted to use, - * but for which the stream timed out/failed. The probe is a - * RELAY_BEGIN cell with a 0.a.b.c destination address, which - * the exit will reject and reply back, echoing that address. - * - * The reason for such probes is because it is possible to bias - * a user's paths simply by causing timeouts, and these timeouts - * are not possible to differentiate from unresponsive servers. - * - * The probe is sent at the end of the circuit lifetime for two - * reasons: to prevent cryptographic taggers from being able to - * drop cells to cause timeouts, and to prevent easy recognition - * of probes before any real client traffic happens. - * - * Returns -1 if we couldn't probe, 0 otherwise. - */ -static int -pathbias_send_usable_probe(circuit_t *circ) -{ - /* Based on connection_ap_handshake_send_begin() */ - char payload[CELL_PAYLOAD_SIZE]; - int payload_len; - origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - crypt_path_t *cpath_layer = NULL; - char *probe_nonce = NULL; - - tor_assert(ocirc); - - cpath_layer = ocirc->cpath->prev; - - if (cpath_layer->state != CPATH_STATE_OPEN) { - /* This can happen for cannibalized circuits. Their - * last hop isn't yet open */ - log_info(LD_CIRC, - "Got pathbias probe request for unopened circuit %d. " - "Opened %d, len %d", ocirc->global_identifier, - ocirc->has_opened, ocirc->build_state->desired_path_len); - return -1; - } - - /* We already went down this road. */ - if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING && - ocirc->pathbias_probe_id) { - log_info(LD_CIRC, - "Got pathbias probe request for circuit %d with " - "outstanding probe", ocirc->global_identifier); - return -1; - } - - /* Can't probe if the channel isn't open */ - if (circ->n_chan == NULL || - (circ->n_chan->state != CHANNEL_STATE_OPEN - && circ->n_chan->state != CHANNEL_STATE_MAINT)) { - log_info(LD_CIRC, - "Skipping pathbias probe for circuit %d: Channel is not open.", - ocirc->global_identifier); - return -1; - } - - circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); - - /* Update timestamp for when circuit_expire_building() should kill us */ - tor_gettimeofday(&circ->timestamp_began); - - /* Generate a random address for the nonce */ - crypto_rand((char*)ô->pathbias_probe_nonce, - sizeof(ocirc->pathbias_probe_nonce)); - ocirc->pathbias_probe_nonce &= 0x00ffffff; - probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce); - - tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce); - payload_len = (int)strlen(payload)+1; - - // XXX: need this? Can we assume ipv4 will always be supported? - // If not, how do we tell? - //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { - // set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); - // payload_len += 4; - //} - - /* Generate+Store stream id, make sure it's non-zero */ - ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc); - - if (ocirc->pathbias_probe_id==0) { - log_warn(LD_CIRC, - "Ran out of stream IDs on circuit %u during " - "pathbias probe attempt.", ocirc->global_identifier); - tor_free(probe_nonce); - return -1; - } - - log_info(LD_CIRC, - "Sending pathbias testing cell to %s:25 on stream %d for circ %d.", - probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier); - tor_free(probe_nonce); - - /* Send a test relay cell */ - if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ, - RELAY_COMMAND_BEGIN, payload, - payload_len, cpath_layer) < 0) { - log_notice(LD_CIRC, - "Failed to send pathbias probe cell on circuit %d.", - ocirc->global_identifier); - return -1; - } - - /* Mark it freshly dirty so it doesn't get expired in the meantime */ - circ->timestamp_dirty = time(NULL); - - return 0; -} - -/** - * Check the response to a pathbias probe, to ensure the - * cell is recognized and the nonce and other probe - * characteristics are as expected. - * - * If the response is valid, return 0. Otherwise return < 0. - */ -int -pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) -{ - /* Based on connection_edge_process_relay_cell() */ - relay_header_t rh; - int reason; - uint32_t ipv4_host; - origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - - tor_assert(cell); - tor_assert(ocirc); - tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING); - - relay_header_unpack(&rh, cell->payload); - - reason = rh.length > 0 ? - get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC; - - if (rh.command == RELAY_COMMAND_END && - reason == END_STREAM_REASON_EXITPOLICY && - ocirc->pathbias_probe_id == rh.stream_id) { - - /* Check length+extract host: It is in network order after the reason code. - * See connection_edge_end(). */ - if (rh.length < 9) { /* reason+ipv4+dns_ttl */ - log_notice(LD_PROTOCOL, - "Short path bias probe response length field (%d).", rh.length); - return - END_CIRC_REASON_TORPROTOCOL; - } - - ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); - - /* Check nonce */ - if (ipv4_host == ocirc->pathbias_probe_nonce) { - pathbias_mark_use_success(ocirc); - circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); - log_info(LD_CIRC, - "Got valid path bias probe back for circ %d, stream %d.", - ocirc->global_identifier, ocirc->pathbias_probe_id); - return 0; - } else { - log_notice(LD_CIRC, - "Got strange probe value 0x%x vs 0x%x back for circ %d, " - "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce, - ocirc->global_identifier, ocirc->pathbias_probe_id); - return -1; - } - } - log_info(LD_CIRC, - "Got another cell back back on pathbias probe circuit %d: " - "Command: %d, Reason: %d, Stream-id: %d", - ocirc->global_identifier, rh.command, reason, rh.stream_id); - return -1; -} - -/** - * Check if a circuit was used and/or closed successfully. - * - * If we attempted to use the circuit to carry a stream but failed - * for whatever reason, or if the circuit mysteriously died before - * we could attach any streams, record these two cases. - * - * If we *have* successfully used the circuit, or it appears to - * have been closed by us locally, count it as a success. - * - * Returns 0 if we're done making decisions with the circ, - * or -1 if we want to probe it first. - */ -int -pathbias_check_close(origin_circuit_t *ocirc, int reason) -{ - circuit_t *circ = ô->base_; - - if (!pathbias_should_count(ocirc)) { - return 0; - } - - switch (ocirc->path_state) { - /* If the circuit was closed after building, but before use, we need - * to ensure we were the ones who tried to close it (and not a remote - * actor). */ - case PATH_STATE_BUILD_SUCCEEDED: - if (reason & END_CIRC_REASON_FLAG_REMOTE) { - /* Remote circ close reasons on an unused circuit all could be bias */ - log_info(LD_CIRC, - "Circuit %d remote-closed without successful use for reason %d. " - "Circuit purpose %d currently %d,%s. Len %d.", - ocirc->global_identifier, - reason, circ->purpose, ocirc->has_opened, - circuit_state_to_string(circ->state), - ocirc->build_state->desired_path_len); - pathbias_count_collapse(ocirc); - } else if ((reason & ~END_CIRC_REASON_FLAG_REMOTE) - == END_CIRC_REASON_CHANNEL_CLOSED && - circ->n_chan && - circ->n_chan->reason_for_closing - != CHANNEL_CLOSE_REQUESTED) { - /* If we didn't close the channel ourselves, it could be bias */ - /* XXX: Only count bias if the network is live? - * What about clock jumps/suspends? */ - log_info(LD_CIRC, - "Circuit %d's channel closed without successful use for reason " - "%d, channel reason %d. Circuit purpose %d currently %d,%s. Len " - "%d.", ocirc->global_identifier, - reason, circ->n_chan->reason_for_closing, - circ->purpose, ocirc->has_opened, - circuit_state_to_string(circ->state), - ocirc->build_state->desired_path_len); - pathbias_count_collapse(ocirc); - } else { - pathbias_count_successful_close(ocirc); - } - break; - - /* If we tried to use a circuit but failed, we should probe it to ensure - * it has not been tampered with. */ - case PATH_STATE_USE_ATTEMPTED: - /* XXX: Only probe and/or count failure if the network is live? - * What about clock jumps/suspends? */ - if (pathbias_send_usable_probe(circ) == 0) - return -1; - else - pathbias_count_use_failed(ocirc); - - /* Any circuit where there were attempted streams but no successful - * streams could be bias */ - log_info(LD_CIRC, - "Circuit %d closed without successful use for reason %d. " - "Circuit purpose %d currently %d,%s. Len %d.", - ocirc->global_identifier, - reason, circ->purpose, ocirc->has_opened, - circuit_state_to_string(circ->state), - ocirc->build_state->desired_path_len); - break; - - case PATH_STATE_USE_SUCCEEDED: - pathbias_count_successful_close(ocirc); - pathbias_count_use_success(ocirc); - break; - - case PATH_STATE_USE_FAILED: - pathbias_count_use_failed(ocirc); - break; - - case PATH_STATE_NEW_CIRC: - case PATH_STATE_BUILD_ATTEMPTED: - case PATH_STATE_ALREADY_COUNTED: - default: - // Other states are uninteresting. No stats to count. - break; - } - - ocirc->path_state = PATH_STATE_ALREADY_COUNTED; - - return 0; -} - -/** - * Count a successfully closed circuit. - */ -static void -pathbias_count_successful_close(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - /* In the long run: circuit_success ~= successful_circuit_close + - * circ_failure + stream_failure */ - guard->successful_circuits_closed++; - entry_guards_changed(); - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - log_info(LD_CIRC, - "Successfully closed circuit has no known guard. " - "Circuit is a %s currently %s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } -} - -/** - * Count a circuit that fails after it is built, but before it can - * carry any traffic. - * - * This is needed because there are ways to destroy a - * circuit after it has successfully completed. Right now, this is - * used for purely informational/debugging purposes. - */ -static void -pathbias_count_collapse(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - guard->collapsed_circuits++; - entry_guards_changed(); - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - log_info(LD_CIRC, - "Destroyed circuit has no known guard. " - "Circuit is a %s currently %s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } -} - -/** - * Count a known failed circuit (because we could not probe it). - * - * This counter is informational. - */ -static void -pathbias_count_use_failed(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - guard->unusable_circuits++; - entry_guards_changed(); - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - /* XXX note cut-and-paste code in this function compared to nearby - * functions. Would be nice to refactor. -RD */ - log_info(LD_CIRC, - "Stream-failing circuit has no known guard. " - "Circuit is a %s currently %s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } -} - -/** - * Count timeouts for path bias log messages. - * - * These counts are purely informational. - */ -void -pathbias_count_timeout(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - - if (!pathbias_should_count(circ)) { - return; - } - - /* For hidden service circs, they can actually be used - * successfully and then time out later (because - * the other side declines to use them). */ - if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - guard->timeouts++; - entry_guards_changed(); - } -} - -/** - * Helper function to count all of the currently opened circuits - * for a guard that are in a given path state range. The state - * range is inclusive on both ends. - */ -static int -pathbias_count_circs_in_states(entry_guard_t *guard, - path_state_t from, - path_state_t to) -{ - circuit_t *circ; - int open_circuits = 0; - - /* Count currently open circuits. Give them the benefit of the doubt. */ - for (circ = global_circuitlist; circ; circ = circ->next) { - origin_circuit_t *ocirc = NULL; - if (!CIRCUIT_IS_ORIGIN(circ) || /* didn't originate here */ - circ->marked_for_close) /* already counted */ - continue; - - ocirc = TO_ORIGIN_CIRCUIT(circ); - - if (!ocirc->cpath || !ocirc->cpath->extend_info) - continue; - - if (ocirc->path_state >= from && - ocirc->path_state <= to && - pathbias_should_count(ocirc) && - fast_memeq(guard->identity, - ocirc->cpath->extend_info->identity_digest, - DIGEST_LEN)) { - log_debug(LD_CIRC, "Found opened circuit %d in path_state %s", - ocirc->global_identifier, - pathbias_state_to_string(ocirc->path_state)); - open_circuits++; - } - } - - return open_circuits; -} - -/** - * Return the number of circuits counted as successfully closed for - * this guard. - * - * Also add in the currently open circuits to give them the benefit - * of the doubt. - */ -double -pathbias_get_close_success_count(entry_guard_t *guard) -{ - return guard->successful_circuits_closed + - pathbias_count_circs_in_states(guard, - PATH_STATE_BUILD_SUCCEEDED, - PATH_STATE_USE_SUCCEEDED); -} - -/** - * Return the number of circuits counted as successfully used - * this guard. - * - * Also add in the currently open circuits that we are attempting - * to use to give them the benefit of the doubt. - */ -double -pathbias_get_use_success_count(entry_guard_t *guard) -{ - return guard->use_successes + - pathbias_count_circs_in_states(guard, - PATH_STATE_USE_ATTEMPTED, - PATH_STATE_USE_SUCCEEDED); -} - -/** - * Check the path bias use rate against our consensus parameter limits. - * - * Emits a log message if the use success rates are too low. - * - * If pathbias_get_dropguards() is set, we also disable the use of - * very failure prone guards. - */ -static void -pathbias_measure_use_rate(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - if (guard->use_attempts > pathbias_get_min_use(options)) { - /* Note: We rely on the < comparison here to allow us to set a 0 - * rate and disable the feature entirely. If refactoring, don't - * change to <= */ - if (pathbias_get_use_success_count(guard)/guard->use_attempts - < pathbias_get_extreme_use_rate(options)) { - /* Dropping is currently disabled by default. */ - if (pathbias_get_dropguards(options)) { - if (!guard->path_bias_disabled) { - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing to carry an extremely large " - "amount of stream on its circuits. " - "To avoid potential route manipulation attacks, Tor has " - "disabled use of this guard. " - "Use counts are %ld/%ld. Success counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - guard->path_bias_disabled = 1; - guard->bad_since = approx_time(); - entry_guards_changed(); - return; - } - } else if (!guard->path_bias_use_extreme) { - guard->path_bias_use_extreme = 1; - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing to carry an extremely large " - "amount of streams on its circuits. " - "This could indicate a route manipulation attack, network " - "overload, bad local network connectivity, or a bug. " - "Use counts are %ld/%ld. Success counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } else if (pathbias_get_use_success_count(guard)/guard->use_attempts - < pathbias_get_notice_use_rate(options)) { - if (!guard->path_bias_use_noticed) { - guard->path_bias_use_noticed = 1; - log_notice(LD_CIRC, - "Your Guard %s ($%s) is failing to carry more streams on its " - "circuits than usual. " - "Most likely this means the Tor network is overloaded " - "or your network connection is poor. " - "Use counts are %ld/%ld. Success counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } - } -} - -/** - * Check the path bias circuit close status rates against our consensus - * parameter limits. - * - * Emits a log message if the use success rates are too low. - * - * If pathbias_get_dropguards() is set, we also disable the use of - * very failure prone guards. - * - * XXX: This function shares similar log messages and checks to - * pathbias_measure_use_rate(). It may be possible to combine them - * eventually, especially if we can ever remove the need for 3 - * levels of closure warns (if the overall circuit failure rate - * goes down with ntor). One way to do so would be to multiply - * the build rate with the use rate to get an idea of the total - * fraction of the total network paths the user is able to use. - * See ticket #8159. - */ -static void -pathbias_measure_close_rate(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - if (guard->circ_attempts > pathbias_get_min_circs(options)) { - /* Note: We rely on the < comparison here to allow us to set a 0 - * rate and disable the feature entirely. If refactoring, don't - * change to <= */ - if (pathbias_get_close_success_count(guard)/guard->circ_attempts - < pathbias_get_extreme_rate(options)) { - /* Dropping is currently disabled by default. */ - if (pathbias_get_dropguards(options)) { - if (!guard->path_bias_disabled) { - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing an extremely large " - "amount of circuits. " - "To avoid potential route manipulation attacks, Tor has " - "disabled use of this guard. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - guard->path_bias_disabled = 1; - guard->bad_since = approx_time(); - entry_guards_changed(); - return; - } - } else if (!guard->path_bias_extreme) { - guard->path_bias_extreme = 1; - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing an extremely large " - "amount of circuits. " - "This could indicate a route manipulation attack, " - "extreme network overload, or a bug. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts - < pathbias_get_warn_rate(options)) { - if (!guard->path_bias_warned) { - guard->path_bias_warned = 1; - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing a very large " - "amount of circuits. " - "Most likely this means the Tor network is " - "overloaded, but it could also mean an attack against " - "you or potentially the guard itself. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts - < pathbias_get_notice_rate(options)) { - if (!guard->path_bias_noticed) { - guard->path_bias_noticed = 1; - log_notice(LD_CIRC, - "Your Guard %s ($%s) is failing more circuits than " - "usual. " - "Most likely this means the Tor network is overloaded. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } - } -} - -/** - * This function scales the path bias use rates if we have - * more data than the scaling threshold. This allows us to - * be more sensitive to recent measurements. - * - * XXX: The attempt count transfer stuff here might be done - * better by keeping separate pending counters that get - * transfered at circuit close. See ticket #8160. - */ -static void -pathbias_scale_close_rates(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - /* If we get a ton of circuits, just scale everything down */ - if (guard->circ_attempts > pathbias_get_scale_threshold(options)) { - double scale_ratio = pathbias_get_scale_ratio(options); - int opened_attempts = pathbias_count_circs_in_states(guard, - PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED); - int opened_built = pathbias_count_circs_in_states(guard, - PATH_STATE_BUILD_SUCCEEDED, - PATH_STATE_USE_FAILED); - /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->circ_attempts >= guard->circ_successes); - - guard->circ_attempts -= (opened_attempts+opened_built); - guard->circ_successes -= opened_built; - - guard->circ_attempts *= scale_ratio; - guard->circ_successes *= scale_ratio; - guard->timeouts *= scale_ratio; - guard->successful_circuits_closed *= scale_ratio; - guard->collapsed_circuits *= scale_ratio; - guard->unusable_circuits *= scale_ratio; - - guard->circ_attempts += (opened_attempts+opened_built); - guard->circ_successes += opened_built; - - entry_guards_changed(); - - log_info(LD_CIRC, - "Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard " - "%s ($%s)", - guard->circ_successes, guard->successful_circuits_closed, - guard->circ_attempts, opened_built, opened_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - - /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->circ_attempts < guard->circ_successes) { - log_notice(LD_BUG, - "Scaling has mangled pathbias counts to %f/%f (%d/%d open) " - "for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, opened_built, - opened_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - } - } -} - -/** - * This function scales the path bias circuit close rates if we have - * more data than the scaling threshold. This allows us to be more - * sensitive to recent measurements. - * - * XXX: The attempt count transfer stuff here might be done - * better by keeping separate pending counters that get - * transfered at circuit close. See ticket #8160. - */ -void -pathbias_scale_use_rates(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - /* If we get a ton of circuits, just scale everything down */ - if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) { - double scale_ratio = pathbias_get_scale_ratio(options); - int opened_attempts = pathbias_count_circs_in_states(guard, - PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); - /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->use_attempts >= guard->use_successes); - - guard->use_attempts -= opened_attempts; - - guard->use_attempts *= scale_ratio; - guard->use_successes *= scale_ratio; - - guard->use_attempts += opened_attempts; - - log_info(LD_CIRC, - "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)", - guard->use_successes, guard->use_attempts, opened_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - - /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->use_attempts < guard->use_successes) { - log_notice(LD_BUG, - "Scaling has mangled pathbias usage counts to %f/%f " - "(%d open) for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, - opened_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - } - - entry_guards_changed(); - } -} - -/** Increment the number of times we successfully extended a circuit to - * <b>guard</b>, first checking if the failure rate is high enough that - * we should eliminate the guard. Return -1 if the guard looks no good; - * return 0 if the guard looks fine. - */ -static int -entry_guard_inc_circ_attempt_count(entry_guard_t *guard) -{ - entry_guards_changed(); - - pathbias_measure_close_rate(guard); - - if (guard->path_bias_disabled) - return -1; - - pathbias_scale_close_rates(guard); - guard->circ_attempts++; - - log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - return 0; -} - /** A "created" cell <b>reply</b> came back to us on circuit <b>circ</b>. * (The body of <b>reply</b> varies depending on what sort of handshake * this is.) @@ -2765,11 +1246,7 @@ onionskin_answer(or_circuit_t *circ, * number of endpoints that would give something away about our destination. * * If the routerlist <b>nodes</b> doesn't have enough routers - * to handle the desired path length, return as large a path length as - * is feasible, except if it's less than 2, in which case return -1. - * XXX ^^ I think this behavior is a hold-over from back when we had only a - * few relays in the network, and certainly back before guards existed. - * We should very likely get rid of it. -RD + * to handle the desired path length, return -1. */ static int new_route_len(uint8_t purpose, extend_info_t *exit, smartlist_t *nodes) @@ -2790,19 +1267,13 @@ new_route_len(uint8_t purpose, extend_info_t *exit, smartlist_t *nodes) log_debug(LD_CIRC,"Chosen route length %d (%d/%d routers suitable).", routelen, num_acceptable_routers, smartlist_len(nodes)); - if (num_acceptable_routers < 2) { + if (num_acceptable_routers < routelen) { log_info(LD_CIRC, - "Not enough acceptable routers (%d). Discarding this circuit.", - num_acceptable_routers); + "Not enough acceptable routers (%d/%d). Discarding this circuit.", + num_acceptable_routers, routelen); return -1; } - if (num_acceptable_routers < routelen) { - log_info(LD_CIRC,"Not enough routers: cutting routelen from %d to %d.", - routelen, num_acceptable_routers); - routelen = num_acceptable_routers; - } - return routelen; } diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index a3091707e..ebcb22c45 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -57,16 +57,6 @@ const char *build_state_get_exit_nickname(cpath_build_state_t *state); const node_t *choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state); -double pathbias_get_extreme_rate(const or_options_t *options); -double pathbias_get_extreme_use_rate(const or_options_t *options); -int pathbias_get_dropguards(const or_options_t *options); -void pathbias_count_timeout(origin_circuit_t *circ); -int pathbias_check_close(origin_circuit_t *circ, int reason); -int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); -void pathbias_count_use_attempt(origin_circuit_t *circ); -void pathbias_mark_use_success(origin_circuit_t *circ); -void pathbias_mark_use_rollback(origin_circuit_t *circ); -const char *pathbias_state_to_string(path_state_t state); #endif diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index b0e24a5fe..c31bc49d0 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -8,9 +8,10 @@ * \file circuitlist.c * \brief Manage the global circuit list. **/ - +#define CIRCUITLIST_PRIVATE #include "or.h" #include "channel.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -36,12 +37,12 @@ /********* START VARIABLES **********/ /** A global list of all circuits at this hop. */ -circuit_t *global_circuitlist=NULL; +struct global_circuitlist_s global_circuitlist = + TOR_LIST_HEAD_INITIALIZER(global_circuitlist); /** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */ static smartlist_t *circuits_pending_chans = NULL; -static void circuit_free(circuit_t *circ); static void circuit_free_cpath(crypt_path_t *cpath); static void circuit_free_cpath_node(crypt_path_t *victim); static void cpath_ref_decref(crypt_path_reference_t *cpath_ref); @@ -207,18 +208,123 @@ circuit_set_circid_chan_helper(circuit_t *circ, int direction, } } +/** Mark that circuit id <b>id</b> shouldn't be used on channel <b>chan</b>, + * even if there is no circuit on the channel. We use this to keep the + * circuit id from getting re-used while we have queued but not yet sent + * a destroy cell. */ +void +channel_mark_circid_unusable(channel_t *chan, circid_t id) +{ + chan_circid_circuit_map_t search; + chan_circid_circuit_map_t *ent; + + /* See if there's an entry there. That wouldn't be good. */ + memset(&search, 0, sizeof(search)); + search.chan = chan; + search.circ_id = id; + ent = HT_FIND(chan_circid_map, &chan_circid_map, &search); + + if (ent && ent->circuit) { + /* we have a problem. */ + log_warn(LD_BUG, "Tried to mark %u unusable on %p, but there was already " + "a circuit there.", (unsigned)id, chan); + } else if (ent) { + /* It's already marked. */ + } else { + ent = tor_malloc_zero(sizeof(chan_circid_circuit_map_t)); + ent->chan = chan; + ent->circ_id = id; + /* leave circuit at NULL */ + HT_INSERT(chan_circid_map, &chan_circid_map, ent); + } +} + +/** Mark that a circuit id <b>id</b> can be used again on <b>chan</b>. + * We use this to re-enable the circuit ID after we've sent a destroy cell. + */ +void +channel_mark_circid_usable(channel_t *chan, circid_t id) +{ + chan_circid_circuit_map_t search; + chan_circid_circuit_map_t *ent; + + /* See if there's an entry there. That wouldn't be good. */ + memset(&search, 0, sizeof(search)); + search.chan = chan; + search.circ_id = id; + ent = HT_REMOVE(chan_circid_map, &chan_circid_map, &search); + if (ent && ent->circuit) { + log_warn(LD_BUG, "Tried to mark %u usable on %p, but there was already " + "a circuit there.", (unsigned)id, chan); + return; + } + if (_last_circid_chan_ent == ent) + _last_circid_chan_ent = NULL; + tor_free(ent); +} + +/** Called to indicate that a DESTROY is pending on <b>chan</b> with + * circuit ID <b>id</b>, but hasn't been sent yet. */ +void +channel_note_destroy_pending(channel_t *chan, circid_t id) +{ + circuit_t *circ = circuit_get_by_circid_channel_even_if_marked(id,chan); + if (circ) { + if (circ->n_chan == chan && circ->n_circ_id == id) { + circ->n_delete_pending = 1; + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + if (orcirc->p_chan == chan && orcirc->p_circ_id == id) { + circ->p_delete_pending = 1; + } + } + return; + } + channel_mark_circid_unusable(chan, 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. */ +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) { + if (circ->n_chan == chan && circ->n_circ_id == id) { + circ->n_delete_pending = 0; + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + if (orcirc->p_chan == chan && orcirc->p_circ_id == id) { + circ->p_delete_pending = 0; + } + } + /* XXXX this shouldn't happen; log a bug here. */ + return; + } + channel_mark_circid_usable(chan, id); +} + /** Set the p_conn field of a circuit <b>circ</b>, along * with the corresponding circuit ID, and add the circuit as appropriate * to the (chan,id)-\>circuit map. */ void -circuit_set_p_circid_chan(or_circuit_t *circ, circid_t id, +circuit_set_p_circid_chan(or_circuit_t *or_circ, circid_t id, channel_t *chan) { - circuit_set_circid_chan_helper(TO_CIRCUIT(circ), CELL_DIRECTION_IN, - id, chan); + circuit_t *circ = TO_CIRCUIT(or_circ); + channel_t *old_chan = or_circ->p_chan; + circid_t old_id = or_circ->p_circ_id; + + circuit_set_circid_chan_helper(circ, CELL_DIRECTION_IN, id, chan); if (chan) - tor_assert(bool_eq(circ->p_chan_cells.n, circ->next_active_on_p_chan)); + tor_assert(bool_eq(or_circ->p_chan_cells.n, + or_circ->next_active_on_p_chan)); + + if (circ->p_delete_pending && old_chan) { + channel_mark_circid_unusable(old_chan, old_id); + circ->p_delete_pending = 0; + } } /** Set the n_conn field of a circuit <b>circ</b>, along @@ -228,10 +334,18 @@ void circuit_set_n_circid_chan(circuit_t *circ, circid_t id, channel_t *chan) { + channel_t *old_chan = circ->n_chan; + circid_t old_id = circ->n_circ_id; + circuit_set_circid_chan_helper(circ, CELL_DIRECTION_OUT, id, chan); if (chan) tor_assert(bool_eq(circ->n_chan_cells.n, circ->next_active_on_n_chan)); + + if (circ->n_delete_pending && old_chan) { + channel_mark_circid_unusable(old_chan, old_id); + circ->n_delete_pending = 0; + } } /** Change the state of <b>circ</b> to <b>state</b>, adding it to or removing @@ -257,21 +371,6 @@ circuit_set_state(circuit_t *circ, uint8_t state) circ->state = state; } -/** Add <b>circ</b> to the global list of circuits. This is called only from - * within circuit_new. - */ -static void -circuit_add(circuit_t *circ) -{ - if (!global_circuitlist) { /* first one */ - global_circuitlist = circ; - circ->next = NULL; - } else { - circ->next = global_circuitlist; - global_circuitlist = circ; - } -} - /** Append to <b>out</b> all circuits in state CHAN_WAIT waiting for * the given connection. */ void @@ -329,33 +428,17 @@ circuit_count_pending_on_channel(channel_t *chan) void circuit_close_all_marked(void) { - circuit_t *tmp,*m; - - while (global_circuitlist && global_circuitlist->marked_for_close) { - tmp = global_circuitlist->next; - circuit_free(global_circuitlist); - global_circuitlist = tmp; - } - - tmp = global_circuitlist; - while (tmp && tmp->next) { - if (tmp->next->marked_for_close) { - m = tmp->next->next; - circuit_free(tmp->next); - tmp->next = m; - /* Need to check new tmp->next; don't advance tmp. */ - } else { - /* Advance tmp. */ - tmp = tmp->next; - } - } + circuit_t *circ, *tmp; + TOR_LIST_FOREACH_SAFE(circ, &global_circuitlist, head, tmp) + if (circ->marked_for_close) + circuit_free(circ); } /** Return the head of the global linked list of circuits. */ -circuit_t * -circuit_get_global_list_(void) +struct global_circuitlist_s * +circuit_get_global_list(void) { - return global_circuitlist; + return &global_circuitlist; } /** Function to make circ-\>state human-readable */ @@ -570,8 +653,9 @@ init_circuit_base(circuit_t *circ) circ->package_window = circuit_initial_package_window(); circ->deliver_window = CIRCWINDOW_START; + cell_queue_init(&circ->n_chan_cells); - circuit_add(circ); + TOR_LIST_INSERT_HEAD(&global_circuitlist, circ, head); } /** Allocate space for a new circuit, initializing with <b>p_circ_id</b> @@ -595,7 +679,7 @@ origin_circuit_new(void) init_circuit_base(TO_CIRCUIT(circ)); - circ_times.last_circ_at = approx_time(); + circuit_build_times_update_last_circ(get_circuit_build_times_mutable()); return circ; } @@ -615,6 +699,7 @@ or_circuit_new(circid_t p_circ_id, channel_t *p_chan) circuit_set_p_circid_chan(circ, p_circ_id, p_chan); circ->remaining_relay_early_cells = MAX_RELAY_EARLY_CELLS_PER_CIRCUIT; + cell_queue_init(&circ->p_chan_cells); init_circuit_base(TO_CIRCUIT(circ)); @@ -623,7 +708,7 @@ or_circuit_new(circid_t p_circ_id, channel_t *p_chan) /** Deallocate space associated with circ. */ -static void +STATIC void circuit_free(circuit_t *circ) { void *mem; @@ -689,6 +774,8 @@ circuit_free(circuit_t *circ) extend_info_free(circ->n_hop); tor_free(circ->n_chan_create_cell); + TOR_LIST_REMOVE(circ, head); + /* Remove from map. */ circuit_set_n_circid_chan(circ, 0, NULL); @@ -724,11 +811,11 @@ circuit_free_cpath(crypt_path_t *cpath) void circuit_free_all(void) { - circuit_t *next; - while (global_circuitlist) { - next = global_circuitlist->next; - if (! CIRCUIT_IS_ORIGIN(global_circuitlist)) { - or_circuit_t *or_circ = TO_OR_CIRCUIT(global_circuitlist); + circuit_t *tmp, *tmp2; + + TOR_LIST_FOREACH_SAFE(tmp, &global_circuitlist, head, tmp2) { + if (! CIRCUIT_IS_ORIGIN(tmp)) { + or_circuit_t *or_circ = TO_OR_CIRCUIT(tmp); while (or_circ->resolving_streams) { edge_connection_t *next_conn; next_conn = or_circ->resolving_streams->next_stream; @@ -736,8 +823,7 @@ circuit_free_all(void) or_circ->resolving_streams = next_conn; } } - circuit_free(global_circuitlist); - global_circuitlist = next; + circuit_free(tmp); } smartlist_free(circuits_pending_chans); @@ -807,7 +893,7 @@ circuit_dump_by_conn(connection_t *conn, int severity) circuit_t *circ; edge_connection_t *tmpconn; - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { circid_t n_circ_id = circ->n_circ_id, p_circ_id = 0; if (circ->marked_for_close) { @@ -871,7 +957,7 @@ circuit_dump_by_chan(channel_t *chan, int severity) tor_assert(chan); - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { circid_t n_circ_id = circ->n_circ_id, p_circ_id = 0; if (circ->marked_for_close) { @@ -912,7 +998,7 @@ origin_circuit_t * circuit_get_by_global_id(uint32_t id) { circuit_t *circ; - for (circ=global_circuitlist;circ;circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { if (CIRCUIT_IS_ORIGIN(circ) && TO_ORIGIN_CIRCUIT(circ)->global_identifier == id) { if (circ->marked_for_close) @@ -928,9 +1014,13 @@ circuit_get_by_global_id(uint32_t id) * - circ-\>n_circ_id or circ-\>p_circ_id is equal to <b>circ_id</b>, and * - circ is attached to <b>chan</b>, either as p_chan or n_chan. * Return NULL if no such circuit exists. + * + * If <b>found_entry_out</b> is provided, set it to true if we have a + * placeholder entry for circid/chan, and leave it unset otherwise. */ static INLINE circuit_t * -circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan) +circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan, + int *found_entry_out) { chan_circid_circuit_map_t search; chan_circid_circuit_map_t *found; @@ -951,21 +1041,27 @@ circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan) " circ_id %u, channel ID " U64_FORMAT " (%p)", found->circuit, (unsigned)circ_id, U64_PRINTF_ARG(chan->global_identifier), chan); + if (found_entry_out) + *found_entry_out = 1; return found->circuit; } log_debug(LD_CIRC, - "circuit_get_by_circid_channel_impl() found nothing for" + "circuit_get_by_circid_channel_impl() found %s for" " circ_id %u, channel ID " U64_FORMAT " (%p)", + found ? "placeholder" : "nothing", (unsigned)circ_id, U64_PRINTF_ARG(chan->global_identifier), chan); + if (found_entry_out) + *found_entry_out = found ? 1 : 0; + return NULL; /* The rest of this checks for bugs. Disabled by default. */ /* We comment it out because coverity complains otherwise. { circuit_t *circ; - for (circ=global_circuitlist;circ;circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { if (! CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); if (or_circ->p_chan == chan && or_circ->p_circ_id == circ_id) { @@ -993,7 +1089,7 @@ circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan) circuit_t * circuit_get_by_circid_channel(circid_t circ_id, channel_t *chan) { - circuit_t *circ = circuit_get_by_circid_channel_impl(circ_id, chan); + circuit_t *circ = circuit_get_by_circid_channel_impl(circ_id, chan, NULL); if (!circ || circ->marked_for_close) return NULL; else @@ -1009,7 +1105,7 @@ circuit_t * circuit_get_by_circid_channel_even_if_marked(circid_t circ_id, channel_t *chan) { - return circuit_get_by_circid_channel_impl(circ_id, chan); + return circuit_get_by_circid_channel_impl(circ_id, chan, NULL); } /** Return true iff the circuit ID <b>circ_id</b> is currently used by a @@ -1017,7 +1113,9 @@ circuit_get_by_circid_channel_even_if_marked(circid_t circ_id, int circuit_id_in_use_on_channel(circid_t circ_id, channel_t *chan) { - return circuit_get_by_circid_channel_impl(circ_id, chan) != NULL; + int found = 0; + return circuit_get_by_circid_channel_impl(circ_id, chan, &found) != NULL + || found; } /** Return the circuit that a given edge connection is using. */ @@ -1045,7 +1143,7 @@ circuit_unlink_all_from_channel(channel_t *chan, int reason) channel_unlink_all_circuits(chan); - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { int mark = 0; if (circ->n_chan == chan) { circuit_set_n_circid_chan(circ, 0, NULL); @@ -1081,8 +1179,7 @@ origin_circuit_t * circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data) { circuit_t *circ; - - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { if (!circ->marked_for_close && circ->purpose == CIRCUIT_PURPOSE_C_REND_READY) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); @@ -1110,11 +1207,11 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, circuit_t *circ; tor_assert(CIRCUIT_PURPOSE_IS_ORIGIN(purpose)); if (start == NULL) - circ = global_circuitlist; + circ = TOR_LIST_FIRST(&global_circuitlist); else - circ = TO_CIRCUIT(start)->next; + circ = TOR_LIST_NEXT(TO_CIRCUIT(start), head); - for ( ; circ; circ = circ->next) { + for ( ; circ; circ = TOR_LIST_NEXT(circ, head)) { if (circ->marked_for_close) continue; if (circ->purpose != purpose) @@ -1137,7 +1234,7 @@ circuit_get_by_rend_token_and_purpose(uint8_t purpose, const char *token, size_t len) { circuit_t *circ; - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { if (! circ->marked_for_close && circ->purpose == purpose && tor_memeq(TO_OR_CIRCUIT(circ)->rend_token, token, len)) @@ -1199,7 +1296,7 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, "capacity %d, internal %d", purpose, need_uptime, need_capacity, internal); - for (circ_=global_circuitlist; circ_; circ_ = circ_->next) { + TOR_LIST_FOREACH(circ_, &global_circuitlist, head) { if (CIRCUIT_IS_ORIGIN(circ_) && circ_->state == CIRCUIT_STATE_OPEN && !circ_->marked_for_close && @@ -1289,8 +1386,7 @@ void circuit_mark_all_unused_circs(void) { circuit_t *circ; - - for (circ=global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { if (CIRCUIT_IS_ORIGIN(circ) && !circ->marked_for_close && !circ->timestamp_dirty) @@ -1309,8 +1405,7 @@ void circuit_mark_all_dirty_circs_as_unusable(void) { circuit_t *circ; - - for (circ=global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { if (CIRCUIT_IS_ORIGIN(circ) && !circ->marked_for_close && circ->timestamp_dirty) { @@ -1514,7 +1609,7 @@ marked_circuit_free_cells(circuit_t *circ) } /** Return the number of cells used by the circuit <b>c</b>'s cell queues. */ -static size_t +STATIC size_t n_cells_in_circ_queues(const circuit_t *c) { size_t n = c->n_chan_cells.n; @@ -1537,13 +1632,15 @@ static uint32_t circuit_max_queued_cell_age(const circuit_t *c, uint32_t now) { uint32_t age = 0; - if (c->n_chan_cells.head) - age = now - c->n_chan_cells.head->inserted_time; + packed_cell_t *cell; + + if (NULL != (cell = TOR_SIMPLEQ_FIRST(&c->n_chan_cells.head))) + age = now - cell->inserted_time; if (! CIRCUIT_IS_ORIGIN(c)) { const or_circuit_t *orcirc = TO_OR_CIRCUIT((circuit_t*)c); - if (orcirc->p_chan_cells.head) { - uint32_t age2 = now - orcirc->p_chan_cells.head->inserted_time; + if (NULL != (cell = TOR_SIMPLEQ_FIRST(&orcirc->p_chan_cells.head))) { + uint32_t age2 = now - cell->inserted_time; if (age2 > age) return age2; } @@ -1604,7 +1701,7 @@ circuits_handle_oom(size_t current_allocation) /* This algorithm itself assumes that you've got enough memory slack * to actually run it. */ - for (circ = global_circuitlist; circ; circ = circ->next) + TOR_LIST_FOREACH(circ, &global_circuitlist, head) smartlist_add(circlist, circ); /* Set circcomp_now_tmp so that the sort can work. */ @@ -1725,15 +1822,16 @@ assert_circuit_ok(const circuit_t *c) /* We use the _impl variant here to make sure we don't fail on marked * circuits, which would not be returned by the regular function. */ circuit_t *c2 = circuit_get_by_circid_channel_impl(c->n_circ_id, - c->n_chan); + c->n_chan, NULL); tor_assert(c == c2); } } if (or_circ && or_circ->p_chan) { if (or_circ->p_circ_id) { /* ibid */ - circuit_t *c2 = circuit_get_by_circid_channel_impl(or_circ->p_circ_id, - or_circ->p_chan); + circuit_t *c2 = + circuit_get_by_circid_channel_impl(or_circ->p_circ_id, + or_circ->p_chan, NULL); tor_assert(c == c2); } } diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 874f68cd2..bf3d1b467 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -12,7 +12,11 @@ #ifndef TOR_CIRCUITLIST_H #define TOR_CIRCUITLIST_H -circuit_t * circuit_get_global_list_(void); +#include "testsupport.h" + +TOR_LIST_HEAD(global_circuitlist_s, circuit_t); + +struct global_circuitlist_s* circuit_get_global_list(void); const char *circuit_state_to_string(int state); const char *circuit_purpose_to_controller_string(uint8_t purpose); const char *circuit_purpose_to_controller_hs_state_string(uint8_t purpose); @@ -23,6 +27,8 @@ void circuit_set_p_circid_chan(or_circuit_t *circ, circid_t id, channel_t *chan); void circuit_set_n_circid_chan(circuit_t *circ, circid_t id, channel_t *chan); +void channel_mark_circid_unusable(channel_t *chan, circid_t id); +void channel_mark_circid_usable(channel_t *chan, circid_t id); void circuit_set_state(circuit_t *circ, uint8_t state); void circuit_close_all_marked(void); int32_t circuit_initial_package_window(void); @@ -63,5 +69,13 @@ void assert_circuit_ok(const circuit_t *c); void circuit_free_all(void); void circuits_handle_oom(size_t current_allocation); +void channel_note_destroy_pending(channel_t *chan, circid_t id); +void channel_note_destroy_not_pending(channel_t *chan, circid_t id); + +#ifdef CIRCUITLIST_PRIVATE +STATIC void circuit_free(circuit_t *circ); +STATIC size_t n_cells_in_circ_queues(const circuit_t *c); +#endif + #endif diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 545cfd065..f2af94393 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -10,6 +10,7 @@ #include "channel.h" #include "circuitlist.h" #include "circuitmux.h" +#include "relay.h" /* * Private typedefs for circuitmux.c @@ -115,6 +116,22 @@ struct circuitmux_s { */ struct circuit_t *active_circuits_head, *active_circuits_tail; + /** List of queued destroy cells */ + cell_queue_t destroy_cell_queue; + /** Boolean: True iff the last cell to circuitmux_get_first_active_circuit + * returned the destroy queue. Used to force alternation between + * destroy/non-destroy cells. + * + * XXXX There is no reason to think that alternating is a particularly good + * approach -- it's just designed to prevent destroys from starving other + * cells completely. + */ + unsigned int last_cell_was_destroy : 1; + /** Destroy counter: increment this when a destroy gets queued, decrement + * when we unqueue it, so we can test to make sure they don't starve. + */ + int64_t destroy_ctr; + /* * Circuitmux policy; if this is non-NULL, it can override the built- * in round-robin active circuits behavior. This is how EWMA works in @@ -193,6 +210,11 @@ static void circuitmux_assert_okay_pass_one(circuitmux_t *cmux); static void circuitmux_assert_okay_pass_two(circuitmux_t *cmux); static void circuitmux_assert_okay_pass_three(circuitmux_t *cmux); +/* Static global variables */ + +/** Count the destroy balance to debug destroy queue logic */ +static int64_t global_destroy_ctr = 0; + /* Function definitions */ /** @@ -361,6 +383,7 @@ circuitmux_alloc(void) rv = tor_malloc_zero(sizeof(*rv)); rv->chanid_circid_map = tor_malloc_zero(sizeof(*( rv->chanid_circid_map))); HT_INIT(chanid_circid_muxinfo_map, rv->chanid_circid_map); + cell_queue_init(&rv->destroy_cell_queue); return rv; } @@ -476,6 +499,31 @@ circuitmux_detach_all_circuits(circuitmux_t *cmux) cmux->n_cells = 0; } +/** Reclaim all circuit IDs currently marked as unusable on <b>chan</b> because + * of pending destroy cells in <b>cmux</b>. + * + * This function must be called AFTER circuits are unlinked from the (channel, + * circuid-id) map with circuit_unlink_all_from_channel(), but before calling + * circuitmux_free(). + */ +void +circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, channel_t *chan) +{ + packed_cell_t *cell; + int n_bad = 0; + TOR_SIMPLEQ_FOREACH(cell, &cmux->destroy_cell_queue.head, next) { + circid_t circid = 0; + if (packed_cell_is_destroy(chan, cell, &circid)) { + channel_mark_circid_usable(chan, circid); + } else { + ++n_bad; + } + } + if (n_bad) + log_warn(LD_BUG, "%d cell(s) on destroy queue did not look like a " + "DESTROY cell.", n_bad); +} + /** * Free a circuitmux_t; the circuits must be detached first with * circuitmux_detach_all_circuits(). @@ -508,6 +556,30 @@ circuitmux_free(circuitmux_t *cmux) tor_free(cmux->chanid_circid_map); } + /* + * We're throwing away some destroys; log the counter and + * adjust the global counter by the queue size. + */ + if (cmux->destroy_cell_queue.n > 0) { + cmux->destroy_ctr -= cmux->destroy_cell_queue.n; + global_destroy_ctr -= cmux->destroy_cell_queue.n; + log_debug(LD_CIRC, + "Freeing cmux at %p with %u queued destroys; the last cmux " + "destroy balance was "I64_FORMAT", global is "I64_FORMAT, + cmux, cmux->destroy_cell_queue.n, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); + } else { + log_debug(LD_CIRC, + "Freeing cmux at %p with no queued destroys, the cmux destroy " + "balance was "I64_FORMAT", global is "I64_FORMAT, + cmux, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); + } + + cell_queue_clear(&cmux->destroy_cell_queue); + tor_free(cmux); } @@ -816,7 +888,7 @@ circuitmux_num_cells(circuitmux_t *cmux) { tor_assert(cmux); - return cmux->n_cells; + return cmux->n_cells + cmux->destroy_cell_queue.n; } /** @@ -851,9 +923,9 @@ circuitmux_num_circuits(circuitmux_t *cmux) * Attach a circuit to a circuitmux, for the specified direction. */ -void -circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, - cell_direction_t direction) +MOCK_IMPL(void, +circuitmux_attach_circuit,(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction)) { channel_t *chan = NULL; uint64_t channel_id; @@ -1000,8 +1072,8 @@ circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, * no-op if not attached. */ -void -circuitmux_detach_circuit(circuitmux_t *cmux, circuit_t *circ) +MOCK_IMPL(void, +circuitmux_detach_circuit,(circuitmux_t *cmux, circuit_t *circ)) { chanid_circid_muxinfo_t search, *hashent = NULL; /* @@ -1368,16 +1440,36 @@ circuitmux_set_num_cells(circuitmux_t *cmux, circuit_t *circ, /** * Pick a circuit to send from, using the active circuits list or a * circuitmux policy if one is available. This is called from channel.c. + * + * If we would rather send a destroy cell, return NULL and set + * *<b>destroy_queue_out</b> to the destroy queue. + * + * If we have nothing to send, set *<b>destroy_queue_out</b> to NULL and + * return NULL. */ circuit_t * -circuitmux_get_first_active_circuit(circuitmux_t *cmux) +circuitmux_get_first_active_circuit(circuitmux_t *cmux, + cell_queue_t **destroy_queue_out) { circuit_t *circ = NULL; tor_assert(cmux); + tor_assert(destroy_queue_out); + + *destroy_queue_out = NULL; + + if (cmux->destroy_cell_queue.n && + (!cmux->last_cell_was_destroy || cmux->n_active_circuits == 0)) { + /* We have destroy cells to send, and either we just sent a relay cell, + * or we have no relay cells to send. */ + + /* XXXX We should let the cmux policy have some say in this eventually. */ + /* XXXX Alternating is not a terribly brilliant approach here. */ + *destroy_queue_out = &cmux->destroy_cell_queue; - if (cmux->n_active_circuits > 0) { + cmux->last_cell_was_destroy = 1; + } else if (cmux->n_active_circuits > 0) { /* We also must have a cell available for this to be the case */ tor_assert(cmux->n_cells > 0); /* Do we have a policy-provided circuit selector? */ @@ -1389,7 +1481,11 @@ circuitmux_get_first_active_circuit(circuitmux_t *cmux) tor_assert(cmux->active_circuits_head); circ = cmux->active_circuits_head; } - } else tor_assert(cmux->n_cells == 0); + cmux->last_cell_was_destroy = 0; + } else { + tor_assert(cmux->n_cells == 0); + tor_assert(cmux->destroy_cell_queue.n == 0); + } return circ; } @@ -1463,6 +1559,26 @@ circuitmux_notify_xmit_cells(circuitmux_t *cmux, circuit_t *circ, circuitmux_assert_okay_paranoid(cmux); } +/** + * Notify the circuitmux that a destroy was sent, so we can update + * the counter. + */ + +void +circuitmux_notify_xmit_destroy(circuitmux_t *cmux) +{ + tor_assert(cmux); + + --(cmux->destroy_ctr); + --(global_destroy_ctr); + log_debug(LD_CIRC, + "Cmux at %p sent a destroy, cmux counter is now "I64_FORMAT", " + "global counter is now "I64_FORMAT, + cmux, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); +} + /* * Circuitmux consistency checking assertions */ @@ -1743,3 +1859,40 @@ circuitmux_assert_okay_pass_three(circuitmux_t *cmux) } } +/*DOCDOC */ +void +circuitmux_append_destroy_cell(channel_t *chan, + circuitmux_t *cmux, + circid_t circ_id, + uint8_t reason) +{ + cell_t cell; + memset(&cell, 0, sizeof(cell_t)); + cell.circ_id = circ_id; + cell.command = CELL_DESTROY; + cell.payload[0] = (uint8_t) reason; + + cell_queue_append_packed_copy(NULL, &cmux->destroy_cell_queue, 0, &cell, + chan->wide_circ_ids, 0); + + /* Destroy entering the queue, update counters */ + ++(cmux->destroy_ctr); + ++global_destroy_ctr; + log_debug(LD_CIRC, + "Cmux at %p queued a destroy for circ %u, cmux counter is now " + I64_FORMAT", global counter is now "I64_FORMAT, + cmux, circ_id, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); + + /* XXXX Duplicate code from append_cell_to_circuit_queue */ + if (!channel_has_queued_writes(chan)) { + /* There is no data at all waiting to be sent on the outbuf. Add a + * cell, so that we can notice when it gets flushed, flushed_some can + * get called, and we can start putting more data onto the buffer then. + */ + log_debug(LD_GENERAL, "Primed a buffer."); + channel_flush_from_first_active_circuit(chan, 1); + } +} + diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index 25644ffab..ee2f5d153 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -10,6 +10,7 @@ #define TOR_CIRCUITMUX_H #include "or.h" +#include "testsupport.h" typedef struct circuitmux_policy_s circuitmux_policy_t; typedef struct circuitmux_policy_data_s circuitmux_policy_data_t; @@ -120,17 +121,27 @@ unsigned int circuitmux_num_circuits(circuitmux_t *cmux); unsigned int circuitmux_num_active_circuits(circuitmux_t *cmux); /* Channel interface */ -circuit_t * circuitmux_get_first_active_circuit(circuitmux_t *cmux); +circuit_t * circuitmux_get_first_active_circuit(circuitmux_t *cmux, + cell_queue_t **destroy_queue_out); void circuitmux_notify_xmit_cells(circuitmux_t *cmux, circuit_t *circ, unsigned int n_cells); +void circuitmux_notify_xmit_destroy(circuitmux_t *cmux); /* Circuit interface */ -void circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, - cell_direction_t direction); -void circuitmux_detach_circuit(circuitmux_t *cmux, circuit_t *circ); +MOCK_DECL(void, circuitmux_attach_circuit, (circuitmux_t *cmux, + circuit_t *circ, + cell_direction_t direction)); +MOCK_DECL(void, circuitmux_detach_circuit, + (circuitmux_t *cmux, circuit_t *circ)); void circuitmux_clear_num_cells(circuitmux_t *cmux, circuit_t *circ); void circuitmux_set_num_cells(circuitmux_t *cmux, circuit_t *circ, unsigned int n_cells); +void circuitmux_append_destroy_cell(channel_t *chan, + circuitmux_t *cmux, circid_t circ_id, + uint8_t reason); +void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, + channel_t *chan); + #endif /* TOR_CIRCUITMUX_H */ diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 1d7812bf2..eaefc9edd 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -18,6 +18,10 @@ #undef log #include <math.h> +static void cbt_control_event_buildtimeout_set( + const circuit_build_times_t *cbt, + buildtimeout_set_event_t type); + #define CBT_BIN_TO_MS(bin) ((bin)*CBT_BIN_WIDTH + (CBT_BIN_WIDTH/2)) /** Global list of circuit build times */ @@ -26,12 +30,46 @@ // vary in their own latency. The downside of this is that guards // can change frequently, so we'd be building a lot more circuits // most likely. -/* XXXX024 Make this static; add accessor functions. */ -circuit_build_times_t circ_times; +static circuit_build_times_t circ_times; +#ifdef TOR_UNIT_TESTS /** If set, we're running the unit tests: we should avoid clobbering * our state file or accessing get_options() or get_or_state() */ static int unit_tests = 0; +#else +#define unit_tests 0 +#endif + +/** Return a pointer to the data structure describing our current circuit + * build time history and computations. */ +const circuit_build_times_t * +get_circuit_build_times(void) +{ + return &circ_times; +} + +/** As get_circuit_build_times, but return a mutable pointer. */ +circuit_build_times_t * +get_circuit_build_times_mutable(void) +{ + return &circ_times; +} + +/** Return the time to wait before actually closing an under-construction, in + * milliseconds. */ +double +get_circuit_build_close_time_ms(void) +{ + return circ_times.close_ms; +} + +/** Return the time to wait before giving up on an under-construction circuit, + * in milliseconds. */ +double +get_circuit_build_timeout_ms(void) +{ + return circ_times.timeout_ms; +} /** * This function decides if CBT learning should be disabled. It returns @@ -154,7 +192,7 @@ circuit_build_times_min_circs_to_observe(void) /** Return true iff <b>cbt</b> has recorded enough build times that we * want to start acting on the timeout it implies. */ int -circuit_build_times_enough_to_compute(circuit_build_times_t *cbt) +circuit_build_times_enough_to_compute(const circuit_build_times_t *cbt) { return cbt->total_build_times >= circuit_build_times_min_circs_to_observe(); } @@ -438,7 +476,7 @@ circuit_build_times_get_initial_timeout(void) * Leave estimated parameters, timeout and network liveness intact * for future use. */ -void +STATIC void circuit_build_times_reset(circuit_build_times_t *cbt) { memset(cbt->circuit_build_times, 0, sizeof(cbt->circuit_build_times)); @@ -471,7 +509,7 @@ circuit_build_times_init(circuit_build_times_t *cbt) cbt->liveness.timeouts_after_firsthop = NULL; } cbt->close_ms = cbt->timeout_ms = circuit_build_times_get_initial_timeout(); - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); + cbt_control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); } /** @@ -557,7 +595,7 @@ circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time) * Return maximum circuit build time */ static build_time_t -circuit_build_times_max(circuit_build_times_t *cbt) +circuit_build_times_max(const circuit_build_times_t *cbt) { int i = 0; build_time_t max_build_time = 0; @@ -598,7 +636,7 @@ circuit_build_times_min(circuit_build_times_t *cbt) * The return value must be freed by the caller. */ static uint32_t * -circuit_build_times_create_histogram(circuit_build_times_t *cbt, +circuit_build_times_create_histogram(const circuit_build_times_t *cbt, build_time_t *nbins) { uint32_t *histogram; @@ -688,7 +726,7 @@ circuit_build_times_get_xm(circuit_build_times_t *cbt) * the or_state_t state structure. */ void -circuit_build_times_update_state(circuit_build_times_t *cbt, +circuit_build_times_update_state(const circuit_build_times_t *cbt, or_state_t *state) { uint32_t *histogram; @@ -949,7 +987,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, * an acceptable approximation because we are only concerned with the * accuracy of the CDF of the tail. */ -int +STATIC int circuit_build_times_update_alpha(circuit_build_times_t *cbt) { build_time_t *x=cbt->circuit_build_times; @@ -1033,7 +1071,7 @@ circuit_build_times_update_alpha(circuit_build_times_t *cbt) * * Return value is in milliseconds. */ -double +STATIC double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, double quantile) { @@ -1050,6 +1088,7 @@ circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, return ret; } +#ifdef TOR_UNIT_TESTS /** Pareto CDF */ double circuit_build_times_cdf(circuit_build_times_t *cbt, double x) @@ -1060,7 +1099,9 @@ circuit_build_times_cdf(circuit_build_times_t *cbt, double x) tor_assert(0 <= ret && ret <= 1.0); return ret; } +#endif +#ifdef TOR_UNIT_TESTS /** * Generate a synthetic time using our distribution parameters. * @@ -1093,7 +1134,9 @@ circuit_build_times_generate_sample(circuit_build_times_t *cbt, tor_assert(ret > 0); return ret; } +#endif +#ifdef TOR_UNIT_TESTS /** * Estimate an initial alpha parameter by solving the quantile * function with a quantile point and a specific timeout value. @@ -1114,12 +1157,13 @@ circuit_build_times_initial_alpha(circuit_build_times_t *cbt, (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms)); tor_assert(cbt->alpha > 0); } +#endif /** * Returns true if we need circuits to be built */ int -circuit_build_times_needs_circuits(circuit_build_times_t *cbt) +circuit_build_times_needs_circuits(const circuit_build_times_t *cbt) { /* Return true if < MIN_CIRCUITS_TO_OBSERVE */ return !circuit_build_times_enough_to_compute(cbt); @@ -1130,7 +1174,7 @@ circuit_build_times_needs_circuits(circuit_build_times_t *cbt) * right now. */ int -circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt) +circuit_build_times_needs_circuits_now(const circuit_build_times_t *cbt) { return circuit_build_times_needs_circuits(cbt) && approx_time()-cbt->last_circ_at > circuit_build_times_test_frequency(); @@ -1263,7 +1307,7 @@ circuit_build_times_network_close(circuit_build_times_t *cbt, * in the case of recent liveness changes. */ int -circuit_build_times_network_check_live(circuit_build_times_t *cbt) +circuit_build_times_network_check_live(const circuit_build_times_t *cbt) { if (cbt->liveness.nonlive_timeouts > 0) { return 0; @@ -1282,7 +1326,7 @@ circuit_build_times_network_check_live(circuit_build_times_t *cbt) * to restart the process of building test circuits and estimating a * new timeout. */ -int +STATIC int circuit_build_times_network_check_changed(circuit_build_times_t *cbt) { int total_build_times = cbt->total_build_times; @@ -1329,7 +1373,7 @@ circuit_build_times_network_check_changed(circuit_build_times_t *cbt) = circuit_build_times_get_initial_timeout(); } - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); + cbt_control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); log_notice(LD_CIRC, "Your network connection speed appears to have changed. Resetting " @@ -1511,7 +1555,7 @@ circuit_build_times_set_timeout(circuit_build_times_t *cbt) } } - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_COMPUTED); + cbt_control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_COMPUTED); timeout_rate = circuit_build_times_timeout_rate(cbt); @@ -1546,6 +1590,8 @@ circuit_build_times_set_timeout(circuit_build_times_t *cbt) cbt->total_build_times); } } + +#ifdef TOR_UNIT_TESTS /** Make a note that we're running unit tests (rather than running Tor * itself), so we avoid clobbering our state file. */ void @@ -1553,4 +1599,46 @@ circuitbuild_running_unit_tests(void) { unit_tests = 1; } +#endif + +void +circuit_build_times_update_last_circ(circuit_build_times_t *cbt) +{ + cbt->last_circ_at = approx_time(); +} + +static void +cbt_control_event_buildtimeout_set(const circuit_build_times_t *cbt, + buildtimeout_set_event_t type) +{ + char *args = NULL; + double qnt; + + switch (type) { + case BUILDTIMEOUT_SET_EVENT_RESET: + case BUILDTIMEOUT_SET_EVENT_SUSPENDED: + case BUILDTIMEOUT_SET_EVENT_DISCARD: + qnt = 1.0; + break; + case BUILDTIMEOUT_SET_EVENT_COMPUTED: + case BUILDTIMEOUT_SET_EVENT_RESUME: + default: + qnt = circuit_build_times_quantile_cutoff(); + break; + } + + tor_asprintf(&args, "TOTAL_TIMES=%lu " + "TIMEOUT_MS=%lu XM=%lu ALPHA=%f CUTOFF_QUANTILE=%f " + "TIMEOUT_RATE=%f CLOSE_MS=%lu CLOSE_RATE=%f", + (unsigned long)cbt->total_build_times, + (unsigned long)cbt->timeout_ms, + (unsigned long)cbt->Xm, cbt->alpha, qnt, + circuit_build_times_timeout_rate(cbt), + (unsigned long)cbt->close_ms, + circuit_build_times_close_rate(cbt)); + + control_event_buildtimeout_set(type, args); + + tor_free(args); +} diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h index 87dce99f4..3343310b8 100644 --- a/src/or/circuitstats.h +++ b/src/or/circuitstats.h @@ -12,11 +12,14 @@ #ifndef TOR_CIRCUITSTATS_H #define TOR_CIRCUITSTATS_H -extern circuit_build_times_t circ_times; +const circuit_build_times_t *get_circuit_build_times(void); +circuit_build_times_t *get_circuit_build_times_mutable(void); +double get_circuit_build_close_time_ms(void); +double get_circuit_build_timeout_ms(void); int circuit_build_times_disabled(void); -int circuit_build_times_enough_to_compute(circuit_build_times_t *cbt); -void circuit_build_times_update_state(circuit_build_times_t *cbt, +int circuit_build_times_enough_to_compute(const circuit_build_times_t *cbt); +void circuit_build_times_update_state(const circuit_build_times_t *cbt, or_state_t *state); int circuit_build_times_parse_state(circuit_build_times_t *cbt, or_state_t *state); @@ -27,9 +30,9 @@ int circuit_build_times_count_close(circuit_build_times_t *cbt, void circuit_build_times_set_timeout(circuit_build_times_t *cbt); int circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time); -int circuit_build_times_needs_circuits(circuit_build_times_t *cbt); +int circuit_build_times_needs_circuits(const circuit_build_times_t *cbt); -int circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt); +int circuit_build_times_needs_circuits_now(const circuit_build_times_t *cbt); void circuit_build_times_init(circuit_build_times_t *cbt); void circuit_build_times_free_timeouts(circuit_build_times_t *cbt); void circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, @@ -37,29 +40,59 @@ void circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, double circuit_build_times_timeout_rate(const circuit_build_times_t *cbt); double circuit_build_times_close_rate(const circuit_build_times_t *cbt); +void circuit_build_times_update_last_circ(circuit_build_times_t *cbt); + #ifdef CIRCUITSTATS_PRIVATE -double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, +STATIC double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, double quantile); +STATIC int circuit_build_times_update_alpha(circuit_build_times_t *cbt); +STATIC void circuit_build_times_reset(circuit_build_times_t *cbt); + +/* Network liveness functions */ +STATIC int circuit_build_times_network_check_changed( + circuit_build_times_t *cbt); +#endif + +#ifdef TOR_UNIT_TESTS build_time_t circuit_build_times_generate_sample(circuit_build_times_t *cbt, double q_lo, double q_hi); +double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); void circuit_build_times_initial_alpha(circuit_build_times_t *cbt, double quantile, double time_ms); -int circuit_build_times_update_alpha(circuit_build_times_t *cbt); -double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); void circuitbuild_running_unit_tests(void); -void circuit_build_times_reset(circuit_build_times_t *cbt); - -/* Network liveness functions */ -int circuit_build_times_network_check_changed(circuit_build_times_t *cbt); #endif /* Network liveness functions */ void circuit_build_times_network_is_live(circuit_build_times_t *cbt); -int circuit_build_times_network_check_live(circuit_build_times_t *cbt); +int circuit_build_times_network_check_live(const circuit_build_times_t *cbt); void circuit_build_times_network_circ_success(circuit_build_times_t *cbt); -/* DOCDOC circuit_build_times_get_bw_scale */ -int circuit_build_times_get_bw_scale(networkstatus_t *ns); +#ifdef CIRCUITSTATS_PRIVATE +/** Structure for circuit build times history */ +struct circuit_build_times_s { + /** The circular array of recorded build times in milliseconds */ + build_time_t circuit_build_times[CBT_NCIRCUITS_TO_OBSERVE]; + /** Current index in the circuit_build_times circular array */ + int build_times_idx; + /** Total number of build times accumulated. Max CBT_NCIRCUITS_TO_OBSERVE */ + int total_build_times; + /** Information about the state of our local network connection */ + network_liveness_t liveness; + /** Last time we built a circuit. Used to decide to build new test circs */ + time_t last_circ_at; + /** "Minimum" value of our pareto distribution (actually mode) */ + build_time_t Xm; + /** alpha exponent for pareto dist. */ + double alpha; + /** Have we computed a timeout? */ + int have_computed_timeout; + /** The exact value for that timeout in milliseconds. Stored as a double + * to maintain precision from calculations to and from quantile value. */ + double timeout_ms; + /** How long we wait before actually closing the circuit. */ + double close_ms; +}; +#endif #endif diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 598469198..8b82de0f9 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -12,6 +12,7 @@ #include "or.h" #include "addressmap.h" #include "channel.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" @@ -31,12 +32,6 @@ #include "router.h" #include "routerlist.h" -/********* START VARIABLES **********/ - -extern circuit_t *global_circuitlist; /* from circuitlist.c */ - -/********* END VARIABLES ************/ - static void circuit_expire_old_circuits_clientside(void); static void circuit_increment_failure_count(void); @@ -286,7 +281,7 @@ circuit_get_best(const entry_connection_t *conn, tor_gettimeofday(&now); - for (circ=global_circuitlist;circ;circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { origin_circuit_t *origin_circ; if (!CIRCUIT_IS_ORIGIN(circ)) continue; @@ -327,7 +322,7 @@ count_pending_general_client_circuits(void) int count = 0; - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { if (circ->marked_for_close || circ->state == CIRCUIT_STATE_OPEN || circ->purpose != CIRCUIT_PURPOSE_C_GENERAL || @@ -375,7 +370,7 @@ circuit_conforms_to_options(const origin_circuit_t *circ, void circuit_expire_building(void) { - circuit_t *victim, *next_circ = global_circuitlist; + circuit_t *victim, *next_circ; /* circ_times.timeout_ms and circ_times.close_ms are from * circuit_build_times_get_initial_timeout() if we haven't computed * custom timeouts yet */ @@ -393,10 +388,9 @@ circuit_expire_building(void) * we want to be more lenient with timeouts, in case the * user has relocated and/or changed network connections. * See bug #3443. */ - while (next_circ) { + TOR_LIST_FOREACH(next_circ, circuit_get_global_list(), head) { if (!CIRCUIT_IS_ORIGIN(next_circ) || /* didn't originate here */ next_circ->marked_for_close) { /* don't mess with marked circs */ - next_circ = next_circ->next; continue; } @@ -408,9 +402,7 @@ circuit_expire_building(void) any_opened_circs = 1; break; } - next_circ = next_circ->next; } - next_circ = global_circuitlist; #define SET_CUTOFF(target, msec) do { \ long ms = tor_lround(msec); \ @@ -451,12 +443,12 @@ circuit_expire_building(void) * RTTs = 4a + 3b + 2c * RTTs = 9h */ - SET_CUTOFF(general_cutoff, circ_times.timeout_ms); - SET_CUTOFF(begindir_cutoff, circ_times.timeout_ms); + SET_CUTOFF(general_cutoff, get_circuit_build_timeout_ms()); + SET_CUTOFF(begindir_cutoff, get_circuit_build_timeout_ms()); /* > 3hop circs seem to have a 1.0 second delay on their cannibalized * 4th hop. */ - SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (10/6.0) + 1000); + SET_CUTOFF(fourhop_cutoff, get_circuit_build_timeout_ms() * (10/6.0) + 1000); /* CIRCUIT_PURPOSE_C_ESTABLISH_REND behaves more like a RELAY cell. * Use the stream cutoff (more or less). */ @@ -465,26 +457,25 @@ circuit_expire_building(void) /* Be lenient with cannibalized circs. They already survived the official * CBT, and they're usually not performance-critical. */ SET_CUTOFF(cannibalized_cutoff, - MAX(circ_times.close_ms*(4/6.0), + MAX(get_circuit_build_close_time_ms()*(4/6.0), options->CircuitStreamTimeout * 1000) + 1000); /* Intro circs have an extra round trip (and are also 4 hops long) */ - SET_CUTOFF(c_intro_cutoff, circ_times.timeout_ms * (14/6.0) + 1000); + SET_CUTOFF(c_intro_cutoff, get_circuit_build_timeout_ms() * (14/6.0) + 1000); /* Server intro circs have an extra round trip */ - SET_CUTOFF(s_intro_cutoff, circ_times.timeout_ms * (9/6.0) + 1000); + SET_CUTOFF(s_intro_cutoff, get_circuit_build_timeout_ms() * (9/6.0) + 1000); - SET_CUTOFF(close_cutoff, circ_times.close_ms); - SET_CUTOFF(extremely_old_cutoff, circ_times.close_ms*2 + 1000); + SET_CUTOFF(close_cutoff, get_circuit_build_close_time_ms()); + SET_CUTOFF(extremely_old_cutoff, get_circuit_build_close_time_ms()*2 + 1000); SET_CUTOFF(hs_extremely_old_cutoff, - MAX(circ_times.close_ms*2 + 1000, + MAX(get_circuit_build_close_time_ms()*2 + 1000, options->SocksTimeout * 1000)); - while (next_circ) { + TOR_LIST_FOREACH(next_circ, circuit_get_global_list(), head) { struct timeval cutoff; victim = next_circ; - next_circ = next_circ->next; if (!CIRCUIT_IS_ORIGIN(victim) || /* didn't originate here */ victim->marked_for_close) /* don't mess with marked circs */ continue; @@ -555,12 +546,14 @@ circuit_expire_building(void) * was a timeout, and the timeout value needs to reset if we * see enough of them. Note this means we also need to avoid * double-counting below, too. */ - circuit_build_times_count_timeout(&circ_times, first_hop_succeeded); + circuit_build_times_count_timeout(get_circuit_build_times_mutable(), + first_hop_succeeded); TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout = 1; } continue; } else { static ratelim_t relax_timeout_limit = RATELIM_INIT(3600); + const double build_close_ms = get_circuit_build_close_time_ms(); log_fn_ratelim(&relax_timeout_limit, LOG_NOTICE, LD_CIRC, "No circuits are opened. Relaxed timeout for circuit %d " "(a %s %d-hop circuit in state %s with channel state %s) to " @@ -571,7 +564,8 @@ circuit_expire_building(void) TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len, circuit_state_to_string(victim->state), channel_state_to_string(victim->n_chan->state), - (long)circ_times.close_ms, num_live_entry_guards(0)); + (long)build_close_ms, + num_live_entry_guards(0)); } } @@ -651,7 +645,7 @@ circuit_expire_building(void) } if (circuit_timeout_want_to_count_circ(TO_ORIGIN_CIRCUIT(victim)) && - circuit_build_times_enough_to_compute(&circ_times)) { + circuit_build_times_enough_to_compute(get_circuit_build_times())) { /* Circuits are allowed to last longer for measurement. * Switch their purpose and wait. */ if (victim->purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { @@ -665,8 +659,9 @@ circuit_expire_building(void) * have a timeout. We also want to avoid double-counting * already "relaxed" circuits, which are counted above. */ if (!TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout) { - circuit_build_times_count_timeout(&circ_times, - first_hop_succeeded); + circuit_build_times_count_timeout( + get_circuit_build_times_mutable(), + first_hop_succeeded); } continue; } @@ -683,10 +678,11 @@ circuit_expire_building(void) (long)(now.tv_sec - victim->timestamp_began.tv_sec), victim->purpose, circuit_purpose_to_string(victim->purpose)); - } else if (circuit_build_times_count_close(&circ_times, + } else if (circuit_build_times_count_close( + get_circuit_build_times_mutable(), first_hop_succeeded, victim->timestamp_created.tv_sec)) { - circuit_build_times_set_timeout(&circ_times); + circuit_build_times_set_timeout(get_circuit_build_times_mutable()); } } } @@ -750,7 +746,7 @@ circuit_expire_building(void) if (victim->n_chan) log_info(LD_CIRC, - "Abandoning circ %u %s:%d (state %d,%d:%s, purpose %d, " + "Abandoning circ %u %s:%u (state %d,%d:%s, purpose %d, " "len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, channel_get_canonical_remote_descr(victim->n_chan), (unsigned)victim->n_circ_id, @@ -760,7 +756,7 @@ circuit_expire_building(void) TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); else log_info(LD_CIRC, - "Abandoning circ %u %d (state %d,%d:%s, purpose %d, len %d)", + "Abandoning circ %u %u (state %d,%d:%s, purpose %d, len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, (unsigned)victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, @@ -818,7 +814,7 @@ circuit_stream_is_being_handled(entry_connection_t *conn, get_options()->LongLivedPorts, conn ? conn->socks_request->port : port); - for (circ=global_circuitlist;circ;circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { if (CIRCUIT_IS_ORIGIN(circ) && !circ->marked_for_close && circ->purpose == CIRCUIT_PURPOSE_C_GENERAL && @@ -869,7 +865,7 @@ circuit_predict_and_launch_new(void) int flags = 0; /* First, count how many of each type of circuit we have already. */ - for (circ=global_circuitlist;circ;circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { cpath_build_state_t *build_state; origin_circuit_t *origin_circ; if (!CIRCUIT_IS_ORIGIN(circ)) @@ -949,7 +945,7 @@ circuit_predict_and_launch_new(void) * we can still build circuits preemptively as needed. */ if (num < MAX_UNUSED_OPEN_CIRCUITS-2 && ! circuit_build_times_disabled() && - circuit_build_times_needs_circuits_now(&circ_times)) { + circuit_build_times_needs_circuits_now(get_circuit_build_times())) { flags = CIRCLAUNCH_NEED_CAPACITY; log_info(LD_CIRC, "Have %d clean circs need another buildtime test circ.", num); @@ -1085,7 +1081,7 @@ circuit_expire_old_circuits_clientside(void) cutoff = now; if (! circuit_build_times_disabled() && - circuit_build_times_needs_circuits(&circ_times)) { + 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; @@ -1093,7 +1089,7 @@ circuit_expire_old_circuits_clientside(void) cutoff.tv_sec -= get_options()->CircuitIdleTimeout; } - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { if (circ->marked_for_close || !CIRCUIT_IS_ORIGIN(circ)) continue; /* If the circuit has been dirty for too long, and there are no streams @@ -1176,7 +1172,7 @@ circuit_expire_old_circuits_serverside(time_t now) or_circuit_t *or_circ; time_t cutoff = now - IDLE_ONE_HOP_CIRC_TIMEOUT; - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { if (circ->marked_for_close || CIRCUIT_IS_ORIGIN(circ)) continue; or_circ = TO_OR_CIRCUIT(circ); @@ -1223,7 +1219,7 @@ circuit_enough_testing_circs(void) if (have_performed_bandwidth_test) return 1; - for (circ = global_circuitlist; circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { if (!circ->marked_for_close && CIRCUIT_IS_ORIGIN(circ) && circ->purpose == CIRCUIT_PURPOSE_TESTING && circ->state == CIRCUIT_STATE_OPEN) @@ -1382,10 +1378,11 @@ circuit_build_failed(origin_circuit_t *circ) failed_at_last_hop = 1; } if (circ->cpath && - circ->cpath->state != CPATH_STATE_OPEN) { - /* We failed at the first hop. If there's an OR connection - * to blame, blame it. Also, avoid this relay for a while, and - * fail any one-hop directory fetches destined for it. */ + circ->cpath->state != CPATH_STATE_OPEN && + ! circ->base_.received_destroy) { + /* We failed at the first hop for some reason other than a DESTROY cell. + * If there's an OR connection to blame, blame it. Also, avoid this relay + * for a while, and fail any one-hop directory fetches destined for it. */ const char *n_chan_id = circ->cpath->extend_info->identity_digest; int already_marked = 0; if (circ->base_.n_chan) { diff --git a/src/or/command.c b/src/or/command.c index 699b02fb4..9b3ff16f2 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -53,6 +53,33 @@ static void command_process_created_cell(cell_t *cell, channel_t *chan); static void command_process_relay_cell(cell_t *cell, channel_t *chan); static void command_process_destroy_cell(cell_t *cell, channel_t *chan); +/** Convert the cell <b>command</b> into a lower-case, human-readable + * string. */ +const char * +cell_command_to_string(uint8_t command) +{ + switch (command) { + case CELL_PADDING: return "padding"; + case CELL_CREATE: return "create"; + case CELL_CREATED: return "created"; + case CELL_RELAY: return "relay"; + case CELL_DESTROY: return "destroy"; + case CELL_CREATE_FAST: return "create_fast"; + case CELL_CREATED_FAST: return "created_fast"; + case CELL_VERSIONS: return "versions"; + case CELL_NETINFO: return "netinfo"; + case CELL_RELAY_EARLY: return "relay_early"; + case CELL_CREATE2: return "create2"; + case CELL_CREATED2: return "created2"; + case CELL_VPADDING: return "vpadding"; + case CELL_CERTS: return "certs"; + case CELL_AUTH_CHALLENGE: return "auth_challenge"; + case CELL_AUTHENTICATE: return "authenticate"; + case CELL_AUTHORIZE: return "authorize"; + default: return "unrecognized"; + } +} + #ifdef KEEP_TIMING_STATS /** This is a wrapper function around the actual function that processes the * <b>cell</b> that just arrived on <b>conn</b>. Increment <b>*time</b> @@ -499,6 +526,7 @@ command_process_destroy_cell(cell_t *cell, channel_t *chan) log_debug(LD_OR,"Received for circID %u.",(unsigned)cell->circ_id); reason = (uint8_t)cell->payload[0]; + circ->received_destroy = 1; if (!CIRCUIT_IS_ORIGIN(circ) && cell->circ_id == TO_OR_CIRCUIT(circ)->p_circ_id) { diff --git a/src/or/command.h b/src/or/command.h index 913f46a5c..adea6adea 100644 --- a/src/or/command.h +++ b/src/or/command.h @@ -19,6 +19,8 @@ void command_process_var_cell(channel_t *chan, var_cell_t *cell); void command_setup_channel(channel_t *chan); void command_setup_listener(channel_listener_t *chan_l); +const char *cell_command_to_string(uint8_t command); + extern uint64_t stats_n_padding_cells_processed; extern uint64_t stats_n_create_cells_processed; extern uint64_t stats_n_created_cells_processed; diff --git a/src/or/config.c b/src/or/config.c index 3984755dd..d348f1036 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -10,7 +10,6 @@ **/ #define CONFIG_PRIVATE - #include "or.h" #include "addressmap.h" #include "channel.h" @@ -40,11 +39,14 @@ #include "rendservice.h" #include "rephist.h" #include "router.h" +#include "sandbox.h" #include "util.h" #include "routerlist.h" #include "routerset.h" #include "statefile.h" #include "transports.h" +#include "ext_orport.h" +#include "torgzip.h" #ifdef _WIN32 #include <shlobj.h> #endif @@ -218,6 +220,9 @@ static config_var_t option_vars_[] = { VPORT(DNSPort, LINELIST, NULL), V(DNSListenAddress, LINELIST, NULL), V(DownloadExtraInfo, BOOL, "0"), + V(TestingEnableConnBwEvent, BOOL, "0"), + V(TestingEnableCellStatsEvent, BOOL, "0"), + V(TestingEnableTbEmptyEvent, BOOL, "0"), V(EnforceDistinctSubnets, BOOL, "1"), V(EntryNodes, ROUTERSET, NULL), V(EntryStatistics, BOOL, "0"), @@ -230,13 +235,14 @@ static config_var_t option_vars_[] = { V(ExitPolicyRejectPrivate, BOOL, "1"), V(ExitPortStatistics, BOOL, "0"), V(ExtendAllowPrivateAddresses, BOOL, "0"), + VPORT(ExtORPort, LINELIST, NULL), V(ExtraInfoStatistics, BOOL, "1"), V(FallbackDir, LINELIST, NULL), OBSOLETE("FallbackNetworkstatusFile"), V(FascistFirewall, BOOL, "0"), V(FirewallPorts, CSV, ""), - V(FastFirstHopPK, BOOL, "1"), + V(FastFirstHopPK, AUTOBOOL, "auto"), V(FetchDirInfoEarly, BOOL, "0"), V(FetchDirInfoExtraEarly, BOOL, "0"), V(FetchServerDescriptors, BOOL, "1"), @@ -281,6 +287,7 @@ static config_var_t option_vars_[] = { V(IPv6Exit, BOOL, "0"), VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), V(ServerTransportListenAddr, LINELIST, NULL), + V(ServerTransportOptions, LINELIST, NULL), V(Socks4Proxy, STRING, NULL), V(Socks5Proxy, STRING, NULL), V(Socks5ProxyUsername, STRING, NULL), @@ -370,6 +377,7 @@ static config_var_t option_vars_[] = { V(RunAsDaemon, BOOL, "0"), // V(RunTesting, BOOL, "0"), OBSOLETE("RunTesting"), // currently unused + V(Sandbox, BOOL, "0"), V(SafeLogging, STRING, "1"), V(SafeSocks, BOOL, "0"), V(ServerDNSAllowBrokenConfig, BOOL, "1"), @@ -406,7 +414,7 @@ static config_var_t option_vars_[] = { V(UseEntryGuards, BOOL, "1"), V(UseEntryGuardsAsDirGuards, BOOL, "1"), V(UseMicrodescriptors, AUTOBOOL, "auto"), - V(UseNTorHandshake, AUTOBOOL, "auto"), + V(UseNTorHandshake, AUTOBOOL, "1"), V(User, STRING, NULL), V(UserspaceIOCPBuffers, BOOL, "0"), VAR("V1AuthoritativeDirectory",BOOL, V1AuthoritativeDir, "0"), @@ -415,6 +423,7 @@ static config_var_t option_vars_[] = { V(TestingV3AuthInitialVotingInterval, INTERVAL, "30 minutes"), V(TestingV3AuthInitialVoteDelay, INTERVAL, "5 minutes"), V(TestingV3AuthInitialDistDelay, INTERVAL, "5 minutes"), + V(TestingV3AuthVotingStartOffset, INTERVAL, "0"), V(V3AuthVotingInterval, INTERVAL, "1 hour"), V(V3AuthVoteDelay, INTERVAL, "5 minutes"), V(V3AuthDistDelay, INTERVAL, "5 minutes"), @@ -435,6 +444,24 @@ static config_var_t option_vars_[] = { VAR("__OwningControllerProcess",STRING,OwningControllerProcess, NULL), V(MinUptimeHidServDirectoryV2, INTERVAL, "25 hours"), V(VoteOnHidServDirectoriesV2, BOOL, "1"), + V(TestingServerDownloadSchedule, CSV_INTERVAL, "0, 0, 0, 60, 60, 120, " + "300, 900, 2147483647"), + V(TestingClientDownloadSchedule, CSV_INTERVAL, "0, 0, 60, 300, 600, " + "2147483647"), + V(TestingServerConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 60, " + "300, 600, 1800, 1800, 1800, 1800, " + "1800, 3600, 7200"), + V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 60, " + "300, 600, 1800, 3600, 3600, 3600, " + "10800, 21600, 43200"), + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "3600, 900, 900, 3600"), + V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"), + V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"), + V(TestingConsensusMaxDownloadTries, UINT, "8"), + V(TestingDescriptorMaxDownloadTries, UINT, "8"), + V(TestingMicrodescMaxDownloadTries, UINT, "8"), + V(TestingCertMaxDownloadTries, UINT, "8"), + V(TestingDirAuthVoteGuard, ROUTERSET, NULL), VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "0"), { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } @@ -460,9 +487,28 @@ static const config_var_t testing_tor_network_defaults[] = { V(TestingV3AuthInitialVotingInterval, INTERVAL, "5 minutes"), V(TestingV3AuthInitialVoteDelay, INTERVAL, "20 seconds"), V(TestingV3AuthInitialDistDelay, INTERVAL, "20 seconds"), + V(TestingV3AuthVotingStartOffset, INTERVAL, "0"), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"), V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"), V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"), + V(TestingServerDownloadSchedule, CSV_INTERVAL, "0, 0, 0, 5, 10, 15, " + "20, 30, 60"), + V(TestingClientDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, 15, 20, " + "30, 60"), + V(TestingServerConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " + "15, 20, 30, 60"), + V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " + "15, 20, 30, 60"), + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "60, 30, 30, 60"), + V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"), + V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"), + V(TestingConsensusMaxDownloadTries, UINT, "80"), + V(TestingDescriptorMaxDownloadTries, UINT, "80"), + V(TestingMicrodescMaxDownloadTries, UINT, "80"), + V(TestingCertMaxDownloadTries, UINT, "80"), + V(TestingEnableConnBwEvent, BOOL, "1"), + V(TestingEnableCellStatsEvent, BOOL, "1"), + V(TestingEnableTbEmptyEvent, BOOL, "1"), VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "1"), { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } @@ -475,9 +521,6 @@ static const config_var_t testing_tor_network_defaults[] = { #ifdef _WIN32 static char *get_windows_conf_root(void); #endif -static int options_validate(or_options_t *old_options, - or_options_t *options, - int from_setconf, char **msg); static int options_act_reversible(const or_options_t *old_options, char **msg); static int options_act(const or_options_t *old_options); static int options_transition_allowed(const or_options_t *old, @@ -487,9 +530,8 @@ 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(const char *lst, const char *name, char **msg); +static int check_nickname_list(char **lst, const char *name, char **msg); -static int parse_bridge_line(const char *line, int validate_only); static int parse_client_transport_line(const char *line, int validate_only); static int parse_server_transport_line(const char *line, int validate_only); @@ -517,18 +559,21 @@ static int parse_outbound_addresses(or_options_t *options, int validate_only, char **msg); static void config_maybe_load_geoip_files_(const or_options_t *options, const or_options_t *old_options); +static int options_validate_cb(void *old_options, void *options, + void *default_options, + int from_setconf, char **msg); /** Magic value for or_options_t. */ #define OR_OPTIONS_MAGIC 9090909 /** Configuration format for or_options_t. */ -static config_format_t options_format = { +STATIC config_format_t options_format = { sizeof(or_options_t), OR_OPTIONS_MAGIC, STRUCT_OFFSET(or_options_t, magic_), option_abbrevs_, option_vars_, - (validate_fn_t)options_validate, + options_validate_cb, NULL }; @@ -545,8 +590,12 @@ static or_options_t *global_default_options = NULL; static char *torrc_fname = NULL; /** Name of the most recently read torrc-defaults file.*/ static char *torrc_defaults_fname; -/** Configuration Options set by command line. */ +/** Configuration options set by command line. */ static config_line_t *global_cmdline_options = NULL; +/** Non-configuration options set by the command line */ +static config_line_t *global_cmdline_only_options = NULL; +/** Boolean: Have we parsed the command line? */ +static int have_parsed_cmdline = 0; /** Contents of most recently read DirPortFrontPage file. */ static char *global_dirfrontpagecontents = NULL; /** List of port_cfg_t for all configured ports. */ @@ -678,7 +727,7 @@ get_short_version(void) /** Release additional memory allocated in options */ -static void +STATIC void or_options_free(or_options_t *options) { if (!options) @@ -691,6 +740,7 @@ or_options_free(or_options_t *options) smartlist_free(options->NodeFamilySets); } tor_free(options->BridgePassword_AuthDigest_); + tor_free(options->command_arg); config_free(&options_format, options); } @@ -707,6 +757,9 @@ config_free_all(void) config_free_lines(global_cmdline_options); global_cmdline_options = NULL; + config_free_lines(global_cmdline_only_options); + global_cmdline_only_options = NULL; + if (configured_ports) { SMARTLIST_FOREACH(configured_ports, port_cfg_t *, p, port_cfg_free(p)); @@ -972,6 +1025,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) int set_conn_limit = 0; int r = -1; int logs_marked = 0; + int old_min_log_level = get_min_log_level(); /* Daemonize _first_, since we only want to open most of this stuff in * the subprocess. Libevent bases can't be reliably inherited across @@ -1081,9 +1135,6 @@ options_act_reversible(const or_options_t *old_options, char **msg) /* No need to roll back, since you can't change the value. */ } - /* Write control ports to disk as appropriate */ - control_ports_write_to_file(); - if (directory_caches_v2_dir_info(options)) { char *fn = NULL; tor_asprintf(&fn, "%s"PATH_SEPARATOR"cached-status", @@ -1119,7 +1170,16 @@ options_act_reversible(const or_options_t *old_options, char **msg) add_callback_log(severity, control_event_logmsg); control_adjust_event_log_severity(); tor_free(severity); + tor_log_update_sigsafe_err_fds(); + } + if (get_min_log_level() >= LOG_INFO && + get_min_log_level() != old_min_log_level) { + log_warn(LD_GENERAL, "Your log may contain sensitive information: you're " + "logging more than \"notice\". Please log safely. Don't log " + "unless it serves an important reason, and overwrite the log " + "afterwards."); } + SMARTLIST_FOREACH(replaced_listeners, connection_t *, conn, { log_notice(LD_NET, "Closing old %s on %s:%d", @@ -1272,6 +1332,9 @@ options_act(const or_options_t *old_options) } } + /* Write control ports to disk as appropriate */ + control_ports_write_to_file(); + if (running_tor && !have_lockfile()) { if (try_locking(options, 1) < 0) return -1; @@ -1302,14 +1365,23 @@ options_act(const or_options_t *old_options) } #endif + if (options->SafeLogging_ != SAFELOG_SCRUB_ALL && + (!old_options || old_options->SafeLogging_ != options->SafeLogging_)) { + log_warn(LD_GENERAL, "Your log may contain sensitive information - you " + "disabled SafeLogging. Please log safely. Don't log unless it " + "serves an important reason. Overwrite the log afterwards."); + } + if (options->Bridges) { mark_bridge_list(); for (cl = options->Bridges; cl; cl = cl->next) { - if (parse_bridge_line(cl->value, 0)<0) { + bridge_line_t *bridge_line = parse_bridge_line(cl->value); + if (!bridge_line) { log_warn(LD_BUG, "Previously validated Bridge line could not be added!"); return -1; } + bridge_add_from_config(bridge_line); } sweep_bridge_list(); } @@ -1423,8 +1495,14 @@ options_act(const or_options_t *old_options) return -1; } - if (init_cookie_authentication(options->CookieAuthentication) < 0) { - log_warn(LD_CONFIG,"Error creating cookie authentication file."); + if (init_control_cookie_authentication(options->CookieAuthentication) < 0) { + log_warn(LD_CONFIG,"Error creating control cookie authentication file."); + return -1; + } + + /* If we have an ExtORPort, initialize its auth cookie. */ + if (init_ext_or_cookie_authentication(!!options->ExtORPort_lines) < 0) { + log_warn(LD_CONFIG,"Error creating Extended ORPort cookie file."); return -1; } @@ -1732,40 +1810,66 @@ options_act(const or_options_t *old_options) return 0; } -/** Helper: Read a list of configuration options from the command line. - * If successful, put them in *<b>result</b> and return 0, and return - * -1 and leave *<b>result</b> alone. */ -static int -config_get_commandlines(int argc, char **argv, config_line_t **result) +static const struct { + const char *name; + int takes_argument; +} CMDLINE_ONLY_OPTIONS[] = { + { "-f", 1 }, + { "--allow-missing-torrc", 0 }, + { "--defaults-torrc", 1 }, + { "--hash-password", 1 }, + { "--dump-config", 1 }, + { "--list-fingerprint", 0 }, + { "--verify-config", 0 }, + { "--ignore-missing-torrc", 0 }, + { "--quiet", 0 }, + { "--hush", 0 }, + { "--version", 0 }, + { "--library-versions", 0 }, + { "-h", 0 }, + { "--help", 0 }, + { "--list-torrc-options", 0 }, + { "--digests", 0 }, + { "--nt-service", 0 }, + { "-nt-service", 0 }, + { NULL, 0 }, +}; + +/** Helper: Read a list of configuration options from the command line. If + * successful, or if ignore_errors is set, put them in *<b>result</b>, put the + * commandline-only options in *<b>cmdline_result</b>, and return 0; + * otherwise, return -1 and leave *<b>result</b> and <b>cmdline_result</b> + * alone. */ +int +config_parse_commandline(int argc, char **argv, int ignore_errors, + config_line_t **result, + config_line_t **cmdline_result) { + config_line_t *param = NULL; + config_line_t *front = NULL; config_line_t **new = &front; - char *s; + + config_line_t *front_cmdline = NULL; + config_line_t **new_cmdline = &front_cmdline; + + char *s, *arg; int i = 1; while (i < argc) { unsigned command = CONFIG_LINE_NORMAL; int want_arg = 1; + int is_cmdline = 0; + int j; - if (!strcmp(argv[i],"-f") || - !strcmp(argv[i],"--defaults-torrc") || - !strcmp(argv[i],"--hash-password")) { - i += 2; /* command-line option with argument. ignore them. */ - continue; - } else if (!strcmp(argv[i],"--list-fingerprint") || - !strcmp(argv[i],"--verify-config") || - !strcmp(argv[i],"--ignore-missing-torrc") || - !strcmp(argv[i],"--quiet") || - !strcmp(argv[i],"--hush")) { - i += 1; /* command-line option. ignore it. */ - continue; - } else if (!strcmp(argv[i],"--nt-service") || - !strcmp(argv[i],"-nt-service")) { - i += 1; - continue; + for (j = 0; CMDLINE_ONLY_OPTIONS[j].name != NULL; ++j) { + if (!strcmp(argv[i], CMDLINE_ONLY_OPTIONS[j].name)) { + is_cmdline = 1; + want_arg = CMDLINE_ONLY_OPTIONS[j].takes_argument; + break; + } } - *new = tor_malloc_zero(sizeof(config_line_t)); s = argv[i]; /* Each keyword may be prefixed with one or two dashes. */ @@ -1785,22 +1889,38 @@ config_get_commandlines(int argc, char **argv, config_line_t **result) } if (want_arg && i == argc-1) { - log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.", - argv[i]); - config_free_lines(front); - return -1; + if (ignore_errors) { + arg = strdup(""); + } else { + log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.", + argv[i]); + config_free_lines(front); + config_free_lines(front_cmdline); + return -1; + } + } else { + arg = want_arg ? tor_strdup(argv[i+1]) : strdup(""); } - (*new)->key = tor_strdup(config_expand_abbrev(&options_format, s, 1, 1)); - (*new)->value = want_arg ? tor_strdup(argv[i+1]) : tor_strdup(""); - (*new)->command = command; - (*new)->next = NULL; + param = tor_malloc_zero(sizeof(config_line_t)); + param->key = is_cmdline ? tor_strdup(argv[i]) : tor_strdup(s); + param->value = arg; + param->command = command; + param->next = NULL; log_debug(LD_CONFIG, "command line: parsed keyword '%s', value '%s'", - (*new)->key, (*new)->value); + param->key, param->value); + + if (is_cmdline) { + *new_cmdline = param; + new_cmdline = &((*new_cmdline)->next); + } else { + *new = param; + new = &((*new)->next); + } - new = &((*new)->next); i += want_arg ? 2 : 1; } + *cmdline_result = front_cmdline; *result = front; return 0; } @@ -1852,7 +1972,8 @@ options_trial_assign(config_line_t *list, int use_defaults, return r; } - if (options_validate(get_options_mutable(), trial_options, 1, msg) < 0) { + if (options_validate(get_options_mutable(), trial_options, + global_default_options, 1, msg) < 0) { config_free(&options_format, trial_options); return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ } @@ -2166,10 +2287,29 @@ options_init(or_options_t *options) * include options that are the same as Tor's defaults. */ char * -options_dump(const or_options_t *options, int minimal) +options_dump(const or_options_t *options, int how_to_dump) { - return config_dump(&options_format, global_default_options, - options, minimal, 0); + const or_options_t *use_defaults; + int minimal; + switch (how_to_dump) { + case OPTIONS_DUMP_MINIMAL: + use_defaults = global_default_options; + minimal = 1; + break; + case OPTIONS_DUMP_DEFAULTS: + use_defaults = NULL; + minimal = 1; + break; + case OPTIONS_DUMP_ALL: + use_defaults = NULL; + minimal = 0; + break; + default: + log_warn(LD_BUG, "Bogus value for how_to_dump==%d", how_to_dump); + return NULL; + } + + return config_dump(&options_format, use_defaults, options, minimal, 0); } /** Return 0 if every element of sl is a string holding a decimal @@ -2286,10 +2426,19 @@ compute_publishserverdescriptor(or_options_t *options) * */ #define RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT (10) -/** Return 0 if every setting in <b>options</b> is reasonable, and a - * permissible transition from <b>old_options</b>. Else return -1. - * Should have no side effects, except for normalizing the contents of - * <b>options</b>. +static int +options_validate_cb(void *old_options, void *options, void *default_options, + int from_setconf, char **msg) +{ + return options_validate(old_options, options, default_options, + from_setconf, msg); +} + +/** Return 0 if every setting in <b>options</b> is reasonable, is a + * permissible transition from <b>old_options</b>, and none of the + * testing-only settings differ from <b>default_options</b> unless in + * testing mode. Else return -1. Should have no side effects, except for + * normalizing the contents of <b>options</b>. * * On error, tor_strdup an error explanation into *<b>msg</b>. * @@ -2298,9 +2447,9 @@ compute_publishserverdescriptor(or_options_t *options) * Log line should stay empty. If it's 0, then give us a default log * if there are no logs defined. */ -static int +STATIC int options_validate(or_options_t *old_options, or_options_t *options, - int from_setconf, char **msg) + or_options_t *default_options, int from_setconf, char **msg) { int i; config_line_t *cl; @@ -2973,14 +3122,14 @@ options_validate(or_options_t *old_options, or_options_t *options, size_t len; len = strlen(options->Socks5ProxyUsername); - if (len < 1 || len > 255) + if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE) REJECT("Socks5ProxyUsername must be between 1 and 255 characters."); if (!options->Socks5ProxyPassword) REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername."); len = strlen(options->Socks5ProxyPassword); - if (len < 1 || len > 255) + if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE) REJECT("Socks5ProxyPassword must be between 1 and 255 characters."); } else if (options->Socks5ProxyPassword) REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername."); @@ -3040,7 +3189,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "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 (check_nickname_list(&options->MyFamily, "MyFamily", msg)) return -1; for (cl = options->NodeFamilies; cl; cl = cl->next) { routerset_t *rs = routerset_new(); @@ -3064,8 +3213,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("If you set UseBridges, you must set TunnelDirConns."); for (cl = options->Bridges; cl; cl = cl->next) { - if (parse_bridge_line(cl->value, 1)<0) - REJECT("Bridge line did not parse. See logs for details."); + bridge_line_t *bridge_line = parse_bridge_line(cl->value); + if (!bridge_line) + REJECT("Bridge line did not parse. See logs for details."); + bridge_line_free(bridge_line); } for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { @@ -3101,6 +3252,30 @@ options_validate(or_options_t *old_options, or_options_t *options, "ServerTransportListenAddr line will be ignored."); } + for (cl = options->ServerTransportOptions; cl; cl = cl->next) { + /** If get_options_from_transport_options_line() fails with + 'transport' being NULL, it means that something went wrong + while parsing the ServerTransportOptions line. */ + smartlist_t *options_sl = + get_options_from_transport_options_line(cl->value, NULL); + if (!options_sl) + REJECT("ServerTransportOptions did not parse. See logs for details."); + + SMARTLIST_FOREACH(options_sl, char *, cp, tor_free(cp)); + smartlist_free(options_sl); + } + + /* If we are a bridge with a pluggable transport proxy but no + Extended ORPort, inform the user that she is missing out. */ + if (server_mode(options) && options->ServerTransportPlugin && + !options->ExtORPort_lines) { + log_notice(LD_CONFIG, "We are a bridge with a pluggable transport " + "proxy but the Extended ORPort is disabled. The " + "Extended ORPort helps Tor communicate with the pluggable " + "transport proxy. Please enable it using the ExtORPort " + "torrc option."); + } + if (options->ConstrainedSockets) { /* If the user wants to constrain socket buffer use, make sure the desired * limit is between MIN|MAX_TCPSOCK_BUFFER in k increments. */ @@ -3193,35 +3368,46 @@ options_validate(or_options_t *old_options, or_options_t *options, "ignore you."); } - /*XXXX checking for defaults manually like this is a bit fragile.*/ - - /* Keep changes to hard-coded values synchronous to man page and default - * values table. */ - if (options->TestingV3AuthInitialVotingInterval != 30*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingV3AuthInitialVotingInterval may only be changed in testing " - "Tor networks!"); - } else if (options->TestingV3AuthInitialVotingInterval < MIN_VOTE_INTERVAL) { +#define CHECK_DEFAULT(arg) \ + STMT_BEGIN \ + if (!options->TestingTorNetwork && \ + !options->UsingTestNetworkDefaults_ && \ + !config_is_same(&options_format,options, \ + default_options,#arg)) { \ + REJECT(#arg " may only be changed in testing Tor " \ + "networks!"); \ + } STMT_END + CHECK_DEFAULT(TestingV3AuthInitialVotingInterval); + CHECK_DEFAULT(TestingV3AuthInitialVoteDelay); + CHECK_DEFAULT(TestingV3AuthInitialDistDelay); + CHECK_DEFAULT(TestingV3AuthVotingStartOffset); + CHECK_DEFAULT(TestingAuthDirTimeToLearnReachability); + CHECK_DEFAULT(TestingEstimatedDescriptorPropagationTime); + CHECK_DEFAULT(TestingServerDownloadSchedule); + CHECK_DEFAULT(TestingClientDownloadSchedule); + CHECK_DEFAULT(TestingServerConsensusDownloadSchedule); + CHECK_DEFAULT(TestingClientConsensusDownloadSchedule); + CHECK_DEFAULT(TestingBridgeDownloadSchedule); + CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest); + CHECK_DEFAULT(TestingDirConnectionMaxStall); + CHECK_DEFAULT(TestingConsensusMaxDownloadTries); + CHECK_DEFAULT(TestingDescriptorMaxDownloadTries); + CHECK_DEFAULT(TestingMicrodescMaxDownloadTries); + CHECK_DEFAULT(TestingCertMaxDownloadTries); +#undef CHECK_DEFAULT + + if (options->TestingV3AuthInitialVotingInterval < MIN_VOTE_INTERVAL) { REJECT("TestingV3AuthInitialVotingInterval is insanely low."); } else if (((30*60) % options->TestingV3AuthInitialVotingInterval) != 0) { REJECT("TestingV3AuthInitialVotingInterval does not divide evenly into " "30 minutes."); } - if (options->TestingV3AuthInitialVoteDelay != 5*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - - REJECT("TestingV3AuthInitialVoteDelay may only be changed in testing " - "Tor networks!"); - } else if (options->TestingV3AuthInitialVoteDelay < MIN_VOTE_SECONDS) { + if (options->TestingV3AuthInitialVoteDelay < MIN_VOTE_SECONDS) { REJECT("TestingV3AuthInitialVoteDelay is way too low."); } - if (options->TestingV3AuthInitialDistDelay != 5*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingV3AuthInitialDistDelay may only be changed in testing " - "Tor networks!"); - } else if (options->TestingV3AuthInitialDistDelay < MIN_DIST_SECONDS) { + if (options->TestingV3AuthInitialDistDelay < MIN_DIST_SECONDS) { REJECT("TestingV3AuthInitialDistDelay is way too low."); } @@ -3232,26 +3418,79 @@ options_validate(or_options_t *old_options, or_options_t *options, "must be less than half TestingV3AuthInitialVotingInterval"); } - if (options->TestingAuthDirTimeToLearnReachability != 30*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingAuthDirTimeToLearnReachability may only be changed in " - "testing Tor networks!"); - } else if (options->TestingAuthDirTimeToLearnReachability < 0) { + if (options->TestingV3AuthVotingStartOffset > + MIN(options->TestingV3AuthInitialVotingInterval, + options->V3AuthVotingInterval)) { + REJECT("TestingV3AuthVotingStartOffset is higher than the voting " + "interval."); + } + + if (options->TestingAuthDirTimeToLearnReachability < 0) { REJECT("TestingAuthDirTimeToLearnReachability must be non-negative."); } else if (options->TestingAuthDirTimeToLearnReachability > 2*60*60) { COMPLAIN("TestingAuthDirTimeToLearnReachability is insanely high."); } - if (options->TestingEstimatedDescriptorPropagationTime != 10*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingEstimatedDescriptorPropagationTime may only be changed in " - "testing Tor networks!"); - } else if (options->TestingEstimatedDescriptorPropagationTime < 0) { + if (options->TestingEstimatedDescriptorPropagationTime < 0) { REJECT("TestingEstimatedDescriptorPropagationTime must be non-negative."); } else if (options->TestingEstimatedDescriptorPropagationTime > 60*60) { COMPLAIN("TestingEstimatedDescriptorPropagationTime is insanely high."); } + if (options->TestingClientMaxIntervalWithoutRequest < 1) { + REJECT("TestingClientMaxIntervalWithoutRequest is way too low."); + } else if (options->TestingClientMaxIntervalWithoutRequest > 3600) { + COMPLAIN("TestingClientMaxIntervalWithoutRequest is insanely high."); + } + + if (options->TestingDirConnectionMaxStall < 5) { + REJECT("TestingDirConnectionMaxStall is way too low."); + } else if (options->TestingDirConnectionMaxStall > 3600) { + COMPLAIN("TestingDirConnectionMaxStall is insanely high."); + } + + if (options->TestingConsensusMaxDownloadTries < 2) { + REJECT("TestingConsensusMaxDownloadTries must be greater than 1."); + } else if (options->TestingConsensusMaxDownloadTries > 800) { + COMPLAIN("TestingConsensusMaxDownloadTries is insanely high."); + } + + if (options->TestingDescriptorMaxDownloadTries < 2) { + REJECT("TestingDescriptorMaxDownloadTries must be greater than 1."); + } else if (options->TestingDescriptorMaxDownloadTries > 800) { + COMPLAIN("TestingDescriptorMaxDownloadTries is insanely high."); + } + + if (options->TestingMicrodescMaxDownloadTries < 2) { + REJECT("TestingMicrodescMaxDownloadTries must be greater than 1."); + } else if (options->TestingMicrodescMaxDownloadTries > 800) { + COMPLAIN("TestingMicrodescMaxDownloadTries is insanely high."); + } + + if (options->TestingCertMaxDownloadTries < 2) { + REJECT("TestingCertMaxDownloadTries must be greater than 1."); + } else if (options->TestingCertMaxDownloadTries > 800) { + COMPLAIN("TestingCertMaxDownloadTries is insanely high."); + } + + if (options->TestingEnableConnBwEvent && + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { + REJECT("TestingEnableConnBwEvent may only be changed in testing " + "Tor networks!"); + } + + if (options->TestingEnableCellStatsEvent && + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { + REJECT("TestingEnableCellStatsEvent may only be changed in testing " + "Tor networks!"); + } + + if (options->TestingEnableTbEmptyEvent && + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { + REJECT("TestingEnableTbEmptyEvent may only be changed in testing " + "Tor networks!"); + } + if (options->TestingTorNetwork) { log_warn(LD_CONFIG, "TestingTorNetwork is set. This will make your node " "almost unusable in the public Tor network, and is " @@ -3510,31 +3749,63 @@ get_default_conf_file(int defaults_file) } /** Verify whether lst is a string containing valid-looking comma-separated - * nicknames, or NULL. Return 0 on success. Warn and return -1 on failure. + * nicknames, or NULL. Will normalise <b>lst</b> to prefix '$' to any nickname + * or fingerprint that needs it. Return 0 on success. + * Warn and return -1 on failure. */ static int -check_nickname_list(const char *lst, const char *name, char **msg) +check_nickname_list(char **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, ",", + smartlist_split_string(sl, *lst, ",", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0); - SMARTLIST_FOREACH(sl, const char *, s, + SMARTLIST_FOREACH_BEGIN(sl, char *, s) { if (!is_legal_nickname_or_hexdigest(s)) { + // check if first char is dollar + if (s[0] != '$') { + // Try again but with a dollar symbol prepended + char *prepended; + 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; + } + + // 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; } - }); + } + 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; + } + SMARTLIST_FOREACH(sl, char *, s, tor_free(s)); smartlist_free(sl); + return r; } @@ -3550,26 +3821,26 @@ check_nickname_list(const char *lst, const char *name, char **msg) * filename if it doesn't exist. */ static char * -find_torrc_filename(int argc, char **argv, +find_torrc_filename(config_line_t *cmd_arg, int defaults_file, int *using_default_fname, int *ignore_missing_torrc) { char *fname=NULL; - int i; + config_line_t *p_index; const char *fname_opt = defaults_file ? "--defaults-torrc" : "-f"; const char *ignore_opt = defaults_file ? NULL : "--ignore-missing-torrc"; if (defaults_file) *ignore_missing_torrc = 1; - for (i = 1; i < argc; ++i) { - if (i < argc-1 && !strcmp(argv[i],fname_opt)) { + for (p_index = cmd_arg; p_index; p_index = p_index->next) { + if (!strcmp(p_index->key, fname_opt)) { if (fname) { log_warn(LD_CONFIG, "Duplicate %s options on command line.", fname_opt); tor_free(fname); } - fname = expand_filename(argv[i+1]); + fname = expand_filename(p_index->value); { char *absfname; @@ -3579,8 +3850,7 @@ find_torrc_filename(int argc, char **argv, } *using_default_fname = 0; - ++i; - } else if (ignore_opt && !strcmp(argv[i],ignore_opt)) { + } else if (ignore_opt && !strcmp(p_index->key,ignore_opt)) { *ignore_missing_torrc = 1; } } @@ -3617,7 +3887,7 @@ find_torrc_filename(int argc, char **argv, * Return the contents of the file on success, and NULL on failure. */ static char * -load_torrc_from_disk(int argc, char **argv, int defaults_file) +load_torrc_from_disk(config_line_t *cmd_arg, int defaults_file) { char *fname=NULL; char *cf = NULL; @@ -3625,7 +3895,7 @@ load_torrc_from_disk(int argc, char **argv, int defaults_file) int ignore_missing_torrc = 0; char **fname_var = defaults_file ? &torrc_defaults_fname : &torrc_fname; - fname = find_torrc_filename(argc, argv, defaults_file, + fname = find_torrc_filename(cmd_arg, defaults_file, &using_default_torrc, &ignore_missing_torrc); tor_assert(fname); log_debug(LD_CONFIG, "Opening config file \"%s\"", fname); @@ -3667,59 +3937,75 @@ int options_init_from_torrc(int argc, char **argv) { char *cf=NULL, *cf_defaults=NULL; - int i, command; + int command; int retval = -1; - static char **backup_argv; - static int backup_argc; char *command_arg = NULL; char *errmsg=NULL; + config_line_t *p_index = NULL; + config_line_t *cmdline_only_options = NULL; - if (argv) { /* first time we're called. save command line args */ - backup_argv = argv; - backup_argc = argc; - } else { /* we're reloading. need to clean up old options first. */ - argv = backup_argv; - argc = backup_argc; + /* Go through command-line variables */ + if (! have_parsed_cmdline) { + /* Or we could redo the list every time we pass this place. + * It does not really matter */ + if (config_parse_commandline(argc, argv, 0, &global_cmdline_options, + &global_cmdline_only_options) < 0) { + goto err; + } + have_parsed_cmdline = 1; } - if (argc > 1 && (!strcmp(argv[1], "-h") || !strcmp(argv[1],"--help"))) { + cmdline_only_options = global_cmdline_only_options; + + if (config_line_find(cmdline_only_options, "-h") || + config_line_find(cmdline_only_options, "--help")) { print_usage(); exit(0); } - if (argc > 1 && !strcmp(argv[1], "--list-torrc-options")) { + if (config_line_find(cmdline_only_options, "--list-torrc-options")) { /* For documenting validating whether we've documented everything. */ list_torrc_options(); exit(0); } - if (argc > 1 && (!strcmp(argv[1],"--version"))) { + if (config_line_find(cmdline_only_options, "--version")) { printf("Tor version %s.\n",get_version()); exit(0); } - if (argc > 1 && (!strcmp(argv[1],"--digests"))) { + + if (config_line_find(cmdline_only_options, "--digests")) { printf("Tor version %s.\n",get_version()); printf("%s", libor_get_digests()); printf("%s", tor_get_digests()); exit(0); } - /* Go through command-line variables */ - if (!global_cmdline_options) { - /* Or we could redo the list every time we pass this place. - * It does not really matter */ - if (config_get_commandlines(argc, argv, &global_cmdline_options) < 0) { - goto err; - } + if (config_line_find(cmdline_only_options, "--library-versions")) { + printf("Tor version %s. \n", get_version()); + printf("Library versions\tCompiled\t\tRuntime\n"); + printf("Libevent\t\t%-15s\t\t%s\n", + tor_libevent_get_header_version_str(), + tor_libevent_get_version_str()); + 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()); + //TODO: Hex versions? + exit(0); } command = CMD_RUN_TOR; - for (i = 1; i < argc; ++i) { - if (!strcmp(argv[i],"--list-fingerprint")) { + for (p_index = cmdline_only_options; p_index; p_index = p_index->next) { + if (!strcmp(p_index->key,"--list-fingerprint")) { command = CMD_LIST_FINGERPRINT; - } else if (!strcmp(argv[i],"--hash-password")) { + } else if (!strcmp(p_index->key, "--hash-password")) { command = CMD_HASH_PASSWORD; - command_arg = tor_strdup( (i < argc-1) ? argv[i+1] : ""); - ++i; - } else if (!strcmp(argv[i],"--verify-config")) { + command_arg = p_index->value; + } else if (!strcmp(p_index->key, "--dump-config")) { + command = CMD_DUMP_CONFIG; + command_arg = p_index->value; + } else if (!strcmp(p_index->key, "--verify-config")) { command = CMD_VERIFY_CONFIG; } } @@ -3728,10 +4014,15 @@ options_init_from_torrc(int argc, char **argv) cf_defaults = tor_strdup(""); cf = tor_strdup(""); } else { - cf_defaults = load_torrc_from_disk(argc, argv, 1); - cf = load_torrc_from_disk(argc, argv, 0); - if (!cf) - goto err; + cf_defaults = load_torrc_from_disk(cmdline_only_options, 1); + cf = load_torrc_from_disk(cmdline_only_options, 0); + if (!cf) { + if (config_line_find(cmdline_only_options, "--allow-missing-torrc")) { + cf = tor_strdup(""); + } else { + goto err; + } + } } retval = options_init_from_string(cf_defaults, cf, command, command_arg, @@ -3775,7 +4066,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, newoptions->magic_ = OR_OPTIONS_MAGIC; options_init(newoptions); newoptions->command = command; - newoptions->command_arg = command_arg; + newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL; for (i = 0; i < 2; ++i) { const char *body = i==0 ? cf_defaults : cf; @@ -3839,7 +4130,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, newoptions->magic_ = OR_OPTIONS_MAGIC; options_init(newoptions); newoptions->command = command; - newoptions->command_arg = command_arg; + newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL; /* Assign all options a second time. */ for (i = 0; i < 2; ++i) { @@ -3871,7 +4162,8 @@ options_init_from_string(const char *cf_defaults, const char *cf, } /* Validate newoptions */ - if (options_validate(oldoptions, newoptions, 0, msg) < 0) { + if (options_validate(oldoptions, newoptions, newdefaultoptions, + 0, msg) < 0) { err = SETOPT_ERR_PARSE; /*XXX make this a separate return value.*/ goto err; } @@ -4128,21 +4420,72 @@ options_init_logs(or_options_t *options, int validate_only) return ok?0:-1; } +/** Given a smartlist of SOCKS arguments to be passed to a transport + * proxy in <b>args</b>, validate them and return -1 if they are + * corrupted. Return 0 if they seem OK. */ +static int +validate_transport_socks_arguments(const smartlist_t *args) +{ + char *socks_string = NULL; + size_t socks_string_len; + + tor_assert(args); + tor_assert(smartlist_len(args) > 0); + + SMARTLIST_FOREACH_BEGIN(args, const char *, s) { + if (!string_is_key_value(LOG_WARN, s)) { /* items should be k=v items */ + log_warn(LD_CONFIG, "'%s' is not a k=v item.", s); + return -1; + } + } SMARTLIST_FOREACH_END(s); + + socks_string = pt_stringify_socks_args(args); + if (!socks_string) + return -1; + + socks_string_len = strlen(socks_string); + tor_free(socks_string); + + if (socks_string_len > MAX_SOCKS5_AUTH_SIZE_TOTAL) { + log_warn(LD_CONFIG, "SOCKS arguments can't be more than %u bytes (%lu).", + MAX_SOCKS5_AUTH_SIZE_TOTAL, + (unsigned long) socks_string_len); + return -1; + } + + return 0; +} + +/** Deallocate a bridge_line_t structure. */ +/* private */ void +bridge_line_free(bridge_line_t *bridge_line) +{ + if (!bridge_line) + return; + + if (bridge_line->socks_args) { + SMARTLIST_FOREACH(bridge_line->socks_args, char*, s, tor_free(s)); + smartlist_free(bridge_line->socks_args); + } + tor_free(bridge_line->transport_name); + tor_free(bridge_line); +} + /** Read the contents of a Bridge line from <b>line</b>. Return 0 * if the line is well-formed, and -1 if it isn't. If * <b>validate_only</b> is 0, and the line is well-formed, then add - * the bridge described in the line to our internal bridge list. */ -static int -parse_bridge_line(const char *line, int validate_only) + * the bridge described in the line to our internal bridge list. + * + * Bridge line format: + * Bridge [transport] IP:PORT [id-fingerprint] [k=v] [k=v] ... + */ +/* private */ bridge_line_t * +parse_bridge_line(const char *line) { smartlist_t *items = NULL; - int r; char *addrport=NULL, *fingerprint=NULL; - char *transport_name=NULL; - char *field1=NULL; - tor_addr_t addr; - uint16_t port = 0; - char digest[DIGEST_LEN]; + char *field=NULL; + bridge_line_t *bridge_line = tor_malloc_zero(sizeof(bridge_line_t)); items = smartlist_new(); smartlist_split_string(items, line, NULL, @@ -4152,75 +4495,110 @@ parse_bridge_line(const char *line, int validate_only) goto err; } - /* field1 is either a transport name or addrport */ - field1 = smartlist_get(items, 0); + /* first field is either a transport name or addrport */ + field = smartlist_get(items, 0); smartlist_del_keeporder(items, 0); - if (!(strstr(field1, ".") || strstr(field1, ":"))) { - /* new-style bridge line */ - transport_name = field1; + if (string_is_C_identifier(field)) { + /* It's a transport name. */ + bridge_line->transport_name = field; if (smartlist_len(items) < 1) { log_warn(LD_CONFIG, "Too few items to Bridge line."); goto err; } - addrport = smartlist_get(items, 0); + addrport = smartlist_get(items, 0); /* Next field is addrport then. */ smartlist_del_keeporder(items, 0); } else { - addrport = field1; + addrport = field; } - if (tor_addr_port_lookup(addrport, &addr, &port)<0) { + /* Parse addrport. */ + if (tor_addr_port_lookup(addrport, + &bridge_line->addr, &bridge_line->port)<0) { log_warn(LD_CONFIG, "Error parsing Bridge address '%s'", addrport); goto err; } - if (!port) { + if (!bridge_line->port) { log_info(LD_CONFIG, "Bridge address '%s' has no port; using default port 443.", addrport); - port = 443; + bridge_line->port = 443; } + /* If transports are enabled, next field could be a fingerprint or a + socks argument. If transports are disabled, next field must be + a fingerprint. */ if (smartlist_len(items)) { - fingerprint = smartlist_join_strings(items, "", 0, NULL); + if (bridge_line->transport_name) { /* transports enabled: */ + field = smartlist_get(items, 0); + smartlist_del_keeporder(items, 0); + + /* If it's a key=value pair, then it's a SOCKS argument for the + transport proxy... */ + if (string_is_key_value(LOG_DEBUG, field)) { + bridge_line->socks_args = smartlist_new(); + smartlist_add(bridge_line->socks_args, field); + } else { /* ...otherwise, it's the bridge fingerprint. */ + fingerprint = field; + } + + } else { /* transports disabled: */ + fingerprint = smartlist_join_strings(items, "", 0, NULL); + } + } + + /* Handle fingerprint, if it was provided. */ + if (fingerprint) { if (strlen(fingerprint) != HEX_DIGEST_LEN) { log_warn(LD_CONFIG, "Key digest for Bridge is wrong length."); goto err; } - if (base16_decode(digest, DIGEST_LEN, fingerprint, HEX_DIGEST_LEN)<0) { + if (base16_decode(bridge_line->digest, DIGEST_LEN, + fingerprint, HEX_DIGEST_LEN)<0) { log_warn(LD_CONFIG, "Unable to decode Bridge key digest."); goto err; } } - if (!validate_only) { - log_debug(LD_DIR, "Bridge at %s (transport: %s) (%s)", - fmt_addrport(&addr, port), - transport_name ? transport_name : "no transport", - fingerprint ? fingerprint : "no key listed"); - bridge_add_from_config(&addr, port, - fingerprint ? digest : NULL, transport_name); + /* If we are using transports, any remaining items in the smartlist + should be k=v values. */ + if (bridge_line->transport_name && smartlist_len(items)) { + if (!bridge_line->socks_args) + bridge_line->socks_args = smartlist_new(); + + /* append remaining items of 'items' to 'socks_args' */ + smartlist_add_all(bridge_line->socks_args, items); + smartlist_clear(items); + + tor_assert(smartlist_len(bridge_line->socks_args) > 0); + } + + if (bridge_line->socks_args) { + if (validate_transport_socks_arguments(bridge_line->socks_args) < 0) + goto err; } - r = 0; goto done; err: - r = -1; + bridge_line_free(bridge_line); + bridge_line = NULL; done: SMARTLIST_FOREACH(items, char*, s, tor_free(s)); smartlist_free(items); tor_free(addrport); - tor_free(transport_name); tor_free(fingerprint); - return r; + + return bridge_line; } /** Read the contents of a ClientTransportPlugin line from * <b>line</b>. Return 0 if the line is well-formed, and -1 if it * isn't. * - * If <b>validate_only</b> is 0, and the line is well-formed: + * If <b>validate_only</b> is 0, the line is well-formed, and the + * transport is needed by some bridge: * - If it's an external proxy line, add the transport described in the line to * our internal transport list. * - If it's a managed proxy line, launch the managed proxy. */ @@ -4242,7 +4620,8 @@ parse_client_transport_line(const char *line, int validate_only) int is_managed=0; char **proxy_argv=NULL; char **tmp=NULL; - int proxy_argc,i; + int proxy_argc, i; + int is_useless_proxy=1; int line_length; @@ -4264,11 +4643,16 @@ parse_client_transport_line(const char *line, int validate_only) smartlist_split_string(transport_list, transports, ",", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport_name) { + /* validate transport names */ if (!string_is_C_identifier(transport_name)) { log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", transport_name); goto err; } + + /* see if we actually need the transports provided by this proxy */ + if (!validate_only && transport_is_needed(transport_name)) + is_useless_proxy = 0; } SMARTLIST_FOREACH_END(transport_name); /* field2 is either a SOCKS version or "exec" */ @@ -4287,9 +4671,15 @@ parse_client_transport_line(const char *line, int validate_only) } if (is_managed) { /* managed */ - if (!validate_only) { /* if we are not just validating, use the - rest of the line as the argv of the proxy - to be launched */ + if (!validate_only && is_useless_proxy) { + log_warn(LD_GENERAL, "Pluggable transport proxy (%s) does not provide " + "any needed transports and will not be launched.", line); + } + + /* If we are not just validating, use the rest of the line as the + argv of the proxy to be launched. Also, make sure that we are + only launching proxies that contribute useful transports. */ + if (!validate_only && !is_useless_proxy) { proxy_argc = line_length-2; tor_assert(proxy_argc > 0); proxy_argv = tor_malloc_zero(sizeof(char*)*(proxy_argc+1)); @@ -4403,6 +4793,63 @@ get_bindaddr_from_transport_listen_line(const char *line,const char *transport) return addrport; } +/** Given a ServerTransportOptions <b>line</b>, return a smartlist + * with the options. Return NULL if the line was not well-formed. + * + * If <b>transport</b> is set, return NULL if the line is not + * referring to <b>transport</b>. + * + * The returned smartlist and its strings are allocated on the heap + * and it's the responsibility of the caller to free it. */ +smartlist_t * +get_options_from_transport_options_line(const char *line,const char *transport) +{ + smartlist_t *items = smartlist_new(); + smartlist_t *options = smartlist_new(); + const char *parsed_transport = NULL; + + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + + if (smartlist_len(items) < 2) { + log_warn(LD_CONFIG,"Too few arguments on ServerTransportOptions line."); + goto err; + } + + parsed_transport = smartlist_get(items, 0); + /* If 'transport' is given, check if it matches the one on the line */ + if (transport && strcmp(transport, parsed_transport)) + goto err; + + SMARTLIST_FOREACH_BEGIN(items, const char *, option) { + if (option_sl_idx == 0) /* skip the transport field (first field)*/ + continue; + + /* validate that it's a k=v value */ + if (!string_is_key_value(LOG_WARN, option)) { + log_warn(LD_CONFIG, "%s is not a k=v value.", escaped(option)); + goto err; + } + + /* add it to the options smartlist */ + smartlist_add(options, tor_strdup(option)); + log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option)); + } SMARTLIST_FOREACH_END(option); + + goto done; + + err: + SMARTLIST_FOREACH(options, char*, s, tor_free(s)); + smartlist_free(options); + options = NULL; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + + return options; +} + /** Given the name of a pluggable transport in <b>transport</b>, check * the configuration file to see if the user has explicitly asked for * it to listen on a specific port. Return a <address:port> string if @@ -4423,6 +4870,26 @@ get_transport_bindaddr_from_config(const char *transport) return NULL; } +/** Given the name of a pluggable transport in <b>transport</b>, check + * the configuration file to see if the user has asked us to pass any + * parameters to the pluggable transport. Return a smartlist + * containing the parameters, otherwise NULL. */ +smartlist_t * +get_options_for_server_transport(const char *transport) +{ + config_line_t *cl; + const or_options_t *options = get_options(); + + for (cl = options->ServerTransportOptions; cl; cl = cl->next) { + smartlist_t *options_sl = + get_options_from_transport_options_line(cl->value, transport); + if (options_sl) + return options_sl; + } + + return NULL; +} + /** Read the contents of a ServerTransportPlugin line from * <b>line</b>. Return 0 if the line is well-formed, and -1 if it * isn't. @@ -4827,6 +5294,27 @@ warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname, } SMARTLIST_FOREACH_END(port); } +/** Warn for every Extended ORPort port in <b>ports</b> that is on a + * publicly routable address. */ +static void +warn_nonlocal_ext_orports(const smartlist_t *ports, const char *portname) +{ + SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { + if (port->type != CONN_TYPE_EXT_OR_LISTENER) + continue; + if (port->is_unix_addr) + continue; + /* XXX maybe warn even if address is RFC1918? */ + if (!tor_addr_is_internal(&port->addr, 1)) { + log_warn(LD_CONFIG, "You specified a public address '%s' for %sPort. " + "This is not advised; this address is supposed to only be " + "exposed on localhost so that your pluggable transport " + "proxies can connect to it.", + fmt_addrport(&port->addr, port->port), portname); + } + } SMARTLIST_FOREACH_END(port); +} + /** Given a list of port_cfg_t in <b>ports</b>, warn any controller port there * is listening on any non-loopback address. If <b>forbid</b> is true, * then emit a stronger warning and remove the port from the list. @@ -4927,6 +5415,7 @@ parse_port_config(smartlist_t *out, smartlist_t *elts; int retval = -1; const unsigned is_control = (listener_type == CONN_TYPE_CONTROL_LISTENER); + const unsigned is_ext_orport = (listener_type == CONN_TYPE_EXT_OR_LISTENER); const unsigned allow_no_options = flags & CL_PORT_NO_OPTIONS; const unsigned use_server_options = flags & CL_PORT_SERVER_OPTIONS; const unsigned warn_nonlocal = flags & CL_PORT_WARN_NONLOCAL; @@ -5004,6 +5493,8 @@ parse_port_config(smartlist_t *out, 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); } @@ -5277,6 +5768,8 @@ parse_port_config(smartlist_t *out, 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); } @@ -5423,6 +5916,14 @@ parse_ports(or_options_t *options, int validate_only, goto err; } if (parse_port_config(ports, + options->ExtORPort_lines, NULL, + "ExtOR", CONN_TYPE_EXT_OR_LISTENER, + "127.0.0.1", 0, + CL_PORT_SERVER_OPTIONS|CL_PORT_WARN_NONLOCAL) < 0) { + *msg = tor_strdup("Invalid ExtORPort configuration"); + goto err; + } + if (parse_port_config(ports, options->DirPort_lines, options->DirListenAddress, "Dir", CONN_TYPE_DIR_LISTENER, "0.0.0.0", 0, @@ -5457,6 +5958,8 @@ parse_ports(or_options_t *options, int validate_only, !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER); options->DNSPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_DNS_LISTENER); + options->ExtORPort_set = + !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER); if (!validate_only) { if (configured_ports) { @@ -5744,7 +6247,7 @@ write_configuration_file(const char *fname, const or_options_t *options) return -1; } - if (!(new_conf = options_dump(options, 1))) { + if (!(new_conf = options_dump(options, OPTIONS_DUMP_MINIMAL))) { log_warn(LD_BUG, "Couldn't get configuration string"); goto err; } @@ -5904,6 +6407,43 @@ options_get_datadir_fname2_suffix(const or_options_t *options, return fname; } +/** Check wether the data directory has a private subdirectory + * <b>subdir</b>. If not, try to create it. Return 0 on success, + * -1 otherwise. */ +int +check_or_create_data_subdir(const char *subdir) +{ + char *statsdir = get_datadir_fname(subdir); + int return_val = 0; + + if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { + log_warn(LD_HIST, "Unable to create %s/ directory!", subdir); + return_val = -1; + } + tor_free(statsdir); + return return_val; +} + +/** Create a file named <b>fname</b> with contents <b>str</b> in the + * subdirectory <b>subdir</b> of the data directory. <b>descr</b> + * should be a short description of the file's content and will be + * used for the warning message, if it's present and the write process + * fails. Return 0 on success, -1 otherwise.*/ +int +write_to_data_subdir(const char* subdir, const char* fname, + const char* str, const char* descr) +{ + char *filename = get_datadir_fname2(subdir, fname); + int return_val = 0; + + if (write_str_to_file(filename, str, 0) < 0) { + log_warn(LD_HIST, "Unable to write %s to disk!", descr ? descr : fname); + return_val = -1; + } + tor_free(filename); + return return_val; +} + /** Given a file name check to see whether the file exists but has not been * modified for a very long time. If so, remove it. */ void @@ -5912,7 +6452,8 @@ remove_file_if_very_old(const char *fname, time_t now) #define VERY_OLD_FILE_AGE (28*24*60*60) struct stat st; - if (stat(fname, &st)==0 && st.st_mtime < now-VERY_OLD_FILE_AGE) { + if (stat(sandbox_intern_string(fname), &st)==0 && + st.st_mtime < now-VERY_OLD_FILE_AGE) { char buf[ISO_TIME_LEN+1]; format_local_iso_time(buf, st.st_mtime); log_notice(LD_GENERAL, "Obsolete file %s hasn't been modified since %s. " @@ -5993,6 +6534,7 @@ getinfo_helper_config(control_connection_t *conn, case CONFIG_TYPE_ISOTIME: type = "Time"; break; case CONFIG_TYPE_ROUTERSET: type = "RouterList"; break; case CONFIG_TYPE_CSV: type = "CommaList"; break; + case CONFIG_TYPE_CSV_INTERVAL: type = "TimeIntervalCommaList"; break; case CONFIG_TYPE_LINELIST: type = "LineList"; break; case CONFIG_TYPE_LINELIST_S: type = "Dependant"; break; case CONFIG_TYPE_LINELIST_V: type = "Virtual"; break; @@ -6124,3 +6666,58 @@ config_maybe_load_geoip_files_(const or_options_t *options, config_load_geoip_file_(AF_INET6, options->GeoIPv6File, "geoip6"); } +/** Initialize cookie authentication (used so far by the ControlPort + * and Extended ORPort). + * + * Allocate memory and create a cookie (of length <b>cookie_len</b>) + * in <b>cookie_out</b>. + * Then write it down to <b>fname</b> and prepend it with <b>header</b>. + * + * If the whole procedure was successful, set + * <b>cookie_is_set_out</b> to True. */ +int +init_cookie_authentication(const char *fname, const char *header, + int cookie_len, + uint8_t **cookie_out, int *cookie_is_set_out) +{ + char cookie_file_str_len = strlen(header) + cookie_len; + char *cookie_file_str = tor_malloc(cookie_file_str_len); + int retval = -1; + + /* We don't want to generate a new cookie every time we call + * options_act(). One should be enough. */ + if (*cookie_is_set_out) { + retval = 0; /* we are all set */ + goto done; + } + + /* If we've already set the cookie, free it before re-setting + it. This can happen if we previously generated a cookie, but + couldn't write it to a disk. */ + if (*cookie_out) + tor_free(*cookie_out); + + /* Generate the cookie */ + *cookie_out = tor_malloc(cookie_len); + if (crypto_rand((char *)*cookie_out, cookie_len) < 0) + goto done; + + /* Create the string that should be written on the file. */ + memcpy(cookie_file_str, header, strlen(header)); + memcpy(cookie_file_str+strlen(header), *cookie_out, cookie_len); + if (write_bytes_to_file(fname, cookie_file_str, cookie_file_str_len, 1)) { + log_warn(LD_FS,"Error writing auth cookie to %s.", escaped(fname)); + goto done; + } + + /* Success! */ + log_info(LD_GENERAL, "Generated auth cookie file in '%s'.", escaped(fname)); + *cookie_is_set_out = 1; + retval = 0; + + done: + memwipe(cookie_file_str, 0, cookie_file_str_len); + tor_free(cookie_file_str); + return retval; +} + diff --git a/src/or/config.h b/src/or/config.h index ef4acac51..8ee2a4572 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -32,7 +32,11 @@ int resolve_my_address(int warn_severity, const or_options_t *options, const char **method_out, char **hostname_out); int is_local_addr(const tor_addr_t *addr); void options_init(or_options_t *options); -char *options_dump(const or_options_t *options, int minimal); + +#define OPTIONS_DUMP_MINIMAL 1 +#define OPTIONS_DUMP_DEFAULTS 2 +#define OPTIONS_DUMP_ALL 3 +char *options_dump(const or_options_t *options, int how_to_dump); int options_init_from_torrc(int argc, char **argv); setopt_err_t options_init_from_string(const char *cf_defaults, const char *cf, int command, const char *command_arg, char **msg); @@ -59,6 +63,10 @@ char *options_get_datadir_fname2_suffix(const or_options_t *options, #define get_datadir_fname_suffix(sub1, suffix) \ get_datadir_fname2_suffix((sub1), NULL, (suffix)) +int check_or_create_data_subdir(const char *subdir); +int write_to_data_subdir(const char* subdir, const char* fname, + const char* str, const char* descr); + int get_num_cpus(const or_options_t *options); const smartlist_t *get_configured_ports(void); @@ -86,10 +94,15 @@ uint32_t get_effective_bwburst(const or_options_t *options); char *get_transport_bindaddr_from_config(const char *transport); -#ifdef CONFIG_PRIVATE -/* Used only by config.c and test.c */ +int init_cookie_authentication(const char *fname, const char *header, + int cookie_len, + uint8_t **cookie_out, int *cookie_is_set_out); + or_options_t *options_new(void); -#endif + +int config_parse_commandline(int argc, char **argv, int ignore_errors, + config_line_t **result, + config_line_t **cmdline_result); void config_register_addressmaps(const or_options_t *options); /* XXXX024 move to connection_edge.h */ @@ -98,5 +111,34 @@ int addressmap_register_auto(const char *from, const char *to, addressmap_entry_source_t addrmap_source, const char **msg); +/** Represents the information stored in a torrc Bridge line. */ +typedef struct bridge_line_t { + tor_addr_t addr; /* The IP address of the bridge. */ + uint16_t port; /* The TCP port of the bridge. */ + char *transport_name; /* The name of the pluggable transport that + should be used to connect to the bridge. */ + char digest[DIGEST_LEN]; /* The bridge's identity key digest. */ + smartlist_t *socks_args; /* SOCKS arguments for the pluggable + transport proxy. */ +} bridge_line_t; + +void bridge_line_free(bridge_line_t *bridge_line); +bridge_line_t *parse_bridge_line(const char *line); +smartlist_t *get_options_from_transport_options_line(const char *line, + const char *transport); +smartlist_t *get_options_for_server_transport(const char *transport); + +#ifdef CONFIG_PRIVATE +#ifdef TOR_UNIT_TESTS +extern struct config_format_t options_format; +#endif + +STATIC void or_options_free(or_options_t *options); +STATIC int options_validate(or_options_t *old_options, + or_options_t *options, + or_options_t *default_options, + int from_setconf, char **msg); +#endif + #endif diff --git a/src/or/confparse.c b/src/or/confparse.c index 8863d9240..c5400a651 100644 --- a/src/or/confparse.c +++ b/src/or/confparse.c @@ -79,6 +79,21 @@ config_line_append(config_line_t **lst, (*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 @@ -223,6 +238,8 @@ config_assign_value(const config_format_t *fmt, void *options, int i, ok; const config_var_t *var; void *lvalue; + int *csv_int; + smartlist_t *csv_str; CONFIG_CHECK(fmt, options); @@ -357,6 +374,36 @@ config_assign_value(const config_format_t *fmt, void *options, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); break; + case CONFIG_TYPE_CSV_INTERVAL: + if (*(smartlist_t**)lvalue) { + SMARTLIST_FOREACH(*(smartlist_t**)lvalue, int *, cp, tor_free(cp)); + smartlist_clear(*(smartlist_t**)lvalue); + } else { + *(smartlist_t**)lvalue = smartlist_new(); + } + csv_str = smartlist_new(); + smartlist_split_string(csv_str, c->value, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(csv_str, char *, str) + { + i = config_parse_interval(str, &ok); + if (!ok) { + tor_asprintf(msg, + "Interval in '%s %s' is malformed or out of bounds.", + c->key, c->value); + SMARTLIST_FOREACH(csv_str, char *, cp, tor_free(cp)); + smartlist_free(csv_str); + return -1; + } + csv_int = tor_malloc_zero(sizeof(int)); + *csv_int = i; + smartlist_add(*(smartlist_t**)lvalue, csv_int); + } + SMARTLIST_FOREACH_END(str); + SMARTLIST_FOREACH(csv_str, char *, cp, tor_free(cp)); + smartlist_free(csv_str); + break; + case CONFIG_TYPE_LINELIST: case CONFIG_TYPE_LINELIST_S: { @@ -555,6 +602,7 @@ config_get_assigned_option(const config_format_t *fmt, const void *options, const config_var_t *var; const void *value; config_line_t *result; + smartlist_t *csv_str; tor_assert(options && key); CONFIG_CHECK(fmt, options); @@ -637,6 +685,20 @@ config_get_assigned_option(const config_format_t *fmt, const void *options, else result->value = tor_strdup(""); break; + case CONFIG_TYPE_CSV_INTERVAL: + if (*(smartlist_t**)value) { + csv_str = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(*(smartlist_t**)value, int *, i) + { + smartlist_add_asprintf(csv_str, "%d", *i); + } + SMARTLIST_FOREACH_END(i); + result->value = smartlist_join_strings(csv_str, ",", 0, NULL); + SMARTLIST_FOREACH(csv_str, char *, cp, tor_free(cp)); + smartlist_free(csv_str); + } else + result->value = tor_strdup(""); + break; case CONFIG_TYPE_OBSOLETE: log_fn(LOG_INFO, LD_CONFIG, "You asked me for the value of an obsolete config option '%s'.", @@ -826,6 +888,13 @@ config_clear(const config_format_t *fmt, void *options, *(smartlist_t **)lvalue = NULL; } break; + case CONFIG_TYPE_CSV_INTERVAL: + if (*(smartlist_t**)lvalue) { + SMARTLIST_FOREACH(*(smartlist_t **)lvalue, int *, cp, tor_free(cp)); + smartlist_free(*(smartlist_t **)lvalue); + *(smartlist_t **)lvalue = NULL; + } + break; case CONFIG_TYPE_LINELIST: case CONFIG_TYPE_LINELIST_S: config_free_lines(*(config_line_t **)lvalue); @@ -1005,8 +1074,8 @@ config_dump(const config_format_t *fmt, const void *default_options, /* XXX use a 1 here so we don't add a new log line while dumping */ if (default_options == NULL) { - if (fmt->validate_fn(NULL, defaults_tmp, 1, &msg) < 0) { - log_err(LD_BUG, "Failed to validate default config."); + if (fmt->validate_fn(NULL, defaults_tmp, defaults_tmp, 1, &msg) < 0) { + log_err(LD_BUG, "Failed to validate default config: %s", msg); tor_free(msg); tor_assert(0); } @@ -1072,20 +1141,36 @@ static struct unit_table_t memory_units[] = { { "kbytes", 1<<10 }, { "kilobyte", 1<<10 }, { "kilobytes", 1<<10 }, + { "kilobits", 1<<7 }, + { "kilobit", 1<<7 }, + { "kbits", 1<<7 }, + { "kbit", 1<<7 }, { "m", 1<<20 }, { "mb", 1<<20 }, { "mbyte", 1<<20 }, { "mbytes", 1<<20 }, { "megabyte", 1<<20 }, { "megabytes", 1<<20 }, + { "megabits", 1<<17 }, + { "megabit", 1<<17 }, + { "mbits", 1<<17 }, + { "mbit", 1<<17 }, { "gb", 1<<30 }, { "gbyte", 1<<30 }, { "gbytes", 1<<30 }, { "gigabyte", 1<<30 }, { "gigabytes", 1<<30 }, + { "gigabits", 1<<27 }, + { "gigabit", 1<<27 }, + { "gbits", 1<<27 }, + { "gbit", 1<<27 }, { "tb", U64_LITERAL(1)<<40 }, { "terabyte", U64_LITERAL(1)<<40 }, { "terabytes", U64_LITERAL(1)<<40 }, + { "terabits", U64_LITERAL(1)<<37 }, + { "terabit", U64_LITERAL(1)<<37 }, + { "tbits", U64_LITERAL(1)<<37 }, + { "tbit", U64_LITERAL(1)<<37 }, { NULL, 0 }, }; diff --git a/src/or/confparse.h b/src/or/confparse.h index 1b987f3bf..2cd6c49a2 100644 --- a/src/or/confparse.h +++ b/src/or/confparse.h @@ -26,6 +26,9 @@ typedef enum config_type_t { CONFIG_TYPE_ISOTIME, /**< An ISO-formatted time relative to UTC. */ CONFIG_TYPE_CSV, /**< A list of strings, separated by commas and * optional whitespace. */ + CONFIG_TYPE_CSV_INTERVAL, /**< A list of strings, separated by commas and + * optional whitespace, representing intervals in + * seconds, with optional units */ CONFIG_TYPE_LINELIST, /**< Uninterpreted config lines */ CONFIG_TYPE_LINELIST_S, /**< Uninterpreted, context-sensitive config lines, * mixed with other keywords. */ @@ -68,12 +71,12 @@ typedef struct config_var_description_t { /** Type of a callback to validate whether a given configuration is * well-formed and consistent. See options_trial_assign() for documentation * of arguments. */ -typedef int (*validate_fn_t)(void*,void*,int,char**); +typedef int (*validate_fn_t)(void*,void*,void*,int,char**); /** Information on the keys, value types, key-to-struct-member mappings, * variable descriptions, validation functions, and abbreviations for a * configuration or storage format. */ -typedef struct { +typedef struct config_format_t { size_t size; /**< Size of the struct that everything gets parsed into. */ uint32_t magic; /**< Required 'magic value' to make sure we have a struct * of the right type. */ @@ -100,6 +103,8 @@ 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); diff --git a/src/or/connection.c b/src/or/connection.c index 78cc31e89..1f6e11fac 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -10,6 +10,7 @@ * on connections. **/ +#define CONNECTION_PRIVATE #include "or.h" #include "buffers.h" /* @@ -17,6 +18,7 @@ * part of a subclass (channel_tls_t). */ #define TOR_CHANNEL_INTERNAL_ +#define CONNECTION_PRIVATE #include "channel.h" #include "channeltls.h" #include "circuitbuild.h" @@ -33,6 +35,7 @@ #include "dns.h" #include "dnsserv.h" #include "entrynodes.h" +#include "ext_orport.h" #include "geoip.h" #include "main.h" #include "policies.h" @@ -44,6 +47,7 @@ #include "router.h" #include "transports.h" #include "routerparse.h" +#include "transports.h" #ifdef USE_BUFFEREVENTS #include <event2/event.h> @@ -97,6 +101,7 @@ static smartlist_t *outgoing_addrs = NULL; #define CASE_ANY_LISTENER_TYPE \ case CONN_TYPE_OR_LISTENER: \ + case CONN_TYPE_EXT_OR_LISTENER: \ case CONN_TYPE_AP_LISTENER: \ case CONN_TYPE_DIR_LISTENER: \ case CONN_TYPE_CONTROL_LISTENER: \ @@ -128,6 +133,8 @@ conn_type_to_string(int type) case CONN_TYPE_CPUWORKER: return "CPU worker"; case CONN_TYPE_CONTROL_LISTENER: return "Control listener"; case CONN_TYPE_CONTROL: return "Control"; + case CONN_TYPE_EXT_OR: return "Extended OR"; + case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener"; default: log_warn(LD_BUG, "unknown connection type %d", type); tor_snprintf(buf, sizeof(buf), "unknown [%d]", type); @@ -164,6 +171,18 @@ conn_state_to_string(int type, int state) case OR_CONN_STATE_OPEN: return "open"; } break; + case CONN_TYPE_EXT_OR: + switch (state) { + case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: + return "waiting for authentication type"; + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: + return "waiting for client nonce"; + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: + return "waiting for client hash"; + case EXT_OR_CONN_STATE_OPEN: return "open"; + case EXT_OR_CONN_STATE_FLUSHING: return "flushing final OKAY"; + } + break; case CONN_TYPE_EXIT: switch (state) { case EXIT_CONN_STATE_RESOLVING: return "waiting for dest info"; @@ -228,6 +247,7 @@ connection_type_uses_bufferevent(connection_t *conn) case CONN_TYPE_DIR: case CONN_TYPE_CONTROL: case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: case CONN_TYPE_CPUWORKER: return 1; default: @@ -258,14 +278,18 @@ dir_connection_new(int socket_family) * Set active_circuit_pqueue_last_recalibrated to current cell_ewma tick. */ or_connection_t * -or_connection_new(int socket_family) +or_connection_new(int type, int socket_family) { or_connection_t *or_conn = tor_malloc_zero(sizeof(or_connection_t)); time_t now = time(NULL); - connection_init(now, TO_CONN(or_conn), CONN_TYPE_OR, socket_family); + tor_assert(type == CONN_TYPE_OR || type == CONN_TYPE_EXT_OR); + connection_init(now, TO_CONN(or_conn), type, socket_family); or_conn->timestamp_last_added_nonpadding = time(NULL); + if (type == CONN_TYPE_EXT_OR) + connection_or_set_ext_or_identifier(or_conn); + return or_conn; } @@ -311,7 +335,6 @@ control_connection_new(int socket_family) tor_malloc_zero(sizeof(control_connection_t)); connection_init(time(NULL), TO_CONN(control_conn), CONN_TYPE_CONTROL, socket_family); - log_notice(LD_CONTROL, "New control connection opened."); return control_conn; } @@ -334,7 +357,8 @@ connection_new(int type, int socket_family) { switch (type) { case CONN_TYPE_OR: - return TO_CONN(or_connection_new(socket_family)); + case CONN_TYPE_EXT_OR: + return TO_CONN(or_connection_new(type, socket_family)); case CONN_TYPE_EXIT: return TO_CONN(edge_connection_new(type, socket_family)); @@ -376,6 +400,7 @@ connection_init(time_t now, connection_t *conn, int type, int socket_family) switch (type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: conn->magic = OR_CONNECTION_MAGIC; break; case CONN_TYPE_EXIT: @@ -434,7 +459,7 @@ connection_link_connections(connection_t *conn_a, connection_t *conn_b) * necessary, close its socket if necessary, and mark the directory as dirty * if <b>conn</b> is an OR or OP connection. */ -static void +STATIC void connection_free_(connection_t *conn) { void *mem; @@ -444,6 +469,7 @@ connection_free_(connection_t *conn) switch (conn->type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: tor_assert(conn->magic == OR_CONNECTION_MAGIC); mem = TO_OR_CONN(conn); memlen = sizeof(or_connection_t); @@ -574,6 +600,13 @@ connection_free_(connection_t *conn) log_warn(LD_BUG, "called on OR conn with non-zeroed identity_digest"); connection_or_remove_from_identity_map(TO_OR_CONN(conn)); } + if (conn->type == CONN_TYPE_OR || conn->type == CONN_TYPE_EXT_OR) { + connection_or_remove_from_ext_or_id_map(TO_OR_CONN(conn)); + tor_free(TO_OR_CONN(conn)->ext_or_conn_id); + tor_free(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash); + tor_free(TO_OR_CONN(conn)->ext_or_transport); + } + #ifdef USE_BUFFEREVENTS if (conn->type == CONN_TYPE_OR && TO_OR_CONN(conn)->bucket_cfg) { ev_token_bucket_cfg_free(TO_OR_CONN(conn)->bucket_cfg); @@ -637,6 +670,7 @@ connection_about_to_close_connection(connection_t *conn) connection_dir_about_to_close(TO_DIR_CONN(conn)); break; case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: connection_or_about_to_close(TO_OR_CONN(conn)); break; case CONN_TYPE_AP: @@ -876,8 +910,11 @@ check_location_for_unix_socket(const or_options_t *options, const char *path) int r = -1; char *p = tor_strdup(path); cpd_check_t flags = CPD_CHECK_MODE_ONLY; - if (get_parent_directory(p)<0) + if (get_parent_directory(p)<0 || p[0] != '/') { + log_warn(LD_GENERAL, "Bad unix socket address '%s'. Tor does not support " + "relative paths for unix sockets.", path); goto done; + } if (options->ControlSocketsGroupWritable) flags |= CPD_GROUP_OK; @@ -926,6 +963,27 @@ make_socket_reuseable(tor_socket_t sock) #endif } +/** Max backlog to pass to listen. We start at */ +static int listen_limit = INT_MAX; + +/* Listen on <b>fd</b> with appropriate backlog. Return as for listen. */ +static int +tor_listen(tor_socket_t fd) +{ + int r; + + if ((r = listen(fd, listen_limit)) < 0) { + if (listen_limit == SOMAXCONN) + return r; + if ((r = listen(fd, SOMAXCONN)) == 0) { + listen_limit = SOMAXCONN; + log_warn(LD_NET, "Setting listen backlog to INT_MAX connections " + "didn't work, but SOMAXCONN did. Lowering backlog limit."); + } + } + return r; +} + /** Bind a new non-blocking socket listening to the socket described * by <b>listensockaddr</b>. * @@ -939,8 +997,8 @@ connection_listener_new(const struct sockaddr *listensockaddr, const port_cfg_t *port_cfg) { listener_connection_t *lis_conn; - connection_t *conn; - tor_socket_t s; /* the socket we're going to make */ + connection_t *conn = NULL; + tor_socket_t s = TOR_INVALID_SOCKET; /* the socket we're going to make */ or_options_t const *options = get_options(); #if defined(HAVE_PWD_H) && defined(HAVE_SYS_UN_H) struct passwd *pw = NULL; @@ -966,7 +1024,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, log_notice(LD_NET, "Opening %s on %s", conn_type_to_string(type), fmt_addrport(&addr, usePort)); - s = tor_open_socket(tor_addr_family(&addr), + s = tor_open_socket_nonblocking(tor_addr_family(&addr), is_tcp ? SOCK_STREAM : SOCK_DGRAM, is_tcp ? IPPROTO_TCP: IPPROTO_UDP); if (!SOCKET_OK(s)) { @@ -988,7 +1046,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, /* We need to set IPV6_V6ONLY so that this socket can't get used for * IPv4 connections. */ if (setsockopt(s,IPPROTO_IPV6, IPV6_V6ONLY, - (void*)&one, sizeof(one))<0) { + (void*)&one, sizeof(one)) < 0) { int e = tor_socket_errno(s); log_warn(LD_NET, "Error setting IPV6_V6ONLY flag: %s", tor_socket_strerror(e)); @@ -1004,15 +1062,13 @@ connection_listener_new(const struct sockaddr *listensockaddr, helpfulhint = ". Is Tor already running?"; log_warn(LD_NET, "Could not bind to %s:%u: %s%s", address, usePort, tor_socket_strerror(e), helpfulhint); - tor_close_socket(s); goto err; } if (is_tcp) { - if (listen(s,SOMAXCONN) < 0) { + if (tor_listen(s) < 0) { log_warn(LD_NET, "Could not listen on %s:%u: %s", address, usePort, tor_socket_strerror(tor_socket_errno(s))); - tor_close_socket(s); goto err; } } @@ -1052,7 +1108,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, strerror(errno)); goto err; } - s = tor_open_socket(AF_UNIX, SOCK_STREAM, 0); + s = tor_open_socket_nonblocking(AF_UNIX, SOCK_STREAM, 0); if (! SOCKET_OK(s)) { log_warn(LD_NET,"Socket creation failed: %s.", strerror(errno)); goto err; @@ -1061,7 +1117,6 @@ connection_listener_new(const struct sockaddr *listensockaddr, if (bind(s, listensockaddr, (socklen_t)sizeof(struct sockaddr_un)) == -1) { log_warn(LD_NET,"Bind to %s failed: %s.", address, tor_socket_strerror(tor_socket_errno(s))); - tor_close_socket(s); goto err; } #ifdef HAVE_PWD_H @@ -1070,12 +1125,10 @@ connection_listener_new(const struct sockaddr *listensockaddr, if (pw == NULL) { log_warn(LD_NET,"Unable to chown() %s socket: user %s not found.", address, options->User); - tor_close_socket(s); goto err; } else if (chown(address, pw->pw_uid, pw->pw_gid) < 0) { log_warn(LD_NET,"Unable to chown() %s socket: %s.", address, strerror(errno)); - tor_close_socket(s); goto err; } } @@ -1085,35 +1138,29 @@ connection_listener_new(const struct sockaddr *listensockaddr, * platforms. */ if (chmod(address, 0660) < 0) { log_warn(LD_FS,"Unable to make %s group-writable.", address); - tor_close_socket(s); goto err; } } - if (listen(s,SOMAXCONN) < 0) { + if (listen(s, SOMAXCONN) < 0) { log_warn(LD_NET, "Could not listen on %s: %s", address, tor_socket_strerror(tor_socket_errno(s))); - tor_close_socket(s); goto err; } #else (void)options; #endif /* HAVE_SYS_UN_H */ } else { - log_err(LD_BUG,"Got unexpected address family %d.", - listensockaddr->sa_family); - tor_assert(0); - } - - if (set_socket_nonblocking(s) == -1) { - tor_close_socket(s); - goto err; + log_err(LD_BUG, "Got unexpected address family %d.", + listensockaddr->sa_family); + tor_assert(0); } lis_conn = listener_connection_new(type, listensockaddr->sa_family); conn = TO_CONN(lis_conn); conn->socket_family = listensockaddr->sa_family; conn->s = s; + s = TOR_INVALID_SOCKET; /* Prevent double-close */ conn->address = tor_strdup(address); conn->port = gotPort; tor_addr_copy(&conn->addr, &addr); @@ -1149,7 +1196,6 @@ connection_listener_new(const struct sockaddr *listensockaddr, if (connection_add(conn) < 0) { /* no space, forget it */ log_warn(LD_NET,"connection_add for listener failed. Giving up."); - connection_free(conn); goto err; } @@ -1168,6 +1214,11 @@ connection_listener_new(const struct sockaddr *listensockaddr, return conn; err: + if (SOCKET_OK(s)) + tor_close_socket(s); + if (conn) + connection_free(conn); + return NULL; } @@ -1252,7 +1303,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) tor_assert((size_t)remotelen >= sizeof(struct sockaddr_in)); memset(&addrbuf, 0, sizeof(addrbuf)); - news = tor_accept_socket(conn->s,remote,&remotelen); + news = tor_accept_socket_nonblocking(conn->s,remote,&remotelen); if (!SOCKET_OK(news)) { /* accept() error */ int e = tor_socket_errno(conn->s); if (ERRNO_IS_ACCEPT_EAGAIN(e)) { @@ -1272,10 +1323,6 @@ connection_handle_listener_read(connection_t *conn, int new_type) (int)news,(int)conn->s); make_socket_reuseable(news); - if (set_socket_nonblocking(news) == -1) { - tor_close_socket(news); - return 0; - } if (options->ConstrainedSockets) set_constrained_socket_buffers(news, (int)options->ConstrainedSockSize); @@ -1330,11 +1377,17 @@ connection_handle_listener_read(connection_t *conn, int new_type) TO_ENTRY_CONN(newconn)->socks_request->socks_prefer_no_auth = TO_LISTENER_CONN(conn)->socks_prefer_no_auth; } + if (new_type == CONN_TYPE_CONTROL) { + log_notice(LD_CONTROL, "New control connection opened from %s.", + fmt_and_decorate_addr(&addr)); + } } else if (conn->socket_family == AF_UNIX) { /* For now only control ports can be Unix domain sockets * and listeners at the same time */ tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER); + tor_assert(new_type == CONN_TYPE_CONTROL); + log_notice(LD_CONTROL, "New control connection opened."); newconn = connection_new(new_type, conn->socket_family); newconn->s = news; @@ -1374,6 +1427,9 @@ connection_init_accepted_conn(connection_t *conn, connection_start_reading(conn); switch (conn->type) { + case CONN_TYPE_EXT_OR: + /* Initiate Extended ORPort authentication. */ + return connection_ext_or_start_auth(TO_OR_CONN(conn)); case CONN_TYPE_OR: control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW, 0); rv = connection_tls_start_handshake(TO_OR_CONN(conn), 1); @@ -1467,7 +1523,7 @@ connection_connect(connection_t *conn, const char *address, return -1; } - s = tor_open_socket(protocol_family,SOCK_STREAM,IPPROTO_TCP); + s = tor_open_socket_nonblocking(protocol_family,SOCK_STREAM,IPPROTO_TCP); if (! SOCKET_OK(s)) { *socket_error = tor_socket_errno(-1); log_warn(LD_NET,"Error creating network socket: %s", @@ -1509,12 +1565,6 @@ connection_connect(connection_t *conn, const char *address, } } - if (set_socket_nonblocking(s) == -1) { - *socket_error = tor_socket_errno(s); - tor_close_socket(s); - return -1; - } - if (options->ConstrainedSockets) set_constrained_socket_buffers(s, (int)options->ConstrainedSockSize); @@ -1580,6 +1630,32 @@ connection_proxy_state_to_string(int state) return states[state]; } +/** Returns the global proxy type used by tor. Use this function for + * logging or high-level purposes, don't use it to fill the + * <b>proxy_type</b> field of or_connection_t; use the actual proxy + * protocol instead.*/ +static int +get_proxy_type(void) +{ + const or_options_t *options = get_options(); + + if (options->HTTPSProxy) + return PROXY_CONNECT; + else if (options->Socks4Proxy) + return PROXY_SOCKS4; + else if (options->Socks5Proxy) + return PROXY_SOCKS5; + else if (options->ClientTransportPlugin) + return PROXY_PLUGGABLE; + else + return PROXY_NONE; +} + +/* One byte for the version, one for the command, two for the + port, and four for the addr... and, one more for the + username NUL: */ +#define SOCKS4_STANDARD_BUFFER_SIZE (1 + 1 + 2 + 4 + 1) + /** Write a proxy request of <b>type</b> (socks4, socks5, https) to conn * for conn->addr:conn->port, authenticating with the auth details given * in the configuration (if available). SOCKS 5 and HTTP CONNECT proxies @@ -1634,17 +1710,45 @@ connection_proxy_connect(connection_t *conn, int type) } case PROXY_SOCKS4: { - unsigned char buf[9]; + unsigned char *buf; uint16_t portn; uint32_t ip4addr; + size_t buf_size = 0; + char *socks_args_string = NULL; - /* Send a SOCKS4 connect request with empty user id */ + /* Send a SOCKS4 connect request */ if (tor_addr_family(&conn->addr) != AF_INET) { log_warn(LD_NET, "SOCKS4 client is incompatible with IPv6"); return -1; } + { /* If we are here because we are trying to connect to a + pluggable transport proxy, check if we have any SOCKS + arguments to transmit. If we do, compress all arguments to + a single string in 'socks_args_string': */ + + if (get_proxy_type() == PROXY_PLUGGABLE) { + socks_args_string = + pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port); + if (socks_args_string) + log_debug(LD_NET, "Sending out '%s' as our SOCKS argument string.", + socks_args_string); + } + } + + { /* Figure out the buffer size we need for the SOCKS message: */ + + buf_size = SOCKS4_STANDARD_BUFFER_SIZE; + + /* If we have a SOCKS argument string, consider its size when + calculating the buffer size: */ + if (socks_args_string) + buf_size += strlen(socks_args_string); + } + + buf = tor_malloc_zero(buf_size); + ip4addr = tor_addr_to_ipv4n(&conn->addr); portn = htons(conn->port); @@ -1652,9 +1756,23 @@ connection_proxy_connect(connection_t *conn, int type) buf[1] = SOCKS_COMMAND_CONNECT; /* command */ memcpy(buf + 2, &portn, 2); /* port */ memcpy(buf + 4, &ip4addr, 4); /* addr */ - buf[8] = 0; /* userid (empty) */ - connection_write_to_buf((char *)buf, sizeof(buf), conn); + /* Next packet field is the userid. If we have pluggable + transport SOCKS arguments, we have to embed them + there. Otherwise, we use an empty userid. */ + if (socks_args_string) { /* place the SOCKS args string: */ + tor_assert(strlen(socks_args_string) > 0); + tor_assert(buf_size >= + SOCKS4_STANDARD_BUFFER_SIZE + strlen(socks_args_string)); + strlcpy((char *)buf + 8, socks_args_string, buf_size - 8); + tor_free(socks_args_string); + } else { + buf[8] = 0; /* no userid */ + } + + connection_write_to_buf((char *)buf, buf_size, conn); + tor_free(buf); + conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; break; } @@ -1666,8 +1784,13 @@ connection_proxy_connect(connection_t *conn, int type) buf[0] = 5; /* version */ + /* We have to use SOCKS5 authentication, if we have a + Socks5ProxyUsername or if we want to pass arguments to our + pluggable transport proxy: */ + if ((options->Socks5ProxyUsername) || + (get_proxy_type() == PROXY_PLUGGABLE && + (get_socks_args_by_bridge_addrport(&conn->addr, conn->port)))) { /* number of auth methods */ - if (options->Socks5ProxyUsername) { buf[1] = 2; buf[2] = 0x00; /* no authentication */ buf[3] = 0x02; /* rfc1929 Username/Passwd auth */ @@ -1861,15 +1984,49 @@ connection_read_proxy_handshake(connection_t *conn) unsigned char buf[1024]; size_t reqsize, usize, psize; const char *user, *pass; + char *socks_args_string = NULL; + + if (get_proxy_type() == PROXY_PLUGGABLE) { + socks_args_string = + pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port); + if (!socks_args_string) { + log_warn(LD_NET, "Could not create SOCKS args string."); + ret = -1; + break; + } + + log_debug(LD_NET, "SOCKS5 arguments: %s", socks_args_string); + tor_assert(strlen(socks_args_string) > 0); + tor_assert(strlen(socks_args_string) <= MAX_SOCKS5_AUTH_SIZE_TOTAL); + + if (strlen(socks_args_string) > MAX_SOCKS5_AUTH_FIELD_SIZE) { + user = socks_args_string; + usize = MAX_SOCKS5_AUTH_FIELD_SIZE; + pass = socks_args_string + MAX_SOCKS5_AUTH_FIELD_SIZE; + psize = strlen(socks_args_string) - MAX_SOCKS5_AUTH_FIELD_SIZE; + } else { + user = socks_args_string; + usize = strlen(socks_args_string); + pass = "\0"; + psize = 1; + } + } else if (get_options()->Socks5ProxyUsername) { + user = get_options()->Socks5ProxyUsername; + pass = get_options()->Socks5ProxyPassword; + tor_assert(user && pass); + usize = strlen(user); + psize = strlen(pass); + } else { + log_err(LD_BUG, "We entered %s for no reason!", __func__); + tor_fragile_assert(); + ret = -1; + break; + } - user = get_options()->Socks5ProxyUsername; - pass = get_options()->Socks5ProxyPassword; - tor_assert(user && pass); - - /* XXX len of user and pass must be <= 255 !!! */ - usize = strlen(user); - psize = strlen(pass); - tor_assert(usize <= 255 && psize <= 255); + /* Username and password lengths should have been checked + above and during torrc parsing. */ + tor_assert(usize <= MAX_SOCKS5_AUTH_FIELD_SIZE && + psize <= MAX_SOCKS5_AUTH_FIELD_SIZE); reqsize = 3 + usize + psize; buf[0] = 1; /* negotiation version */ @@ -1878,6 +2035,9 @@ connection_read_proxy_handshake(connection_t *conn) buf[2 + usize] = psize; memcpy(buf + 3 + usize, pass, psize); + if (socks_args_string) + tor_free(socks_args_string); + connection_write_to_buf((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK; @@ -2430,6 +2590,35 @@ record_num_bytes_transferred(connection_t *conn, #endif #ifndef USE_BUFFEREVENTS +/** Last time at which the global or relay buckets were emptied in msec + * since midnight. */ +static uint32_t global_relayed_read_emptied = 0, + global_relayed_write_emptied = 0, + global_read_emptied = 0, + global_write_emptied = 0; + +/** Helper: convert given <b>tvnow</b> time value to milliseconds since + * midnight. */ +static uint32_t +msec_since_midnight(const struct timeval *tvnow) +{ + return (uint32_t)(((tvnow->tv_sec % 86400L) * 1000L) + + ((uint32_t)tvnow->tv_usec / (uint32_t)1000L)); +} + +/** Check if a bucket which had <b>tokens_before</b> tokens and which got + * <b>tokens_removed</b> tokens removed at timestamp <b>tvnow</b> has run + * out of tokens, and if so, note the milliseconds since midnight in + * <b>timestamp_var</b> for the next TB_EMPTY event. */ +void +connection_buckets_note_empty_ts(uint32_t *timestamp_var, + int tokens_before, size_t tokens_removed, + const struct timeval *tvnow) +{ + if (tokens_before > 0 && (uint32_t)tokens_before <= tokens_removed) + *timestamp_var = msec_since_midnight(tvnow); +} + /** We just read <b>num_read</b> and wrote <b>num_written</b> bytes * onto <b>conn</b>. Decrement buckets appropriately. */ static void @@ -2452,6 +2641,30 @@ connection_buckets_decrement(connection_t *conn, time_t now, if (!connection_is_rate_limited(conn)) return; /* local IPs are free */ + /* If one or more of our token buckets ran dry just now, note the + * timestamp for TB_EMPTY events. */ + if (get_options()->TestingEnableTbEmptyEvent) { + struct timeval tvnow; + tor_gettimeofday_cached(&tvnow); + if (connection_counts_as_relayed_traffic(conn, now)) { + connection_buckets_note_empty_ts(&global_relayed_read_emptied, + global_relayed_read_bucket, num_read, &tvnow); + connection_buckets_note_empty_ts(&global_relayed_write_emptied, + global_relayed_write_bucket, num_written, &tvnow); + } + connection_buckets_note_empty_ts(&global_read_emptied, + global_read_bucket, num_read, &tvnow); + connection_buckets_note_empty_ts(&global_write_emptied, + global_write_bucket, num_written, &tvnow); + if (connection_speaks_cells(conn) && conn->state == OR_CONN_STATE_OPEN) { + or_connection_t *or_conn = TO_OR_CONN(conn); + connection_buckets_note_empty_ts(&or_conn->read_emptied_time, + or_conn->read_bucket, num_read, &tvnow); + connection_buckets_note_empty_ts(&or_conn->write_emptied_time, + or_conn->write_bucket, num_written, &tvnow); + } + } + if (connection_counts_as_relayed_traffic(conn, now)) { global_relayed_read_bucket -= (int)num_read; global_relayed_write_bucket -= (int)num_written; @@ -2471,6 +2684,9 @@ connection_consider_empty_read_buckets(connection_t *conn) { const char *reason; + if (!connection_is_rate_limited(conn)) + return; /* Always okay. */ + if (global_read_bucket <= 0) { reason = "global read bucket exhausted. Pausing."; } else if (connection_counts_as_relayed_traffic(conn, approx_time()) && @@ -2483,9 +2699,6 @@ connection_consider_empty_read_buckets(connection_t *conn) } else return; /* all good, no need to stop it */ - if (conn->type == CONN_TYPE_CPUWORKER) - return; /* Always okay. */ - LOG_FN_CONN(conn, (LOG_DEBUG, LD_NET, "%s", reason)); conn->read_blocked_on_bw = 1; connection_stop_reading(conn); @@ -2498,6 +2711,9 @@ connection_consider_empty_write_buckets(connection_t *conn) { const char *reason; + if (!connection_is_rate_limited(conn)) + return; /* Always okay. */ + if (global_write_bucket <= 0) { reason = "global write bucket exhausted. Pausing."; } else if (connection_counts_as_relayed_traffic(conn, approx_time()) && @@ -2510,9 +2726,6 @@ connection_consider_empty_write_buckets(connection_t *conn) } else return; /* all good, no need to stop it */ - if (conn->type == CONN_TYPE_CPUWORKER) - return; /* Always okay. */ - LOG_FN_CONN(conn, (LOG_DEBUG, LD_NET, "%s", reason)); conn->write_blocked_on_bw = 1; connection_stop_writing(conn); @@ -2564,6 +2777,28 @@ connection_bucket_refill_helper(int *bucket, int rate, int burst, } } +/** Helper: return the time in milliseconds since <b>last_empty_time</b> + * when a bucket ran empty that previously had <b>tokens_before</b> tokens + * now has <b>tokens_after</b> tokens after refilling at timestamp + * <b>tvnow</b>, capped at <b>milliseconds_elapsed</b> milliseconds since + * last refilling that bucket. Return 0 if the bucket has not been empty + * since the last refill or has not been refilled. */ +uint32_t +bucket_millis_empty(int tokens_before, uint32_t last_empty_time, + int tokens_after, int milliseconds_elapsed, + const struct timeval *tvnow) +{ + uint32_t result = 0, refilled; + if (tokens_before <= 0 && tokens_after > tokens_before) { + refilled = msec_since_midnight(tvnow); + result = (uint32_t)((refilled + 86400L * 1000L - last_empty_time) % + (86400L * 1000L)); + if (result > (uint32_t)milliseconds_elapsed) + result = (uint32_t)milliseconds_elapsed; + } + return result; +} + /** Time has passed; increment buckets appropriately. */ void connection_bucket_refill(int milliseconds_elapsed, time_t now) @@ -2572,6 +2807,12 @@ connection_bucket_refill(int milliseconds_elapsed, time_t now) smartlist_t *conns = get_connection_array(); int bandwidthrate, bandwidthburst, relayrate, relayburst; + int prev_global_read = global_read_bucket; + int prev_global_write = global_write_bucket; + int prev_relay_read = global_relayed_read_bucket; + int prev_relay_write = global_relayed_write_bucket; + struct timeval tvnow; /*< Only used if TB_EMPTY events are enabled. */ + bandwidthrate = (int)options->BandwidthRate; bandwidthburst = (int)options->BandwidthBurst; @@ -2606,12 +2847,42 @@ connection_bucket_refill(int milliseconds_elapsed, time_t now) milliseconds_elapsed, "global_relayed_write_bucket"); + /* If buckets were empty before and have now been refilled, tell any + * interested controllers. */ + if (get_options()->TestingEnableTbEmptyEvent) { + uint32_t global_read_empty_time, global_write_empty_time, + relay_read_empty_time, relay_write_empty_time; + tor_gettimeofday_cached(&tvnow); + global_read_empty_time = bucket_millis_empty(prev_global_read, + global_read_emptied, global_read_bucket, + milliseconds_elapsed, &tvnow); + global_write_empty_time = bucket_millis_empty(prev_global_write, + global_write_emptied, global_write_bucket, + milliseconds_elapsed, &tvnow); + control_event_tb_empty("GLOBAL", global_read_empty_time, + global_write_empty_time, milliseconds_elapsed); + relay_read_empty_time = bucket_millis_empty(prev_relay_read, + global_relayed_read_emptied, + global_relayed_read_bucket, + milliseconds_elapsed, &tvnow); + relay_write_empty_time = bucket_millis_empty(prev_relay_write, + global_relayed_write_emptied, + global_relayed_write_bucket, + milliseconds_elapsed, &tvnow); + control_event_tb_empty("RELAY", relay_read_empty_time, + relay_write_empty_time, milliseconds_elapsed); + } + /* refill the per-connection buckets */ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (connection_speaks_cells(conn)) { or_connection_t *or_conn = TO_OR_CONN(conn); int orbandwidthrate = or_conn->bandwidthrate; int orbandwidthburst = or_conn->bandwidthburst; + + int prev_conn_read = or_conn->read_bucket; + int prev_conn_write = or_conn->write_bucket; + if (connection_bucket_should_increase(or_conn->read_bucket, or_conn)) { connection_bucket_refill_helper(&or_conn->read_bucket, orbandwidthrate, @@ -2626,6 +2897,27 @@ connection_bucket_refill(int milliseconds_elapsed, time_t now) milliseconds_elapsed, "or_conn->write_bucket"); } + + /* If buckets were empty before and have now been refilled, tell any + * interested controllers. */ + if (get_options()->TestingEnableTbEmptyEvent) { + char *bucket; + uint32_t conn_read_empty_time, conn_write_empty_time; + tor_asprintf(&bucket, "ORCONN ID="U64_FORMAT, + U64_PRINTF_ARG(or_conn->base_.global_identifier)); + conn_read_empty_time = bucket_millis_empty(prev_conn_read, + or_conn->read_emptied_time, + or_conn->read_bucket, + milliseconds_elapsed, &tvnow); + conn_write_empty_time = bucket_millis_empty(prev_conn_write, + or_conn->write_emptied_time, + or_conn->write_bucket, + milliseconds_elapsed, &tvnow); + control_event_tb_empty(bucket, conn_read_empty_time, + conn_write_empty_time, + milliseconds_elapsed); + tor_free(bucket); + } } if (conn->read_blocked_on_bw == 1 /* marked to turn reading back on now */ @@ -2782,6 +3074,8 @@ connection_handle_read_impl(connection_t *conn) switch (conn->type) { case CONN_TYPE_OR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_OR); + case CONN_TYPE_EXT_OR_LISTENER: + return connection_handle_listener_read(conn, CONN_TYPE_EXT_OR); case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: case CONN_TYPE_AP_NATD_LISTENER: @@ -3034,14 +3328,37 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* change *max_to_read */ *max_to_read = at_most - n_read; - /* Update edge_conn->n_read */ + /* Update edge_conn->n_read and ocirc->n_read_circ_bw */ if (conn->type == CONN_TYPE_AP) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + circuit_t *circ = circuit_get_by_edge_conn(edge_conn); + origin_circuit_t *ocirc; + /* Check for overflow: */ if (PREDICT_LIKELY(UINT32_MAX - edge_conn->n_read > n_read)) edge_conn->n_read += (int)n_read; else edge_conn->n_read = UINT32_MAX; + + if (circ && CIRCUIT_IS_ORIGIN(circ)) { + ocirc = TO_ORIGIN_CIRCUIT(circ); + if (PREDICT_LIKELY(UINT32_MAX - ocirc->n_read_circ_bw > n_read)) + ocirc->n_read_circ_bw += (int)n_read; + else + ocirc->n_read_circ_bw = UINT32_MAX; + } + } + + /* If CONN_BW events are enabled, update conn->n_read_conn_bw for + * OR/DIR/EXIT connections, checking for overflow. */ + if (get_options()->TestingEnableConnBwEvent && + (conn->type == CONN_TYPE_OR || + conn->type == CONN_TYPE_DIR || + conn->type == CONN_TYPE_EXIT)) { + if (PREDICT_LIKELY(UINT32_MAX - conn->n_read_conn_bw > n_read)) + conn->n_read_conn_bw += (int)n_read; + else + conn->n_read_conn_bw = UINT32_MAX; } } @@ -3294,8 +3611,8 @@ connection_outbuf_too_full(connection_t *conn) /** Try to flush more bytes onto <b>conn</b>-\>s. * - * This function gets called either from conn_write() in main.c - * when poll() has declared that conn wants to write, or below + * This function gets called either from conn_write_callback() in main.c + * when libevent tells us that conn wants to write, or below * from connection_write_to_buf() when an entire TLS record is ready. * * Update <b>conn</b>-\>timestamp_lastwritten to now, and call flush_buf @@ -3481,12 +3798,34 @@ connection_handle_write_impl(connection_t *conn, int force) if (n_written && conn->type == CONN_TYPE_AP) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + circuit_t *circ = circuit_get_by_edge_conn(edge_conn); + origin_circuit_t *ocirc; /* Check for overflow: */ if (PREDICT_LIKELY(UINT32_MAX - edge_conn->n_written > n_written)) edge_conn->n_written += (int)n_written; else edge_conn->n_written = UINT32_MAX; + + if (circ && CIRCUIT_IS_ORIGIN(circ)) { + ocirc = TO_ORIGIN_CIRCUIT(circ); + if (PREDICT_LIKELY(UINT32_MAX - ocirc->n_written_circ_bw > n_written)) + ocirc->n_written_circ_bw += (int)n_written; + else + ocirc->n_written_circ_bw = UINT32_MAX; + } + } + + /* If CONN_BW events are enabled, update conn->n_written_conn_bw for + * OR/DIR/EXIT connections, checking for overflow. */ + if (n_written && get_options()->TestingEnableConnBwEvent && + (conn->type == CONN_TYPE_OR || + conn->type == CONN_TYPE_DIR || + conn->type == CONN_TYPE_EXIT)) { + if (PREDICT_LIKELY(UINT32_MAX - conn->n_written_conn_bw > n_written)) + conn->n_written_conn_bw += (int)n_written; + else + conn->n_written_conn_bw = UINT32_MAX; } connection_buckets_decrement(conn, approx_time(), n_read, n_written); @@ -3572,9 +3911,9 @@ connection_flush(connection_t *conn) * it all, so we don't end up with many megabytes of controller info queued at * once. */ -void -connection_write_to_buf_impl_(const char *string, size_t len, - connection_t *conn, int zlib) +MOCK_IMPL(void, +connection_write_to_buf_impl_,(const char *string, size_t len, + connection_t *conn, int zlib)) { /* XXXX This function really needs to return -1 on failure. */ int r; @@ -3814,6 +4153,7 @@ int connection_is_listener(connection_t *conn) { if (conn->type == CONN_TYPE_OR_LISTENER || + conn->type == CONN_TYPE_EXT_OR_LISTENER || conn->type == CONN_TYPE_AP_LISTENER || conn->type == CONN_TYPE_AP_TRANS_LISTENER || conn->type == CONN_TYPE_AP_DNS_LISTENER || @@ -3836,6 +4176,7 @@ connection_state_is_open(connection_t *conn) return 0; if ((conn->type == CONN_TYPE_OR && conn->state == OR_CONN_STATE_OPEN) || + (conn->type == CONN_TYPE_EXT_OR) || (conn->type == CONN_TYPE_AP && conn->state == AP_CONN_STATE_OPEN) || (conn->type == CONN_TYPE_EXIT && conn->state == EXIT_CONN_STATE_OPEN) || (conn->type == CONN_TYPE_CONTROL && @@ -4005,6 +4346,8 @@ connection_process_inbuf(connection_t *conn, int package_partial) switch (conn->type) { case CONN_TYPE_OR: return connection_or_process_inbuf(TO_OR_CONN(conn)); + case CONN_TYPE_EXT_OR: + return connection_ext_or_process_inbuf(TO_OR_CONN(conn)); case CONN_TYPE_EXIT: case CONN_TYPE_AP: return connection_edge_process_inbuf(TO_EDGE_CONN(conn), @@ -4065,6 +4408,8 @@ connection_finished_flushing(connection_t *conn) switch (conn->type) { case CONN_TYPE_OR: return connection_or_finished_flushing(TO_OR_CONN(conn)); + case CONN_TYPE_EXT_OR: + return connection_ext_or_finished_flushing(TO_OR_CONN(conn)); case CONN_TYPE_AP: case CONN_TYPE_EXIT: return connection_edge_finished_flushing(TO_EDGE_CONN(conn)); @@ -4120,6 +4465,7 @@ connection_reached_eof(connection_t *conn) { switch (conn->type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: return connection_or_reached_eof(TO_OR_CONN(conn)); case CONN_TYPE_AP: case CONN_TYPE_EXIT: @@ -4206,6 +4552,7 @@ assert_connection_ok(connection_t *conn, time_t now) switch (conn->type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: tor_assert(conn->magic == OR_CONNECTION_MAGIC); break; case CONN_TYPE_AP: @@ -4311,6 +4658,10 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(conn->state >= OR_CONN_STATE_MIN_); tor_assert(conn->state <= OR_CONN_STATE_MAX_); break; + case CONN_TYPE_EXT_OR: + tor_assert(conn->state >= EXT_OR_CONN_STATE_MIN_); + tor_assert(conn->state <= EXT_OR_CONN_STATE_MAX_); + break; case CONN_TYPE_EXIT: tor_assert(conn->state >= EXIT_CONN_STATE_MIN_); tor_assert(conn->state <= EXIT_CONN_STATE_MAX_); @@ -4372,7 +4723,7 @@ get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, options->Bridges) { const transport_t *transport = NULL; int r; - r = find_transport_by_bridge_addrport(&conn->addr, conn->port, &transport); + r = get_transport_by_bridge_addrport(&conn->addr, conn->port, &transport); if (r<0) return -1; if (transport) { /* transport found */ @@ -4387,24 +4738,6 @@ get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, return 0; } -/** Returns the global proxy type used by tor. */ -static int -get_proxy_type(void) -{ - const or_options_t *options = get_options(); - - if (options->HTTPSProxy) - return PROXY_CONNECT; - else if (options->Socks4Proxy) - return PROXY_SOCKS4; - else if (options->Socks5Proxy) - return PROXY_SOCKS5; - else if (options->ClientTransportPlugin) - return PROXY_PLUGGABLE; - else - return PROXY_NONE; -} - /** Log a failed connection to a proxy server. * <b>conn</b> is the connection we use the proxy server for. */ void @@ -4461,6 +4794,7 @@ connection_free_all(void) /* Unlink everything from the identity map. */ connection_or_clear_identity_map(); + connection_or_clear_ext_or_id_map(); /* Clear out our list of broken connections */ clear_broken_connection_map(0); diff --git a/src/or/connection.h b/src/or/connection.h index c78fe6e65..4073d9fc9 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -19,7 +19,7 @@ const char *conn_type_to_string(int type); const char *conn_state_to_string(int type, int state); dir_connection_t *dir_connection_new(int socket_family); -or_connection_t *or_connection_new(int socket_family); +or_connection_t *or_connection_new(int type, int socket_family); edge_connection_t *edge_connection_new(int type, int socket_family); entry_connection_t *entry_connection_new(int type, int socket_family); control_connection_t *control_connection_new(int socket_family); @@ -89,6 +89,14 @@ int connection_connect(connection_t *conn, const char *address, const tor_addr_t *addr, uint16_t port, int *socket_error); +/** Maximum size of information that we can fit into SOCKS5 username + or password fields. */ +#define MAX_SOCKS5_AUTH_FIELD_SIZE 255 + +/** Total maximum size of information that we can fit into SOCKS5 + username and password fields. */ +#define MAX_SOCKS5_AUTH_SIZE_TOTAL 2*MAX_SOCKS5_AUTH_FIELD_SIZE + int connection_proxy_connect(connection_t *conn, int type); int connection_read_proxy_handshake(connection_t *conn); void log_failed_proxy_connection(connection_t *conn); @@ -122,8 +130,8 @@ int connection_outbuf_too_full(connection_t *conn); int connection_handle_write(connection_t *conn, int force); int connection_flush(connection_t *conn); -void connection_write_to_buf_impl_(const char *string, size_t len, - connection_t *conn, int zlib); +MOCK_DECL(void, connection_write_to_buf_impl_, + (const char *string, size_t len, connection_t *conn, int zlib)); /* DOCDOC connection_write_to_buf */ static void connection_write_to_buf(const char *string, size_t len, connection_t *conn); @@ -206,5 +214,18 @@ void connection_enable_rate_limiting(connection_t *conn); #define connection_type_uses_bufferevent(c) (0) #endif +#ifdef CONNECTION_PRIVATE +STATIC void connection_free_(connection_t *conn); + +/* Used only by connection.c and test*.c */ +uint32_t bucket_millis_empty(int tokens_before, uint32_t last_empty_time, + int tokens_after, int milliseconds_elapsed, + const struct timeval *tvnow); +void connection_buckets_note_empty_ts(uint32_t *timestamp_var, + int tokens_before, + size_t tokens_removed, + const struct timeval *tvnow); +#endif + #endif diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 3a72110ea..d5969aef9 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -14,6 +14,7 @@ #include "addressmap.h" #include "buffers.h" #include "channel.h" +#include "circpathbias.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" @@ -412,7 +413,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) * that the name resolution that led us to <b>addr</b> will be valid for * <b>ttl</b> seconds. Return -1 on error, or the number of bytes used on * success. */ -/* private */int +STATIC int connected_cell_format_payload(uint8_t *payload_out, const tor_addr_t *addr, uint32_t ttl) @@ -2295,7 +2296,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, * Return -1 in the case where want to send a RELAY_END cell, and < -1 when * we don't. **/ -/* static */ int +STATIC int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, uint8_t *end_reason_out) { diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index ea284cbcf..e3a95ad9e 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -12,6 +12,8 @@ #ifndef TOR_CONNECTION_EDGE_H #define TOR_CONNECTION_EDGE_H +#include "testsupport.h" + #define connection_mark_unattached_ap(conn, endreason) \ connection_mark_unattached_ap_((conn), (endreason), __LINE__, SHORT_FILE__) @@ -130,9 +132,9 @@ typedef struct begin_cell_t { unsigned is_begindir : 1; } begin_cell_t; -int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, +STATIC int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, uint8_t *end_reason_out); -int connected_cell_format_payload(uint8_t *payload_out, +STATIC int connected_cell_format_payload(uint8_t *payload_out, const tor_addr_t *addr, uint32_t ttl); #endif diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 3d16e1453..089de93f7 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -37,7 +37,7 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" - +#include "ext_orport.h" #ifdef USE_BUFFEREVENTS #include <event2/bufferevent_ssl.h> #endif @@ -75,6 +75,10 @@ static void connection_or_handle_event_cb(struct bufferevent *bufev, * they form a linked list, with next_with_same_id as the next pointer. */ static digestmap_t *orconn_identity_map = NULL; +/** Global map between Extended ORPort identifiers and OR + * connections. */ +static digestmap_t *orconn_ext_or_id_map = NULL; + /** If conn is listed in orconn_identity_map, remove it, and clear * conn->identity_digest. Otherwise do nothing. */ void @@ -174,6 +178,71 @@ connection_or_set_identity_digest(or_connection_t *conn, const char *digest) #endif } +/** Remove the Extended ORPort identifier of <b>conn</b> from the + * global identifier list. Also, clear the identifier from the + * connection itself. */ +void +connection_or_remove_from_ext_or_id_map(or_connection_t *conn) +{ + or_connection_t *tmp; + if (!orconn_ext_or_id_map) + return; + if (!conn->ext_or_conn_id) + return; + + tmp = digestmap_remove(orconn_ext_or_id_map, conn->ext_or_conn_id); + if (!tor_digest_is_zero(conn->ext_or_conn_id)) + tor_assert(tmp == conn); + + memset(conn->ext_or_conn_id, 0, EXT_OR_CONN_ID_LEN); +} + +/** Return the connection whose ext_or_id is <b>id</b>. Return NULL if no such + * connection is found. */ +or_connection_t * +connection_or_get_by_ext_or_id(const char *id) +{ + if (!orconn_ext_or_id_map) + return NULL; + return digestmap_get(orconn_ext_or_id_map, id); +} + +/** Deallocate the global Extended ORPort identifier list */ +void +connection_or_clear_ext_or_id_map(void) +{ + digestmap_free(orconn_ext_or_id_map, NULL); + orconn_ext_or_id_map = NULL; +} + +/** Creates an Extended ORPort identifier for <b>conn<b/> and deposits + * it into the global list of identifiers. */ +void +connection_or_set_ext_or_identifier(or_connection_t *conn) +{ + char random_id[EXT_OR_CONN_ID_LEN]; + or_connection_t *tmp; + + if (!orconn_ext_or_id_map) + orconn_ext_or_id_map = digestmap_new(); + + /* Remove any previous identifiers: */ + if (conn->ext_or_conn_id && !tor_digest_is_zero(conn->ext_or_conn_id)) + connection_or_remove_from_ext_or_id_map(conn); + + do { + crypto_rand(random_id, sizeof(random_id)); + } while (digestmap_get(orconn_ext_or_id_map, random_id)); + + if (!conn->ext_or_conn_id) + conn->ext_or_conn_id = tor_malloc_zero(EXT_OR_CONN_ID_LEN); + + memcpy(conn->ext_or_conn_id, random_id, EXT_OR_CONN_ID_LEN); + + tmp = digestmap_set(orconn_ext_or_id_map, random_id, conn); + tor_assert(!tmp); +} + /**************************************************************/ /** Map from a string describing what a non-open OR connection was doing when @@ -228,7 +297,7 @@ connection_or_get_state_description(or_connection_t *orconn, const char *conn_state; char tls_state[256]; - tor_assert(conn->type == CONN_TYPE_OR); + tor_assert(conn->type == CONN_TYPE_OR || conn->type == CONN_TYPE_EXT_OR); conn_state = conn_state_to_string(conn->type, conn->state); tor_tls_get_state_description(orconn->tls, tls_state, sizeof(tls_state)); @@ -1077,7 +1146,7 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, return NULL; } - conn = or_connection_new(tor_addr_family(&addr)); + conn = or_connection_new(CONN_TYPE_OR, tor_addr_family(&addr)); /* * Set up conn so it's got all the data we need to remember for channels @@ -1222,8 +1291,8 @@ connection_or_close_for_error(or_connection_t *orconn, int flush) * * Return -1 if <b>conn</b> is broken, else return 0. */ -int -connection_tls_start_handshake(or_connection_t *conn, int receiving) +MOCK_IMPL(int, +connection_tls_start_handshake,(or_connection_t *conn, int receiving)) { channel_listener_t *chan_listener; channel_t *chan; @@ -1480,7 +1549,8 @@ connection_or_handle_event_cb(struct bufferevent *bufev, short event, int connection_or_nonopen_was_started_here(or_connection_t *conn) { - tor_assert(conn->base_.type == CONN_TYPE_OR); + tor_assert(conn->base_.type == CONN_TYPE_OR || + conn->base_.type == CONN_TYPE_EXT_OR); if (!conn->tls) return 1; /* it's still in proxy states or something */ if (conn->handshake_state) @@ -1689,7 +1759,7 @@ connection_tls_finish_handshake(or_connection_t *conn) digest_rcvd) < 0) return -1; - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); if (tor_tls_used_v1_handshake(conn->tls)) { conn->link_proto = 1; @@ -1723,7 +1793,7 @@ connection_or_launch_v3_or_handshake(or_connection_t *conn) tor_assert(connection_or_nonopen_was_started_here(conn)); tor_assert(tor_tls_received_v3_certificate(conn->tls)); - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); connection_or_change_state(conn, OR_CONN_STATE_OR_HANDSHAKING_V3); if (connection_init_or_handshake_state(conn, 1) < 0) @@ -1956,7 +2026,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) if (conn->chan) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); channel_tls_handle_var_cell(var_cell, conn); var_cell_free(var_cell); } else { @@ -1972,7 +2042,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) if (conn->chan) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); connection_fetch_from_buf(buf, cell_network_size, TO_CONN(conn)); /* retrieve cell info from buf (create the host-order struct from the diff --git a/src/or/connection_or.h b/src/or/connection_or.h index 85e68f1a3..8d9302893 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -45,7 +45,8 @@ void connection_or_close_for_error(or_connection_t *orconn, int flush); void connection_or_report_broken_states(int severity, int domain); -int connection_tls_start_handshake(or_connection_t *conn, int receiving); +MOCK_DECL(int,connection_tls_start_handshake,(or_connection_t *conn, + int receiving)); int connection_tls_continue_handshake(or_connection_t *conn); int connection_init_or_handshake_state(or_connection_t *conn, diff --git a/src/or/control.c b/src/or/control.c index a88de12d6..49212de65 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -19,6 +19,7 @@ #include "circuitlist.h" #include "circuitstats.h" #include "circuituse.h" +#include "command.h" #include "config.h" #include "confparse.h" #include "connection.h" @@ -52,46 +53,13 @@ * finished authentication and is accepting commands. */ #define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) -/* Recognized asynchronous event types. It's okay to expand this list - * because it is used both as a list of v0 event types, and as indices - * into the bitfield to determine which controllers want which events. - */ -#define EVENT_MIN_ 0x0001 -#define EVENT_CIRCUIT_STATUS 0x0001 -#define EVENT_STREAM_STATUS 0x0002 -#define EVENT_OR_CONN_STATUS 0x0003 -#define EVENT_BANDWIDTH_USED 0x0004 -#define EVENT_CIRCUIT_STATUS_MINOR 0x0005 -#define EVENT_NEW_DESC 0x0006 -#define EVENT_DEBUG_MSG 0x0007 -#define EVENT_INFO_MSG 0x0008 -#define EVENT_NOTICE_MSG 0x0009 -#define EVENT_WARN_MSG 0x000A -#define EVENT_ERR_MSG 0x000B -#define EVENT_ADDRMAP 0x000C -// #define EVENT_AUTHDIR_NEWDESCS 0x000D -#define EVENT_DESCCHANGED 0x000E -// #define EVENT_NS 0x000F -#define EVENT_STATUS_CLIENT 0x0010 -#define EVENT_STATUS_SERVER 0x0011 -#define EVENT_STATUS_GENERAL 0x0012 -#define EVENT_GUARD 0x0013 -#define EVENT_STREAM_BANDWIDTH_USED 0x0014 -#define EVENT_CLIENTS_SEEN 0x0015 -#define EVENT_NEWCONSENSUS 0x0016 -#define EVENT_BUILDTIMEOUT_SET 0x0017 -#define EVENT_SIGNAL 0x0018 -#define EVENT_CONF_CHANGED 0x0019 -#define EVENT_MAX_ 0x0019 -/* If EVENT_MAX_ ever hits 0x0020, we need to make the mask wider. */ - /** Bitfield: The bit 1<<e is set if <b>any</b> open control * connection is interested in events of type <b>e</b>. We use this * so that we can decide to skip generating event messages that nobody * has interest in without having to walk over the global connection * list to find out. **/ -typedef uint32_t event_mask_t; +typedef uint64_t event_mask_t; /** An event mask of all the events that any controller is interested in * receiving. */ @@ -103,7 +71,7 @@ static int disable_log_messages = 0; /** Macro: true if any control connection is interested in events of type * <b>e</b>. */ #define EVENT_IS_INTERESTING(e) \ - (global_event_mask & (1<<(e))) + (!! (global_event_mask & (((uint64_t)1)<<(e)))) /** If we're using cookie-type authentication, how long should our cookies be? */ @@ -115,7 +83,7 @@ static int authentication_cookie_is_set = 0; /** If authentication_cookie_is_set, a secret cookie that we've stored to disk * and which we're using to authenticate controllers. (If the controller can * read it off disk, it has permission to connect.) */ -static char authentication_cookie[AUTHENTICATION_COOKIE_LEN]; +static uint8_t *authentication_cookie = NULL; #define SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT \ "Tor safe cookie authentication server-to-controller hash" @@ -130,15 +98,6 @@ static char authentication_cookie[AUTHENTICATION_COOKIE_LEN]; * of this so we can respond to getinfo status/bootstrap-phase queries. */ static char last_sent_bootstrap_message[BOOTSTRAP_MSG_LEN]; -/** Flag for event_format_t. Indicates that we should use the one standard - format. - */ -#define ALL_FORMATS 1 - -/** Bit field of flags to select how to format a controller event. Recognized - * flag is ALL_FORMATS. */ -typedef int event_format_t; - static void connection_printf_to_buf(control_connection_t *conn, const char *format, ...) CHECK_PRINTF(2,3); @@ -232,6 +191,20 @@ log_severity_to_event(int severity) } } +/** Helper: clear bandwidth counters of all origin circuits. */ +static void +clear_circ_bw_fields(void) +{ + circuit_t *circ; + origin_circuit_t *ocirc; + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { + if (!CIRCUIT_IS_ORIGIN(circ)) + continue; + ocirc = TO_ORIGIN_CIRCUIT(circ); + ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; + } +} + /** Set <b>global_event_mask*</b> to the bitwise OR of each live control * connection's event_mask field. */ void @@ -257,8 +230,8 @@ control_update_global_event_mask(void) * we want to hear...*/ control_adjust_event_log_severity(); - /* ...then, if we've started logging stream bw, clear the appropriate - * fields. */ + /* ...then, if we've started logging stream or circ bw, clear the + * appropriate fields. */ if (! (old_mask & EVENT_STREAM_BANDWIDTH_USED) && (new_mask & EVENT_STREAM_BANDWIDTH_USED)) { SMARTLIST_FOREACH(conns, connection_t *, conn, @@ -269,6 +242,10 @@ control_update_global_event_mask(void) } }); } + if (! (old_mask & EVENT_CIRC_BANDWIDTH_USED) && + (new_mask & EVENT_CIRC_BANDWIDTH_USED)) { + clear_circ_bw_fields(); + } } /** Adjust the log severities that result in control_event_logmsg being called @@ -334,7 +311,7 @@ connection_write_str_to_buf(const char *s, control_connection_t *conn) * the end. Replace all LF characters sequences with CRLF. Return the number * of bytes in *<b>out</b>. */ -/* static */ size_t +STATIC size_t write_escaped_data(const char *data, size_t len, char **out) { size_t sz_out = len+8; @@ -382,7 +359,7 @@ write_escaped_data(const char *data, size_t len, char **out) * that appears at the start of a line, and replacing all CRLF sequences * with LF. Return the number of * bytes in *<b>out</b>. */ -/* static */ size_t +STATIC size_t read_escaped_data(const char *data, size_t len, char **out) { char *outp; @@ -592,9 +569,9 @@ send_control_done(control_connection_t *conn) * * The EXTENDED_FORMAT and NONEXTENDED_FORMAT flags behave similarly with * respect to the EXTENDED_EVENTS feature. */ -static void -send_control_event_string(uint16_t event, event_format_t which, - const char *msg) +MOCK_IMPL(STATIC void, +send_control_event_string,(uint16_t event, event_format_t which, + const char *msg)) { smartlist_t *conns = get_connection_array(); (void)which; @@ -958,6 +935,11 @@ static const struct control_event_t control_event_table[] = { { EVENT_BUILDTIMEOUT_SET, "BUILDTIMEOUT_SET" }, { EVENT_SIGNAL, "SIGNAL" }, { EVENT_CONF_CHANGED, "CONF_CHANGED"}, + { EVENT_CONN_BW, "CONN_BW" }, + { EVENT_CELL_STATS, "CELL_STATS" }, + { EVENT_TB_EMPTY, "TB_EMPTY" }, + { EVENT_CIRC_BANDWIDTH_USED, "CIRC_BW" }, + { EVENT_TRANSPORT_LAUNCHED, "TRANSPORT_LAUNCHED" }, { 0, NULL }, }; @@ -1447,7 +1429,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, } else if (!strcmp(question, "config-defaults-file")) { *answer = tor_strdup(get_torrc_fname(1)); } else if (!strcmp(question, "config-text")) { - *answer = options_dump(get_options(), 1); + *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL); } else if (!strcmp(question, "info/names")) { *answer = list_getinfo_options(); } else if (!strcmp(question, "dormant")) { @@ -1572,7 +1554,8 @@ munge_extrainfo_into_routerinfo(const char *ri_body, if (!(cp = tor_memstr(ei_body, ei_len, kwd))) continue; ++cp; - eol = memchr(cp, '\n', ei_len - (cp-ei_body)); + if (!(eol = memchr(cp, '\n', ei_len - (cp-ei_body)))) + continue; memcpy(outp, cp, eol-cp+1); outp += eol-cp+1; } @@ -1927,7 +1910,7 @@ getinfo_helper_events(control_connection_t *control_conn, if (!strcmp(question, "circuit-status")) { circuit_t *circ_; smartlist_t *status = smartlist_new(); - for (circ_ = circuit_get_global_list_(); circ_; circ_ = circ_->next) { + TOR_LIST_FOREACH(circ_, circuit_get_global_list(), head) { origin_circuit_t *circ; char *circdesc; const char *state; @@ -3181,6 +3164,30 @@ handle_control_usefeature(control_connection_t *conn, return 0; } +/** Implementation for the DROPGUARDS command. */ +static int +handle_control_dropguards(control_connection_t *conn, + uint32_t len, + const char *body) +{ + smartlist_t *args; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + args = smartlist_new(); + smartlist_split_string(args, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + + if (smartlist_len(args)) { + connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n"); + } else { + remove_all_entry_guards(); + send_control_done(conn); + } + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + return 0; +} + /** Called when <b>conn</b> has no more bytes left on its outbuf. */ int connection_control_finished_flushing(control_connection_t *conn) @@ -3480,6 +3487,9 @@ connection_control_process_inbuf(control_connection_t *conn) } else if (!strcasecmp(conn->incoming_cmd, "AUTHCHALLENGE")) { if (handle_control_authchallenge(conn, cmd_data_len, args)) return -1; + } else if (!strcasecmp(conn->incoming_cmd, "DROPGUARDS")) { + if (handle_control_dropguards(conn, cmd_data_len, args)) + return -1; } else { connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", conn->incoming_cmd); @@ -3847,17 +3857,17 @@ control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, } ncircs += connection_or_get_num_circuits(conn); if (ncircs && (tp == OR_CONN_EVENT_FAILED || tp == OR_CONN_EVENT_CLOSED)) { - tor_snprintf(ncircs_buf, sizeof(ncircs_buf), "%sNCIRCS=%d", - reason ? " " : "", ncircs); + tor_snprintf(ncircs_buf, sizeof(ncircs_buf), " NCIRCS=%d", ncircs); } orconn_target_get_name(name, sizeof(name), conn); send_control_event(EVENT_OR_CONN_STATUS, ALL_FORMATS, - "650 ORCONN %s %s %s%s%s\r\n", + "650 ORCONN %s %s%s%s%s ID="U64_FORMAT"\r\n", name, status, - reason ? "REASON=" : "", + reason ? " REASON=" : "", orconn_end_reason_to_control_string(reason), - ncircs_buf); + ncircs_buf, + U64_PRINTF_ARG(conn->base_.global_identifier)); return 0; } @@ -3868,6 +3878,8 @@ control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, int control_event_stream_bandwidth(edge_connection_t *edge_conn) { + circuit_t *circ; + origin_circuit_t *ocirc; if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { if (!edge_conn->n_read && !edge_conn->n_written) return 0; @@ -3878,6 +3890,12 @@ control_event_stream_bandwidth(edge_connection_t *edge_conn) (unsigned long)edge_conn->n_read, (unsigned long)edge_conn->n_written); + circ = circuit_get_by_edge_conn(edge_conn); + if (circ && CIRCUIT_IS_ORIGIN(circ)) { + ocirc = TO_ORIGIN_CIRCUIT(circ); + ocirc->n_read_circ_bw += edge_conn->n_read; + ocirc->n_written_circ_bw += edge_conn->n_written; + } edge_conn->n_written = edge_conn->n_read = 0; } @@ -3915,6 +3933,235 @@ control_event_stream_bandwidth_used(void) return 0; } +/** A second or more has elapsed: tell any interested control connections + * how much bandwidth origin circuits have used. */ +int +control_event_circ_bandwidth_used(void) +{ + circuit_t *circ; + origin_circuit_t *ocirc; + if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) + return 0; + + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { + if (!CIRCUIT_IS_ORIGIN(circ)) + continue; + ocirc = TO_ORIGIN_CIRCUIT(circ); + if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) + continue; + send_control_event(EVENT_CIRC_BANDWIDTH_USED, ALL_FORMATS, + "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu\r\n", + ocirc->global_identifier, + (unsigned long)ocirc->n_read_circ_bw, + (unsigned long)ocirc->n_written_circ_bw); + ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; + } + + return 0; +} + +/** Print out CONN_BW event for a single OR/DIR/EXIT <b>conn</b> and reset + * bandwidth counters. */ +int +control_event_conn_bandwidth(connection_t *conn) +{ + const char *conn_type_str; + if (!get_options()->TestingEnableConnBwEvent || + !EVENT_IS_INTERESTING(EVENT_CONN_BW)) + return 0; + if (!conn->n_read_conn_bw && !conn->n_written_conn_bw) + return 0; + switch (conn->type) { + case CONN_TYPE_OR: + conn_type_str = "OR"; + break; + case CONN_TYPE_DIR: + conn_type_str = "DIR"; + break; + case CONN_TYPE_EXIT: + conn_type_str = "EXIT"; + break; + default: + return 0; + } + send_control_event(EVENT_CONN_BW, ALL_FORMATS, + "650 CONN_BW ID="U64_FORMAT" TYPE=%s " + "READ=%lu WRITTEN=%lu\r\n", + U64_PRINTF_ARG(conn->global_identifier), + conn_type_str, + (unsigned long)conn->n_read_conn_bw, + (unsigned long)conn->n_written_conn_bw); + conn->n_written_conn_bw = conn->n_read_conn_bw = 0; + return 0; +} + +/** A second or more has elapsed: tell any interested control + * connections how much bandwidth connections have used. */ +int +control_event_conn_bandwidth_used(void) +{ + if (get_options()->TestingEnableConnBwEvent && + EVENT_IS_INTERESTING(EVENT_CONN_BW)) { + SMARTLIST_FOREACH(get_connection_array(), connection_t *, conn, + control_event_conn_bandwidth(conn)); + } + return 0; +} + +/** Helper: iterate over cell statistics of <b>circ</b> and sum up added + * cells, removed cells, and waiting times by cell command and direction. + * Store results in <b>cell_stats</b>. Free cell statistics of the + * circuit afterwards. */ +void +sum_up_cell_stats_by_command(circuit_t *circ, cell_stats_t *cell_stats) +{ + memset(cell_stats, 0, sizeof(cell_stats_t)); + SMARTLIST_FOREACH_BEGIN(circ->testing_cell_stats, + testing_cell_stats_entry_t *, ent) { + tor_assert(ent->command <= CELL_COMMAND_MAX_); + if (!ent->removed && !ent->exitward) { + cell_stats->added_cells_appward[ent->command] += 1; + } else if (!ent->removed && ent->exitward) { + cell_stats->added_cells_exitward[ent->command] += 1; + } else if (!ent->exitward) { + cell_stats->removed_cells_appward[ent->command] += 1; + cell_stats->total_time_appward[ent->command] += ent->waiting_time * 10; + } else { + cell_stats->removed_cells_exitward[ent->command] += 1; + cell_stats->total_time_exitward[ent->command] += ent->waiting_time * 10; + } + tor_free(ent); + } SMARTLIST_FOREACH_END(ent); + smartlist_free(circ->testing_cell_stats); + circ->testing_cell_stats = NULL; +} + +/** Helper: append a cell statistics string to <code>event_parts</code>, + * prefixed with <code>key</code>=. Statistics consist of comma-separated + * key:value pairs with lower-case command strings as keys and cell + * numbers or total waiting times as values. A key:value pair is included + * if the entry in <code>include_if_non_zero</code> is not zero, but with + * the (possibly zero) entry from <code>number_to_include</code>. Both + * arrays are expected to have a length of CELL_COMMAND_MAX_ + 1. If no + * entry in <code>include_if_non_zero</code> is positive, no string will + * be added to <code>event_parts</code>. */ +void +append_cell_stats_by_command(smartlist_t *event_parts, const char *key, + const uint64_t *include_if_non_zero, + const uint64_t *number_to_include) +{ + smartlist_t *key_value_strings = smartlist_new(); + int i; + for (i = 0; i <= CELL_COMMAND_MAX_; i++) { + if (include_if_non_zero[i] > 0) { + smartlist_add_asprintf(key_value_strings, "%s:"U64_FORMAT, + cell_command_to_string(i), + U64_PRINTF_ARG(number_to_include[i])); + } + } + if (smartlist_len(key_value_strings) > 0) { + char *joined = smartlist_join_strings(key_value_strings, ",", 0, NULL); + smartlist_add_asprintf(event_parts, "%s=%s", key, joined); + SMARTLIST_FOREACH(key_value_strings, char *, cp, tor_free(cp)); + tor_free(joined); + } + smartlist_free(key_value_strings); +} + +/** Helper: format <b>cell_stats</b> for <b>circ</b> for inclusion in a + * CELL_STATS event and write result string to <b>event_string</b>. */ +void +format_cell_stats(char **event_string, circuit_t *circ, + cell_stats_t *cell_stats) +{ + smartlist_t *event_parts = smartlist_new(); + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + smartlist_add_asprintf(event_parts, "ID=%lu", + (unsigned long)ocirc->global_identifier); + } else if (TO_OR_CIRCUIT(circ)->p_chan) { + or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); + smartlist_add_asprintf(event_parts, "InboundQueue=%lu", + (unsigned long)or_circ->p_circ_id); + smartlist_add_asprintf(event_parts, "InboundConn="U64_FORMAT, + U64_PRINTF_ARG(or_circ->p_chan->global_identifier)); + append_cell_stats_by_command(event_parts, "InboundAdded", + cell_stats->added_cells_appward, + cell_stats->added_cells_appward); + append_cell_stats_by_command(event_parts, "InboundRemoved", + cell_stats->removed_cells_appward, + cell_stats->removed_cells_appward); + append_cell_stats_by_command(event_parts, "InboundTime", + cell_stats->removed_cells_appward, + cell_stats->total_time_appward); + } + if (circ->n_chan) { + smartlist_add_asprintf(event_parts, "OutboundQueue=%lu", + (unsigned long)circ->n_circ_id); + smartlist_add_asprintf(event_parts, "OutboundConn="U64_FORMAT, + U64_PRINTF_ARG(circ->n_chan->global_identifier)); + append_cell_stats_by_command(event_parts, "OutboundAdded", + cell_stats->added_cells_exitward, + cell_stats->added_cells_exitward); + append_cell_stats_by_command(event_parts, "OutboundRemoved", + cell_stats->removed_cells_exitward, + cell_stats->removed_cells_exitward); + append_cell_stats_by_command(event_parts, "OutboundTime", + cell_stats->removed_cells_exitward, + cell_stats->total_time_exitward); + } + *event_string = smartlist_join_strings(event_parts, " ", 0, NULL); + SMARTLIST_FOREACH(event_parts, char *, cp, tor_free(cp)); + smartlist_free(event_parts); +} + +/** A second or more has elapsed: tell any interested control connection + * how many cells have been processed for a given circuit. */ +int +control_event_circuit_cell_stats(void) +{ + circuit_t *circ; + cell_stats_t *cell_stats; + char *event_string; + if (!get_options()->TestingEnableCellStatsEvent || + !EVENT_IS_INTERESTING(EVENT_CELL_STATS)) + return 0; + cell_stats = tor_malloc(sizeof(cell_stats_t));; + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { + if (!circ->testing_cell_stats) + continue; + sum_up_cell_stats_by_command(circ, cell_stats); + format_cell_stats(&event_string, circ, cell_stats); + send_control_event(EVENT_CELL_STATS, ALL_FORMATS, + "650 CELL_STATS %s\r\n", event_string); + tor_free(event_string); + } + tor_free(cell_stats); + return 0; +} + +/** Tokens in <b>bucket</b> have been refilled: the read bucket was empty + * for <b>read_empty_time</b> millis, the write bucket was empty for + * <b>write_empty_time</b> millis, and buckets were last refilled + * <b>milliseconds_elapsed</b> millis ago. Only emit TB_EMPTY event if + * either read or write bucket have been empty before. */ +int +control_event_tb_empty(const char *bucket, uint32_t read_empty_time, + uint32_t write_empty_time, + int milliseconds_elapsed) +{ + if (get_options()->TestingEnableTbEmptyEvent && + EVENT_IS_INTERESTING(EVENT_TB_EMPTY) && + (read_empty_time > 0 || write_empty_time > 0)) { + send_control_event(EVENT_TB_EMPTY, ALL_FORMATS, + "650 TB_EMPTY %s READ=%d WRITTEN=%d " + "LAST=%d\r\n", + bucket, read_empty_time, write_empty_time, + milliseconds_elapsed); + } + return 0; +} + /** A second or more has elapsed: tell any interested control * connections how much bandwidth we used. */ int @@ -4162,32 +4409,26 @@ control_event_newconsensus(const networkstatus_t *consensus) /** Called when we compute a new circuitbuildtimeout */ int -control_event_buildtimeout_set(const circuit_build_times_t *cbt, - buildtimeout_set_event_t type) +control_event_buildtimeout_set(buildtimeout_set_event_t type, + const char *args) { const char *type_string = NULL; - double qnt; if (!control_event_is_interesting(EVENT_BUILDTIMEOUT_SET)) return 0; - qnt = circuit_build_times_quantile_cutoff(); - switch (type) { case BUILDTIMEOUT_SET_EVENT_COMPUTED: type_string = "COMPUTED"; break; case BUILDTIMEOUT_SET_EVENT_RESET: type_string = "RESET"; - qnt = 1.0; break; case BUILDTIMEOUT_SET_EVENT_SUSPENDED: type_string = "SUSPENDED"; - qnt = 1.0; break; case BUILDTIMEOUT_SET_EVENT_DISCARD: type_string = "DISCARD"; - qnt = 1.0; break; case BUILDTIMEOUT_SET_EVENT_RESUME: type_string = "RESUME"; @@ -4198,15 +4439,8 @@ control_event_buildtimeout_set(const circuit_build_times_t *cbt, } send_control_event(EVENT_BUILDTIMEOUT_SET, ALL_FORMATS, - "650 BUILDTIMEOUT_SET %s TOTAL_TIMES=%lu " - "TIMEOUT_MS=%lu XM=%lu ALPHA=%f CUTOFF_QUANTILE=%f " - "TIMEOUT_RATE=%f CLOSE_MS=%lu CLOSE_RATE=%f\r\n", - type_string, (unsigned long)cbt->total_build_times, - (unsigned long)cbt->timeout_ms, - (unsigned long)cbt->Xm, cbt->alpha, qnt, - circuit_build_times_timeout_rate(cbt), - (unsigned long)cbt->close_ms, - circuit_build_times_close_rate(cbt)); + "650 BUILDTIMEOUT_SET %s %s\r\n", + type_string, args); return 0; } @@ -4445,44 +4679,27 @@ get_cookie_file(void) } } -/** Choose a random authentication cookie and write it to disk. - * Anybody who can read the cookie from disk will be considered - * authorized to use the control connection. Return -1 if we can't - * write the file, or 0 on success. */ +/* Initialize the cookie-based authentication system of the + * ControlPort. If <b>enabled</b> is 0, then disable the cookie + * authentication system. */ int -init_cookie_authentication(int enabled) +init_control_cookie_authentication(int enabled) { - char *fname; + char *fname = NULL; + int retval; + if (!enabled) { authentication_cookie_is_set = 0; return 0; } - /* We don't want to generate a new cookie every time we call - * options_act(). One should be enough. */ - if (authentication_cookie_is_set) - return 0; /* all set */ - fname = get_cookie_file(); - crypto_rand(authentication_cookie, AUTHENTICATION_COOKIE_LEN); - authentication_cookie_is_set = 1; - if (write_bytes_to_file(fname, authentication_cookie, - AUTHENTICATION_COOKIE_LEN, 1)) { - log_warn(LD_FS,"Error writing authentication cookie to %s.", - escaped(fname)); - tor_free(fname); - return -1; - } -#ifndef _WIN32 - if (get_options()->CookieAuthFileGroupReadable) { - if (chmod(fname, 0640)) { - log_warn(LD_FS,"Unable to make %s group-readable.", escaped(fname)); - } - } -#endif - + retval = init_cookie_authentication(fname, "", /* no header */ + AUTHENTICATION_COOKIE_LEN, + &authentication_cookie, + &authentication_cookie_is_set); tor_free(fname); - return 0; + return retval; } /** A copy of the process specifier of Tor's owning controller, or @@ -4698,8 +4915,8 @@ control_event_bootstrap(bootstrap_status_t status, int progress) * that indicates a problem. <b>warn</b> gives a hint as to why, and * <b>reason</b> provides an "or_conn_end_reason" tag. */ -void -control_event_bootstrap_problem(const char *warn, int reason) +MOCK_IMPL(void, +control_event_bootstrap_problem, (const char *warn, int reason)) { int status = bootstrap_percent; const char *tag, *summary; @@ -4766,3 +4983,35 @@ control_event_clients_seen(const char *controller_str) "650 CLIENTS_SEEN %s\r\n", controller_str); } +/** A new pluggable transport called <b>transport_name</b> was + * launched on <b>addr</b>:<b>port</b>. <b>mode</b> is either + * "server" or "client" depending on the mode of the pluggable + * transport. + * "650" SP "TRANSPORT_LAUNCHED" SP Mode SP Name SP Address SP Port + */ +void +control_event_transport_launched(const char *mode, const char *transport_name, + tor_addr_t *addr, uint16_t port) +{ + send_control_event(EVENT_TRANSPORT_LAUNCHED, ALL_FORMATS, + "650 TRANSPORT_LAUNCHED %s %s %s %u\r\n", + mode, transport_name, fmt_addr(addr), port); +} + +/** Free any leftover allocated memory of the control.c subsystem. */ +void +control_free_all(void) +{ + if (authentication_cookie) /* Free the auth cookie */ + tor_free(authentication_cookie); +} + +#ifdef TOR_UNIT_TESTS +/* For testing: change the value of global_event_mask */ +void +control_testing_set_global_event_mask(uint64_t mask) +{ + global_event_mask = mask; +} +#endif + diff --git a/src/or/control.h b/src/or/control.h index 61062da2c..c8db643b7 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -50,6 +50,13 @@ int control_event_or_conn_status(or_connection_t *conn, int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written); int control_event_stream_bandwidth(edge_connection_t *edge_conn); int control_event_stream_bandwidth_used(void); +int control_event_circ_bandwidth_used(void); +int control_event_conn_bandwidth(connection_t *conn); +int control_event_conn_bandwidth_used(void); +int control_event_circuit_cell_stats(void); +int control_event_tb_empty(const char *bucket, uint32_t read_empty_time, + uint32_t write_empty_time, + int milliseconds_elapsed); void control_event_logmsg(int severity, uint32_t domain, const char *msg); int control_event_descriptors_changed(smartlist_t *routers); int control_event_address_mapped(const char *from, const char *to, @@ -73,11 +80,11 @@ int control_event_server_status(int severity, const char *format, ...) int control_event_guard(const char *nickname, const char *digest, const char *status); int control_event_conf_changed(const smartlist_t *elements); -int control_event_buildtimeout_set(const circuit_build_times_t *cbt, - buildtimeout_set_event_t type); +int control_event_buildtimeout_set(buildtimeout_set_event_t type, + const char *args); int control_event_signal(uintptr_t signal); -int init_cookie_authentication(int enabled); +int init_control_cookie_authentication(int enabled); smartlist_t *decode_hashed_passwords(config_line_t *passwords); void disable_control_logging(void); void enable_control_logging(void); @@ -85,14 +92,100 @@ void enable_control_logging(void); void monitor_owning_controller_process(const char *process_spec); void control_event_bootstrap(bootstrap_status_t status, int progress); -void control_event_bootstrap_problem(const char *warn, int reason); +MOCK_DECL(void, control_event_bootstrap_problem,(const char *warn, + int reason)); void control_event_clients_seen(const char *controller_str); +void control_event_transport_launched(const char *mode, + const char *transport_name, + tor_addr_t *addr, uint16_t port); + +void control_free_all(void); #ifdef CONTROL_PRIVATE +/* Recognized asynchronous event types. It's okay to expand this list + * because it is used both as a list of v0 event types, and as indices + * into the bitfield to determine which controllers want which events. + */ +#define EVENT_MIN_ 0x0001 +#define EVENT_CIRCUIT_STATUS 0x0001 +#define EVENT_STREAM_STATUS 0x0002 +#define EVENT_OR_CONN_STATUS 0x0003 +#define EVENT_BANDWIDTH_USED 0x0004 +#define EVENT_CIRCUIT_STATUS_MINOR 0x0005 +#define EVENT_NEW_DESC 0x0006 +#define EVENT_DEBUG_MSG 0x0007 +#define EVENT_INFO_MSG 0x0008 +#define EVENT_NOTICE_MSG 0x0009 +#define EVENT_WARN_MSG 0x000A +#define EVENT_ERR_MSG 0x000B +#define EVENT_ADDRMAP 0x000C +/* Exposed above */ +// #define EVENT_AUTHDIR_NEWDESCS 0x000D +#define EVENT_DESCCHANGED 0x000E +/* Exposed above */ +// #define EVENT_NS 0x000F +#define EVENT_STATUS_CLIENT 0x0010 +#define EVENT_STATUS_SERVER 0x0011 +#define EVENT_STATUS_GENERAL 0x0012 +#define EVENT_GUARD 0x0013 +#define EVENT_STREAM_BANDWIDTH_USED 0x0014 +#define EVENT_CLIENTS_SEEN 0x0015 +#define EVENT_NEWCONSENSUS 0x0016 +#define EVENT_BUILDTIMEOUT_SET 0x0017 +#define EVENT_SIGNAL 0x0018 +#define EVENT_CONF_CHANGED 0x0019 +#define EVENT_CONN_BW 0x001A +#define EVENT_CELL_STATS 0x001B +#define EVENT_TB_EMPTY 0x001C +#define EVENT_CIRC_BANDWIDTH_USED 0x001D +#define EVENT_TRANSPORT_LAUNCHED 0x0020 +#define EVENT_MAX_ 0x0020 +/* If EVENT_MAX_ ever hits 0x0040, we need to make the mask into a + * different structure. */ + /* Used only by control.c and test.c */ -size_t write_escaped_data(const char *data, size_t len, char **out); -size_t read_escaped_data(const char *data, size_t len, char **out); +STATIC size_t write_escaped_data(const char *data, size_t len, char **out); +STATIC size_t read_escaped_data(const char *data, size_t len, char **out); +/** Flag for event_format_t. Indicates that we should use the one standard + format. (Other formats previous existed, and are now deprecated) + */ +#define ALL_FORMATS 1 +/** Bit field of flags to select how to format a controller event. Recognized + * flag is ALL_FORMATS. */ +typedef int event_format_t; + +#ifdef TOR_UNIT_TESTS +MOCK_DECL(STATIC void, +send_control_event_string,(uint16_t event, event_format_t which, + const char *msg)); + +void control_testing_set_global_event_mask(uint64_t mask); +#endif + +/** Helper structure: temporarily stores cell statistics for a circuit. */ +typedef struct cell_stats_t { + /** Number of cells added in app-ward direction by command. */ + uint64_t added_cells_appward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells added in exit-ward direction by command. */ + uint64_t added_cells_exitward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells removed in app-ward direction by command. */ + uint64_t removed_cells_appward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells removed in exit-ward direction by command. */ + uint64_t removed_cells_exitward[CELL_COMMAND_MAX_ + 1]; + /** Total waiting time of cells in app-ward direction by command. */ + uint64_t total_time_appward[CELL_COMMAND_MAX_ + 1]; + /** Total waiting time of cells in exit-ward direction by command. */ + uint64_t total_time_exitward[CELL_COMMAND_MAX_ + 1]; +} cell_stats_t; +void sum_up_cell_stats_by_command(circuit_t *circ, + cell_stats_t *cell_stats); +void append_cell_stats_by_command(smartlist_t *event_parts, + const char *key, + const uint64_t *include_if_non_zero, + const uint64_t *number_to_include); +void format_cell_stats(char **event_string, circuit_t *circ, + cell_stats_t *cell_stats); #endif #endif diff --git a/src/or/directory.c b/src/or/directory.c index 3752367c4..12c5b189f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -747,7 +747,7 @@ connection_dir_request_failed(dir_connection_t *conn) conn->base_.address); } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC) { log_info(LD_DIR, "Giving up on downloading microdescriptors from " - " directory server at '%s'; will retry", conn->base_.address); + "directory server at '%s'; will retry", conn->base_.address); connection_dir_download_routerdesc_failed(conn); } } @@ -1387,7 +1387,7 @@ directory_send_command(dir_connection_t *conn, * so it does. Return 0. * Otherwise, return -1. */ -static int +STATIC int parse_http_url(const char *headers, char **url) { char *s, *start, *tmp; @@ -1416,6 +1416,19 @@ parse_http_url(const char *headers, char **url) } } + /* Check if the header is well formed (next sequence + * should be HTTP/1.X\r\n). Assumes we're supporting 1.0? */ + { + unsigned minor_ver; + char ch; + char *e = (char *)eat_whitespace_no_nl(s); + if (2 != tor_sscanf(e, "HTTP/1.%u%c", &minor_ver, &ch)) { + return -1; + } + if (ch != '\r') + return -1; + } + if (s-start < 5 || strcmpstart(start,"/tor/")) { /* need to rewrite it */ *url = tor_malloc(s - start + 5); strlcpy(*url,"/tor", s-start+5); @@ -1626,8 +1639,9 @@ load_downloaded_routers(const char *body, smartlist_t *which, added = router_load_routers_from_string(body, NULL, SAVED_NOWHERE, which, descriptor_digests, buf); - control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, - count_loading_descriptors_progress()); + if (general) + control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, + count_loading_descriptors_progress()); return added; } @@ -2968,7 +2982,9 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, tor_addr_t addr; if (tor_inet_aton((TO_CONN(conn))->address, &in)) { tor_addr_from_ipv4h(&addr, ntohl(in.s_addr)); - geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, time(NULL)); + 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. */ @@ -3725,57 +3741,27 @@ dir_networkstatus_download_failed(smartlist_t *failed, int status_code) } SMARTLIST_FOREACH_END(fp); } -/** Schedule for when servers should download things in general. */ -static const int server_dl_schedule[] = { - 0, 0, 0, 60, 60, 60*2, 60*5, 60*15, INT_MAX -}; -/** Schedule for when clients should download things in general. */ -static const int client_dl_schedule[] = { - 0, 0, 60, 60*5, 60*10, INT_MAX -}; -/** Schedule for when servers should download consensuses. */ -static const int server_consensus_dl_schedule[] = { - 0, 0, 60, 60*5, 60*10, 60*30, 60*30, 60*30, 60*30, 60*30, 60*60, 60*60*2 -}; -/** Schedule for when clients should download consensuses. */ -static const int client_consensus_dl_schedule[] = { - 0, 0, 60, 60*5, 60*10, 60*30, 60*60, 60*60, 60*60, 60*60*3, 60*60*6, 60*60*12 -}; -/** Schedule for when clients should download bridge descriptors. */ -static const int bridge_dl_schedule[] = { - 60*60, 15*60, 15*60, 60*60 -}; - -/** Decide which download schedule we want to use, and then return a - * pointer to it along with a pointer to its length. Helper function for - * download_status_increment_failure() and download_status_reset(). */ -static void -find_dl_schedule_and_len(download_status_t *dls, int server, - const int **schedule, size_t *schedule_len) +/** Decide which download schedule we want to use based on descriptor type + * in <b>dls</b> and whether we are acting as directory <b>server</b>, and + * then return a list of int pointers defining download delays in seconds. + * Helper function for download_status_increment_failure() and + * download_status_reset(). */ +static const smartlist_t * +find_dl_schedule_and_len(download_status_t *dls, int server) { switch (dls->schedule) { case DL_SCHED_GENERIC: - if (server) { - *schedule = server_dl_schedule; - *schedule_len = sizeof(server_dl_schedule)/sizeof(int); - } else { - *schedule = client_dl_schedule; - *schedule_len = sizeof(client_dl_schedule)/sizeof(int); - } - break; + if (server) + return get_options()->TestingServerDownloadSchedule; + else + return get_options()->TestingClientDownloadSchedule; case DL_SCHED_CONSENSUS: - if (server) { - *schedule = server_consensus_dl_schedule; - *schedule_len = sizeof(server_consensus_dl_schedule)/sizeof(int); - } else { - *schedule = client_consensus_dl_schedule; - *schedule_len = sizeof(client_consensus_dl_schedule)/sizeof(int); - } - break; + if (server) + return get_options()->TestingServerConsensusDownloadSchedule; + else + return get_options()->TestingClientConsensusDownloadSchedule; case DL_SCHED_BRIDGE: - *schedule = bridge_dl_schedule; - *schedule_len = sizeof(bridge_dl_schedule)/sizeof(int); - break; + return get_options()->TestingBridgeDownloadSchedule; default: tor_assert(0); } @@ -3789,8 +3775,7 @@ time_t download_status_increment_failure(download_status_t *dls, int status_code, const char *item, int server, time_t now) { - const int *schedule; - size_t schedule_len; + const smartlist_t *schedule; int increment; tor_assert(dls); if (status_code != 503 || server) { @@ -3798,14 +3783,14 @@ download_status_increment_failure(download_status_t *dls, int status_code, ++dls->n_download_failures; } - find_dl_schedule_and_len(dls, server, &schedule, &schedule_len); + schedule = find_dl_schedule_and_len(dls, server); - if (dls->n_download_failures < schedule_len) - increment = schedule[dls->n_download_failures]; + if (dls->n_download_failures < smartlist_len(schedule)) + increment = *(int *)smartlist_get(schedule, dls->n_download_failures); else if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD) increment = INT_MAX; else - increment = schedule[schedule_len-1]; + increment = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); if (increment < INT_MAX) dls->next_attempt_at = now+increment; @@ -3838,14 +3823,11 @@ download_status_increment_failure(download_status_t *dls, int status_code, void download_status_reset(download_status_t *dls) { - const int *schedule; - size_t schedule_len; - - find_dl_schedule_and_len(dls, get_options()->DirPort_set, - &schedule, &schedule_len); + const smartlist_t *schedule = find_dl_schedule_and_len( + dls, get_options()->DirPort_set); dls->n_download_failures = 0; - dls->next_attempt_at = time(NULL) + schedule[0]; + dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); } /** Return the number of failures on <b>dls</b> since the last success (if @@ -3890,7 +3872,8 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code, } else { dls = router_get_dl_status_by_descriptor_digest(digest); } - if (!dls || dls->n_download_failures >= MAX_ROUTERDESC_DOWNLOAD_FAILURES) + if (!dls || dls->n_download_failures >= + get_options()->TestingDescriptorMaxDownloadTries) continue; download_status_increment_failure(dls, status_code, cp, server, now); } SMARTLIST_FOREACH_END(cp); @@ -3921,7 +3904,8 @@ dir_microdesc_download_failed(smartlist_t *failed, if (!rs) continue; dls = &rs->dl_status; - if (dls->n_download_failures >= MAX_MICRODESC_DOWNLOAD_FAILURES) + if (dls->n_download_failures >= + get_options()->TestingMicrodescMaxDownloadTries) continue; { char buf[BASE64_DIGEST256_LEN+1]; diff --git a/src/or/directory.h b/src/or/directory.h index 41f18a172..0453160f7 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -118,5 +118,10 @@ download_status_mark_impossible(download_status_t *dl) int download_status_get_n_failures(const download_status_t *dls); +#ifdef TOR_UNIT_TESTS +/* Used only by directory.c and test_dir.c */ +STATIC int parse_http_url(const char *headers, char **url); +#endif + #endif diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 3e46153a5..8d2da5b50 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -26,6 +26,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "routerset.h" /** * \file dirserv.c @@ -1907,7 +1908,7 @@ router_counts_toward_thresholds(const node_t *node, time_t now, * the Weighted Fractional Uptime history, and use them to set thresholds for * the Stable, Fast, and Guard flags. Update the fields stable_uptime, * stable_mtbf, enough_mtbf_info, guard_wfu, guard_tk, fast_bandwidth, - * guard_bandwidh_including_exits, guard_bandwidth_excluding_exits, + * guard_bandwidth_including_exits, and guard_bandwidth_excluding_exits. * * Also, set the is_exit flag of each router appropriately. */ static void @@ -1956,6 +1957,10 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, /* Now, fill in the arrays. */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + if (options->BridgeAuthoritativeDir && + node->ri && + node->ri->purpose != ROUTER_PURPOSE_BRIDGE) + continue; if (router_counts_toward_thresholds(node, now, omit_as_sybil, require_mbw)) { routerinfo_t *ri = node->ri; @@ -2070,6 +2075,21 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, tor_free(wfus); } +/* Use dirserv_compute_performance_thresholds() to compute the thresholds + * for the status flags, specifically for bridges. + * + * This is only called by a Bridge Authority from + * networkstatus_getinfo_by_purpose(). + */ +void +dirserv_compute_bridge_flag_thresholds(routerlist_t *rl) +{ + + digestmap_t *omit_as_sybil = digestmap_new(); + dirserv_compute_performance_thresholds(rl, omit_as_sybil); + digestmap_free(omit_as_sybil, NULL); +} + /** Measured bandwidth cache entry */ typedef struct mbw_cache_entry_s { long mbw_kb; @@ -2082,7 +2102,7 @@ static digestmap_t *mbw_cache = NULL; /** Store a measured bandwidth cache entry when reading the measured * bandwidths file. */ -void +STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, time_t as_of) { @@ -2112,7 +2132,7 @@ dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, } /** Clear and free the measured bandwidth cache */ -void +STATIC void dirserv_clear_measured_bw_cache(void) { if (mbw_cache) { @@ -2123,7 +2143,7 @@ dirserv_clear_measured_bw_cache(void) } /** Scan the measured bandwidth cache and remove expired entries */ -void +STATIC void dirserv_expire_measured_bw_cache(time_t now) { @@ -2145,7 +2165,7 @@ dirserv_expire_measured_bw_cache(time_t now) } /** Get the current size of the measured bandwidth cache */ -int +STATIC int dirserv_get_measured_bw_cache_size(void) { if (mbw_cache) return digestmap_size(mbw_cache); @@ -2155,7 +2175,7 @@ dirserv_get_measured_bw_cache_size(void) /** Query the cache by identity digest, return value indicates whether * we found it. The bw_out and as_of_out pointers receive the cached * bandwidth value and the time it was cached if not NULL. */ -int +STATIC int dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out, time_t *as_of_out) { @@ -2176,7 +2196,7 @@ dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out, } /** Predicate wrapper for dirserv_query_measured_bw_cache() */ -int +STATIC int dirserv_has_measured_bw(const char *node_id) { return dirserv_query_measured_bw_cache_kb(node_id, NULL, NULL); @@ -2705,6 +2725,11 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, } else { rs->is_possible_guard = 0; } + if (options->TestingTorNetwork && + routerset_contains_routerstatus(options->TestingDirAuthVoteGuard, + rs, 0)) { + rs->is_possible_guard = 1; + } rs->is_bad_directory = listbaddirs && node->is_bad_directory; rs->is_bad_exit = listbadexits && node->is_bad_exit; @@ -2754,7 +2779,7 @@ clear_status_flags_on_sybil(routerstatus_t *rs) * into a measured_bw_line_t output structure. Returns -1 on failure * or 0 on success. */ -int +STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) { char *line = tor_strdup(orig_line); @@ -2835,7 +2860,7 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) * of bandwidth statuses. Returns true if a line is found, * false otherwise. */ -int +STATIC int measured_bw_line_apply(measured_bw_line_t *parsed_line, smartlist_t *routerstatuses) { @@ -3093,7 +3118,8 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, else last_consensus_interval = options->TestingV3AuthInitialVotingInterval; v3_out->valid_after = - dirvote_get_start_of_next_interval(now, (int)last_consensus_interval); + dirvote_get_start_of_next_interval(now, (int)last_consensus_interval, + options->TestingV3AuthVotingStartOffset); format_iso_time(tbuf, v3_out->valid_after); log_notice(LD_DIR,"Choosing valid-after time in vote as %s: " "consensus_set=%d, last_interval=%d", @@ -3167,7 +3193,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, /** For v2 authoritative directories only: Replace the contents of * <b>the_v2_networkstatus</b> with a newly generated network status * object. */ -cached_dir_t * +STATIC cached_dir_t * generate_v2_networkstatus_opinion(void) { cached_dir_t *r = NULL; diff --git a/src/or/dirserv.h b/src/or/dirserv.h index f9d36d760..c2d6131c7 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -12,6 +12,8 @@ #ifndef TOR_DIRSERV_H #define TOR_DIRSERV_H +#include "testsupport.h" + /** What fraction (1 over this number) of the relay ID space do we * (as a directory authority) launch connections to at each reachability * test? */ @@ -49,6 +51,7 @@ int list_server_status_v1(smartlist_t *routers, char **router_status_out, int dirserv_dump_directory_to_string(char **dir_out, crypto_pk_t *private_key); char *dirserv_get_flag_thresholds_line(void); +void dirserv_compute_bridge_flag_thresholds(routerlist_t *rl); int directory_fetches_from_authorities(const or_options_t *options); int directory_fetches_dir_info_early(const or_options_t *options); @@ -119,20 +122,21 @@ cached_dir_t *new_cached_dir(char *s, time_t published); /* Put the MAX_MEASUREMENT_AGE #define here so unit tests can see it */ #define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */ -int measured_bw_line_parse(measured_bw_line_t *out, const char *line); +STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *line); -int measured_bw_line_apply(measured_bw_line_t *parsed_line, +STATIC int measured_bw_line_apply(measured_bw_line_t *parsed_line, smartlist_t *routerstatuses); -void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, +STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, time_t as_of); -void dirserv_clear_measured_bw_cache(void); -void dirserv_expire_measured_bw_cache(time_t now); -int dirserv_get_measured_bw_cache_size(void); -int dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_out, - time_t *as_of_out); -int dirserv_has_measured_bw(const char *node_id); -cached_dir_t *generate_v2_networkstatus_opinion(void); +STATIC void dirserv_clear_measured_bw_cache(void); +STATIC void dirserv_expire_measured_bw_cache(time_t now); +STATIC int dirserv_get_measured_bw_cache_size(void); +STATIC int dirserv_query_measured_bw_cache_kb(const char *node_id, + long *bw_out, + time_t *as_of_out); +STATIC int dirserv_has_measured_bw(const char *node_id); +STATIC cached_dir_t *generate_v2_networkstatus_opinion(void); #endif int dirserv_read_measured_bandwidths(const char *from_file, diff --git a/src/or/dirvote.c b/src/or/dirvote.c index c6d124490..4d3ee9cdb 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -60,7 +60,7 @@ static char *make_consensus_method_list(int low, int high, const char *sep); /** Return a new string containing the string representation of the vote in * <b>v3_ns</b>, signed with our v3 signing key <b>private_signing_key</b>. * For v3 authorities. */ -char * +STATIC char * format_networkstatus_vote(crypto_pk_t *private_signing_key, networkstatus_t *v3_ns) { @@ -587,7 +587,7 @@ compute_consensus_versions_list(smartlist_t *lst, int n_versioning) /** Helper: given a list of valid networkstatus_t, return a new string * containing the contents of the consensus network parameter set. */ -/* private */ char * +STATIC char * dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) { int i; @@ -2533,12 +2533,13 @@ dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out) timing_out->dist_delay = options->V3AuthDistDelay; } -/** Return the start of the next interval of size <b>interval</b> (in seconds) - * after <b>now</b>. Midnight always starts a fresh interval, and if the last - * interval of a day would be truncated to less than half its size, it is - * rolled into the previous interval. */ +/** Return the start of the next interval of size <b>interval</b> (in + * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always + * starts a fresh interval, and if the last interval of a day would be + * truncated to less than half its size, it is rolled into the + * previous interval. */ time_t -dirvote_get_start_of_next_interval(time_t now, int interval) +dirvote_get_start_of_next_interval(time_t now, int interval, int offset) { struct tm tm; time_t midnight_today=0; @@ -2566,6 +2567,10 @@ dirvote_get_start_of_next_interval(time_t now, int interval) if (next + interval/2 > midnight_tomorrow) next = midnight_tomorrow; + next += offset; + if (next - interval > now) + next -= interval; + return next; } @@ -2629,8 +2634,10 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now) vote_delay = dist_delay = interval / 4; start = voting_schedule.interval_starts = - dirvote_get_start_of_next_interval(now,interval); - end = dirvote_get_start_of_next_interval(start+1, interval); + dirvote_get_start_of_next_interval(now,interval, + options->TestingV3AuthVotingStartOffset); + end = dirvote_get_start_of_next_interval(start+1, interval, + options->TestingV3AuthVotingStartOffset); tor_assert(end > start); @@ -3136,7 +3143,7 @@ dirvote_compute_consensuses(void) }); votefile = get_datadir_fname("v3-status-votes"); - write_chunks_to_file(votefile, votestrings, 0); + write_chunks_to_file(votefile, votestrings, 0, 0); tor_free(votefile); SMARTLIST_FOREACH(votestrings, sized_chunk_t *, c, tor_free(c)); smartlist_free(votestrings); diff --git a/src/or/dirvote.h b/src/or/dirvote.h index b23645212..3a4951a95 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -12,10 +12,12 @@ #ifndef TOR_DIRVOTE_H #define TOR_DIRVOTE_H +#include "testsupport.h" + /** Lowest allowable value for VoteSeconds. */ -#define MIN_VOTE_SECONDS 20 +#define MIN_VOTE_SECONDS 2 /** Lowest allowable value for DistSeconds. */ -#define MIN_DIST_SECONDS 20 +#define MIN_DIST_SECONDS 2 /** Smallest allowable voting interval. */ #define MIN_VOTE_INTERVAL 300 @@ -86,7 +88,9 @@ authority_cert_t *authority_cert_dup(authority_cert_t *cert); /* vote scheduling */ void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out); -time_t dirvote_get_start_of_next_interval(time_t now, int interval); +time_t dirvote_get_start_of_next_interval(time_t now, + int interval, + int offset); void dirvote_recalculate_timing(const or_options_t *options, time_t now); void dirvote_act(const or_options_t *options, time_t now); @@ -134,9 +138,9 @@ document_signature_t *voter_get_sig_by_algorithm( digest_algorithm_t alg); #ifdef DIRVOTE_PRIVATE -char *format_networkstatus_vote(crypto_pk_t *private_key, +STATIC char *format_networkstatus_vote(crypto_pk_t *private_key, networkstatus_t *v3_ns); -char *dirvote_compute_params(smartlist_t *votes, int method, +STATIC char *dirvote_compute_params(smartlist_t *votes, int method, int total_authorities); #endif diff --git a/src/or/dns.c b/src/or/dns.c index f2b7eecc3..a1fe0de1d 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -24,6 +24,7 @@ #include "relay.h" #include "router.h" #include "ht.h" +#include "../common/sandbox.h" #ifdef HAVE_EVENT2_DNS_H #include <event2/event.h> #include <event2/dns.h> @@ -1443,13 +1444,14 @@ configure_nameservers(int force) const or_options_t *options; const char *conf_fname; struct stat st; - int r; + int r, flags; options = get_options(); conf_fname = options->ServerDNSResolvConfFile; #ifndef _WIN32 if (!conf_fname) conf_fname = "/etc/resolv.conf"; #endif + flags = DNS_OPTIONS_ALL; if (!the_evdns_base) { if (!(the_evdns_base = evdns_base_new(tor_libevent_get_base(), 0))) { @@ -1477,7 +1479,7 @@ configure_nameservers(int force) evdns_set_log_fn(evdns_log_cb); if (conf_fname) { - if (stat(conf_fname, &st)) { + if (stat(sandbox_intern_string(conf_fname), &st)) { log_warn(LD_EXIT, "Unable to stat resolver configuration in '%s': %s", conf_fname, strerror(errno)); goto err; @@ -1491,9 +1493,16 @@ configure_nameservers(int force) evdns_base_search_clear(the_evdns_base); evdns_base_clear_nameservers_and_suspend(the_evdns_base); } +#if defined(DNS_OPTION_HOSTSFILE) && defined(USE_LIBSECCOMP) + if (flags & DNS_OPTION_HOSTSFILE) { + flags ^= DNS_OPTION_HOSTSFILE; + evdns_base_load_hosts(the_evdns_base, + sandbox_intern_string("/etc/hosts")); + } +#endif log_info(LD_EXIT, "Parsing resolver configuration in '%s'", conf_fname); - if ((r = evdns_base_resolv_conf_parse(the_evdns_base, - DNS_OPTIONS_ALL, conf_fname))) { + if ((r = evdns_base_resolv_conf_parse(the_evdns_base, flags, + sandbox_intern_string(conf_fname)))) { log_warn(LD_EXIT, "Unable to parse '%s', or no nameservers in '%s' (%d)", conf_fname, conf_fname, r); goto err; diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 2aa063cda..d463303fc 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -13,6 +13,7 @@ **/ #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitstats.h" #include "config.h" @@ -54,6 +55,10 @@ typedef struct { /** When should we next try to fetch a descriptor for this bridge? */ download_status_t fetch_status; + + /** A smartlist of k=v values to be passed to the SOCKS proxy, if + transports are used for this bridge. */ + smartlist_t *socks_args; } bridge_info_t; /** A list of our chosen entry guards. */ @@ -359,7 +364,7 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, entry->can_retry = 1; } entry->is_dir_cache = node->rs && - node->rs->version_supports_microdesc_cache; + node->rs->version_supports_microdesc_cache; if (get_options()->UseBridges && node_is_a_configured_bridge(node)) entry->is_dir_cache = 1; return NULL; @@ -392,8 +397,8 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, node_describe(node)); strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); memcpy(entry->identity, node->identity, DIGEST_LEN); - entry->is_dir_cache = node_is_dir(node) && - node->rs && node->rs->version_supports_microdesc_cache; + entry->is_dir_cache = node_is_dir(node) && node->rs && + node->rs->version_supports_microdesc_cache; if (get_options()->UseBridges && node_is_a_configured_bridge(node)) entry->is_dir_cache = 1; @@ -594,6 +599,25 @@ remove_dead_entry_guards(time_t now) return changed ? 1 : 0; } +/** Remove all currently listed entry guards. So new ones will be chosen. */ +void +remove_all_entry_guards(void) +{ + char dbuf[HEX_DIGEST_LEN+1]; + + while (smartlist_len(entry_guards)) { + entry_guard_t *entry = smartlist_get(entry_guards, 0); + base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); + log_info(LD_CIRC, "Entry guard '%s' (%s) has been dropped.", + entry->nickname, dbuf); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del(entry_guards, 0); + } + log_entry_guards(LOG_INFO); + entry_guards_changed(); +} + /** A new directory or router-status has arrived; update the down/listed * status of the entry guards. * @@ -1595,6 +1619,11 @@ bridge_free(bridge_info_t *bridge) return; tor_free(bridge->transport_name); + if (bridge->socks_args) { + SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s)); + smartlist_free(bridge->socks_args); + } + tor_free(bridge); } @@ -1628,7 +1657,8 @@ get_configured_bridge_by_orports_digest(const char *digest, /** If we have a bridge configured whose digest matches <b>digest</b>, or a * bridge with no known digest whose address matches <b>addr</b>:<b>/port</b>, - * return that bridge. Else return NULL. */ + * return that bridge. Else return NULL. If <b>digest</b> is NULL, check for + * address/port matches only. */ static bridge_info_t * get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, uint16_t port, @@ -1638,7 +1668,7 @@ get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, return NULL; SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { - if (tor_digest_is_zero(bridge->identity) && + if ((tor_digest_is_zero(bridge->identity) || digest == NULL) && !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) && bridge->port == port) return bridge; @@ -1773,30 +1803,68 @@ bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port, } SMARTLIST_FOREACH_END(bridge); } -/** Remember a new bridge at <b>addr</b>:<b>port</b>. If <b>digest</b> - * is set, it tells us the identity key too. If we already had the - * bridge in our list, unmark it, and don't actually add anything new. - * If <b>transport_name</b> is non-NULL - the bridge is associated with a - * pluggable transport - we assign the transport to the bridge. */ +/** Return True if we have a bridge that uses a transport with name + * <b>transport_name</b>. */ +int +transport_is_needed(const char *transport_name) +{ + if (!bridge_list) + return 0; + + SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { + if (bridge->transport_name && + !strcmp(bridge->transport_name, transport_name)) + return 1; + } SMARTLIST_FOREACH_END(bridge); + + return 0; +} + +/** Register the bridge information in <b>bridge_line</b> to the + * bridge subsystem. Steals reference of <b>bridge_line</b>. */ void -bridge_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *digest, const char *transport_name) +bridge_add_from_config(bridge_line_t *bridge_line) { bridge_info_t *b; - bridge_resolve_conflicts(addr, port, digest, transport_name); + { /* Log the bridge we are about to register: */ + log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)", + fmt_addrport(&bridge_line->addr, bridge_line->port), + bridge_line->transport_name ? + bridge_line->transport_name : "no transport", + tor_digest_is_zero(bridge_line->digest) ? + "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN)); + + if (bridge_line->socks_args) { /* print socks arguments */ + int i = 0; + + tor_assert(smartlist_len(bridge_line->socks_args) > 0); + + log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:", + smartlist_len(bridge_line->socks_args)); + SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg, + log_debug(LD_CONFIG, "%d: %s", ++i, arg)); + } + } + + bridge_resolve_conflicts(&bridge_line->addr, + bridge_line->port, + bridge_line->digest, + bridge_line->transport_name); b = tor_malloc_zero(sizeof(bridge_info_t)); - tor_addr_copy(&b->addr, addr); - b->port = port; - if (digest) - memcpy(b->identity, digest, DIGEST_LEN); - if (transport_name) - b->transport_name = tor_strdup(transport_name); + tor_addr_copy(&b->addr, &bridge_line->addr); + b->port = bridge_line->port; + memcpy(b->identity, bridge_line->digest, DIGEST_LEN); + if (bridge_line->transport_name) + b->transport_name = bridge_line->transport_name; b->fetch_status.schedule = DL_SCHED_BRIDGE; + b->socks_args = bridge_line->socks_args; if (!bridge_list) bridge_list = smartlist_new(); + tor_free(bridge_line); /* Deallocate bridge_line now. */ + smartlist_add(bridge_list, b); } @@ -1857,7 +1925,7 @@ find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) * transport, but the transport could not be found. */ int -find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, +get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, const transport_t **transport) { *transport = NULL; @@ -1884,6 +1952,17 @@ find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, return 0; } +/** Return a smartlist containing all the SOCKS arguments that we + * should pass to the SOCKS proxy. */ +const smartlist_t * +get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) +{ + bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr, + port, + NULL); + return bridge ? bridge->socks_args : NULL; +} + /** We need to ask <b>bridge</b> for its server descriptor. */ static void launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) @@ -2250,6 +2329,6 @@ entry_guards_free_all(void) clear_bridge_list(); smartlist_free(bridge_list); bridge_list = NULL; - circuit_build_times_free_timeouts(&circ_times); + circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 52b8dc00e..772c6662d 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -77,6 +77,8 @@ int num_live_entry_guards(int for_directory); #endif +void remove_all_entry_guards(void); + void entry_guards_compute_status(const or_options_t *options, time_t now); int entry_guard_register_connect_status(const char *digest, int succeeded, int mark_relay_status, time_t now); @@ -97,9 +99,8 @@ int routerinfo_is_a_configured_bridge(const routerinfo_t *ri); int node_is_a_configured_bridge(const node_t *node); void learned_router_identity(const tor_addr_t *addr, uint16_t port, const char *digest); -void bridge_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *digest, - const char *transport_name); +struct bridge_line_t; +void bridge_add_from_config(struct bridge_line_t *bridge_line); void retry_bridge_descriptor_fetch_directly(const char *digest); void fetch_bridge_descriptors(const or_options_t *options, time_t now); void learned_bridge_descriptor(routerinfo_t *ri, int from_cache); @@ -109,15 +110,20 @@ int entries_known_but_down(const or_options_t *options); void entries_retry_all(const or_options_t *options); int any_bridge_supports_microdescriptors(void); +const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr, + uint16_t port); + +int any_bridges_dont_support_microdescriptors(void); void entry_guards_free_all(void); const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port); struct transport_t; -int find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, +int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, const struct transport_t **transport); +int transport_is_needed(const char *transport_name); int validate_pluggable_transports_config(void); double pathbias_get_close_success_count(entry_guard_t *guard); diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c new file mode 100644 index 000000000..d5a0fa1ee --- /dev/null +++ b/src/or/ext_orport.c @@ -0,0 +1,648 @@ +/* Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file ext_orport.c + * \brief Code implementing the Extended ORPort. +*/ + +#define EXT_ORPORT_PRIVATE +#include "or.h" +#include "connection.h" +#include "connection_or.h" +#include "ext_orport.h" +#include "control.h" +#include "config.h" +#include "util.h" +#include "main.h" + +/** Allocate and return a structure capable of holding an Extended + * ORPort message of body length <b>len</b>. */ +ext_or_cmd_t * +ext_or_cmd_new(uint16_t len) +{ + size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len; + ext_or_cmd_t *cmd = tor_malloc(size); + cmd->len = len; + return cmd; +} + +/** Deallocate the Extended ORPort message in <b>cmd</b>. */ +void +ext_or_cmd_free(ext_or_cmd_t *cmd) +{ + tor_free(cmd); +} + +/** Get an Extended ORPort message from <b>conn</b>, and place it in + * <b>out</b>. Return -1 on fail, 0 if we need more data, and 1 if we + * successfully extracted an Extended ORPort command from the + * buffer. */ +static int +connection_fetch_ext_or_cmd_from_buf(connection_t *conn, ext_or_cmd_t **out) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_ext_or_command_from_evbuffer(input, out); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_ext_or_command_from_buf(conn->inbuf, out); + } +} + +/** Write an Extended ORPort message to <b>conn</b>. Use + * <b>command</b> as the command type, <b>bodylen</b> as the body + * length, and <b>body</b>, if it's present, as the body of the + * message. */ +STATIC int +connection_write_ext_or_command(connection_t *conn, + uint16_t command, + const char *body, + size_t bodylen) +{ + char header[4]; + if (bodylen > UINT16_MAX) + return -1; + set_uint16(header, htons(command)); + set_uint16(header+2, htons(bodylen)); + connection_write_to_buf(header, 4, conn); + if (bodylen) { + tor_assert(body); + connection_write_to_buf(body, bodylen, conn); + } + return 0; +} + +/** Transition from an Extended ORPort which accepts Extended ORPort + * messages, to an Extended ORport which accepts OR traffic. */ +static void +connection_ext_or_transition(or_connection_t *conn) +{ + tor_assert(conn->base_.type == CONN_TYPE_EXT_OR); + + conn->base_.type = CONN_TYPE_OR; + TO_CONN(conn)->state = 0; // set the state to a neutral value + control_event_or_conn_status(conn, OR_CONN_EVENT_NEW, 0); + connection_tls_start_handshake(conn, 1); +} + +/** Length of authentication cookie. */ +#define EXT_OR_PORT_AUTH_COOKIE_LEN 32 +/** Length of the header of the cookie file. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN 32 +/** Static cookie file header. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER "! Extended ORPort Auth Cookie !\x0a" +/** Length of safe-cookie protocol hashes. */ +#define EXT_OR_PORT_AUTH_HASH_LEN DIGEST256_LEN +/** Length of safe-cookie protocol nonces. */ +#define EXT_OR_PORT_AUTH_NONCE_LEN 32 +/** Safe-cookie protocol constants. */ +#define EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST \ + "ExtORPort authentication server-to-client hash" +#define EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST \ + "ExtORPort authentication client-to-server hash" + +/* Code to indicate cookie authentication */ +#define EXT_OR_AUTHTYPE_SAFECOOKIE 0x01 + +/** If true, we've set ext_or_auth_cookie to a secret code and stored + * it to disk. */ +STATIC int ext_or_auth_cookie_is_set = 0; +/** If ext_or_auth_cookie_is_set, a secret cookie that we've stored to disk + * and which we're using to authenticate controllers. (If the controller can + * read it off disk, it has permission to connect.) */ +STATIC uint8_t *ext_or_auth_cookie = NULL; + +/** Helper: Return a newly allocated string containing a path to the + * file where we store our authentication cookie. */ +char * +get_ext_or_auth_cookie_file_name(void) +{ + const or_options_t *options = get_options(); + if (options->ExtORPortCookieAuthFile && + strlen(options->ExtORPortCookieAuthFile)) { + return tor_strdup(options->ExtORPortCookieAuthFile); + } else { + return get_datadir_fname("extended_orport_auth_cookie"); + } +} + +/* Initialize the cookie-based authentication system of the + * Extended ORPort. If <b>is_enabled</b> is 0, then disable the cookie + * authentication system. */ +int +init_ext_or_cookie_authentication(int is_enabled) +{ + char *fname = NULL; + int retval; + + if (!is_enabled) { + ext_or_auth_cookie_is_set = 0; + return 0; + } + + fname = get_ext_or_auth_cookie_file_name(); + retval = init_cookie_authentication(fname, EXT_OR_PORT_AUTH_COOKIE_HEADER, + EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN, + &ext_or_auth_cookie, + &ext_or_auth_cookie_is_set); + tor_free(fname); + return retval; +} + +/** Read data from <b>conn</b> and see if the client sent us the + * authentication type that she prefers to use in this session. + * + * Return -1 if we received corrupted data or if we don't support the + * authentication type. Return 0 if we need more data in + * <b>conn</b>. Return 1 if the authentication type negotiation was + * successful. */ +static int +connection_ext_or_auth_neg_auth_type(connection_t *conn) +{ + char authtype[1] = {0}; + + if (connection_get_inbuf_len(conn) < 1) + return 0; + + if (connection_fetch_from_buf(authtype, 1, conn) < 0) + return -1; + + log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); + if (authtype[0] != EXT_OR_AUTHTYPE_SAFECOOKIE) { + /* '1' is the only auth type supported atm */ + return -1; + } + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE; + return 1; +} + +/** DOCDOC */ +STATIC int +handle_client_auth_nonce(const char *client_nonce, size_t client_nonce_len, + char **client_hash_out, + char **reply_out, size_t *reply_len_out) +{ + char server_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + char server_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; + char *reply; + size_t reply_len; + + if (client_nonce_len != EXT_OR_PORT_AUTH_NONCE_LEN) + return -1; + + /* Get our nonce */ + if (crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN) < 0) + return -1; + + { /* set up macs */ + size_t hmac_s_msg_len = strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + size_t hmac_c_msg_len = strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + + char *hmac_s_msg = tor_malloc_zero(hmac_s_msg_len); + char *hmac_c_msg = tor_malloc_zero(hmac_c_msg_len); + char *correct_client_hash = tor_malloc_zero(EXT_OR_PORT_AUTH_HASH_LEN); + + memcpy(hmac_s_msg, + EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST, + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST)); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + memcpy(hmac_c_msg, + EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST, + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST)); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + crypto_hmac_sha256(server_hash, + (char*)ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_s_msg, + hmac_s_msg_len); + + crypto_hmac_sha256(correct_client_hash, + (char*)ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_c_msg, + hmac_c_msg_len); + + /* Store the client hash we generated. We will need to compare it + with the hash sent by the client. */ + *client_hash_out = correct_client_hash; + + memwipe(hmac_s_msg, 0, hmac_s_msg_len); + memwipe(hmac_c_msg, 0, hmac_c_msg_len); + + tor_free(hmac_s_msg); + tor_free(hmac_c_msg); + } + + { /* debug logging */ /* XXX disable this codepath if not logging on debug?*/ + char server_hash_encoded[(2*EXT_OR_PORT_AUTH_HASH_LEN) + 1]; + char server_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + char client_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + + base16_encode(server_hash_encoded, sizeof(server_hash_encoded), + server_hash, sizeof(server_hash)); + base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), + server_nonce, sizeof(server_nonce)); + base16_encode(client_nonce_encoded, sizeof(client_nonce_encoded), + client_nonce, sizeof(client_nonce)); + + log_debug(LD_GENERAL, + "server_hash: '%s'\nserver_nonce: '%s'\nclient_nonce: '%s'", + server_hash_encoded, server_nonce_encoded, client_nonce_encoded); + + memwipe(server_hash_encoded, 0, sizeof(server_hash_encoded)); + memwipe(server_nonce_encoded, 0, sizeof(server_nonce_encoded)); + memwipe(client_nonce_encoded, 0, sizeof(client_nonce_encoded)); + } + + { /* write reply: (server_hash, server_nonce) */ + + reply_len = EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_NONCE_LEN; + reply = tor_malloc_zero(reply_len); + memcpy(reply, server_hash, EXT_OR_PORT_AUTH_HASH_LEN); + memcpy(reply + EXT_OR_PORT_AUTH_HASH_LEN, server_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN); + } + + *reply_out = reply; + *reply_len_out = reply_len; + + return 0; +} + +/** Read the client's nonce out of <b>conn</b>, setup the safe-cookie + * crypto, and then send our own hash and nonce to the client + * + * Return -1 if there was an error; return 0 if we need more data in + * <b>conn</b>, and return 1 if we successfully retrieved the + * client's nonce and sent our own. */ +static int +connection_ext_or_auth_handle_client_nonce(connection_t *conn) +{ + char client_nonce[EXT_OR_PORT_AUTH_NONCE_LEN]; + char *reply=NULL; + size_t reply_len=0; + + if (!ext_or_auth_cookie_is_set) { /* this should not happen */ + log_warn(LD_BUG, "Extended ORPort authentication cookie was not set. " + "That's weird since we should have done that on startup. " + "This might be a Tor bug, please file a bug report. "); + return -1; + } + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) + return 0; + + if (connection_fetch_from_buf(client_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) + return -1; + + /* We extract the ClientNonce from the received data, and use it to + calculate ServerHash and ServerNonce according to proposal 217. + + We also calculate our own ClientHash value and save it in the + connection state. We validate it later against the ClientHash + sent by the client. */ + if (handle_client_auth_nonce(client_nonce, sizeof(client_nonce), + &TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, + &reply, &reply_len) < 0) + return -1; + + connection_write_to_buf(reply, reply_len, conn); + + memwipe(reply, 0, reply_len); + tor_free(reply); + + log_debug(LD_GENERAL, "Got client nonce, and sent our own nonce and hash."); + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH; + return 1; +} + +#define connection_ext_or_auth_send_result_success(c) \ + connection_ext_or_auth_send_result(c, 1) +#define connection_ext_or_auth_send_result_fail(c) \ + connection_ext_or_auth_send_result(c, 0) + +/** Send authentication results to <b>conn</b>. Successful results if + * <b>success</b> is set; failure results otherwise. */ +static void +connection_ext_or_auth_send_result(connection_t *conn, int success) +{ + if (success) + connection_write_to_buf("\x01", 1, conn); + else + connection_write_to_buf("\x00", 1, conn); +} + +/** Receive the client's hash from <b>conn</b>, validate that it's + * correct, and then send the authentication results to the client. + * + * Return -1 if there was an error during validation; return 0 if we + * need more data in <b>conn</b>, and return 1 if we successfully + * validated the client's hash and sent a happy authentication + * result. */ +static int +connection_ext_or_auth_handle_client_hash(connection_t *conn) +{ + char provided_client_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) + return 0; + + if (connection_fetch_from_buf(provided_client_hash, + EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) + return -1; + + if (tor_memneq(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, + provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN)) { + log_warn(LD_GENERAL, "Incorrect client hash. Authentication failed."); + connection_ext_or_auth_send_result_fail(conn); + return -1; + } + + log_debug(LD_GENERAL, "Got client's hash and it was legit."); + + /* send positive auth result */ + connection_ext_or_auth_send_result_success(conn); + conn->state = EXT_OR_CONN_STATE_OPEN; + return 1; +} + +/** Handle data from <b>or_conn</b> received on Extended ORPort. + * Return -1 on error. 0 on unsufficient data. 1 on correct. */ +static int +connection_ext_or_auth_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + + /* State transitions of the Extended ORPort authentication protocol: + + EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE (start state) -> + EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE -> + EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH -> + EXT_OR_CONN_STATE_OPEN + + During EXT_OR_CONN_STATE_OPEN, data is handled by + connection_ext_or_process_inbuf(). + */ + + switch (conn->state) { /* Functionify */ + case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: + return connection_ext_or_auth_neg_auth_type(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: + return connection_ext_or_auth_handle_client_nonce(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: + return connection_ext_or_auth_handle_client_hash(conn); + + default: + log_warn(LD_BUG, "Encountered unexpected connection state %d while trying " + "to process Extended ORPort authentication data.", conn->state); + return -1; + } +} + +/** Extended ORPort commands (Transport-to-Bridge) */ +#define EXT_OR_CMD_TB_DONE 0x0000 +#define EXT_OR_CMD_TB_USERADDR 0x0001 +#define EXT_OR_CMD_TB_TRANSPORT 0x0002 + +/** Extended ORPort commands (Bridge-to-Transport) */ +#define EXT_OR_CMD_BT_OKAY 0x1000 +#define EXT_OR_CMD_BT_DENY 0x1001 +#define EXT_OR_CMD_BT_CONTROL 0x1002 + +/** Process a USERADDR command from the Extended + * ORPort. <b>payload</b> is a payload of size <b>len</b>. + * + * If the USERADDR command was well formed, change the address of + * <b>conn</b> to the address on the USERADDR command. + * + * Return 0 on success and -1 on error. */ +static int +connection_ext_or_handle_cmd_useraddr(connection_t *conn, + const char *payload, uint16_t len) +{ + /* Copy address string. */ + tor_addr_t addr; + uint16_t port; + char *addr_str; + char *address_part=NULL; + int res; + if (memchr(payload, '\0', len)) { + log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort UserAddr"); + return -1; + } + + addr_str = tor_memdup_nulterm(payload, len); + + res = tor_addr_port_split(LOG_INFO, addr_str, &address_part, &port); + tor_free(addr_str); + if (res<0) + return -1; + + res = tor_addr_parse(&addr, address_part); + tor_free(address_part); + if (res<0) + return -1; + + { /* do some logging */ + char *old_address = tor_dup_addr(&conn->addr); + char *new_address = tor_dup_addr(&addr); + + log_debug(LD_NET, "Received USERADDR." + "We rewrite our address from '%s:%u' to '%s:%u'.", + safe_str(old_address), conn->port, safe_str(new_address), port); + + tor_free(old_address); + tor_free(new_address); + } + + /* record the address */ + tor_addr_copy(&conn->addr, &addr); + conn->port = port; + if (conn->address) { + tor_free(conn->address); + } + conn->address = tor_dup_addr(&addr); + + return 0; +} + +/** Process a TRANSPORT command from the Extended + * ORPort. <b>payload</b> is a payload of size <b>len</b>. + * + * If the TRANSPORT command was well formed, register the name of the + * transport on <b>conn</b>. + * + * Return 0 on success and -1 on error. */ +static int +connection_ext_or_handle_cmd_transport(or_connection_t *conn, + const char *payload, uint16_t len) +{ + char *transport_str; + if (memchr(payload, '\0', len)) { + log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort Transport"); + return -1; + } + + transport_str = tor_memdup_nulterm(payload, len); + + /* Transport names MUST be C-identifiers. */ + if (!string_is_C_identifier(transport_str)) { + tor_free(transport_str); + return -1; + } + + /* If ext_or_transport is already occupied (because the PT sent two + * TRANSPORT commands), deallocate the old name and keep the new + * one */ + if (conn->ext_or_transport) + tor_free(conn->ext_or_transport); + + conn->ext_or_transport = transport_str; + return 0; +} + +#define EXT_OR_CONN_STATE_IS_AUTHENTICATING(st) \ + ((st) <= EXT_OR_CONN_STATE_AUTH_MAX) + +/** Process Extended ORPort messages from <b>or_conn</b>. */ +int +connection_ext_or_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + ext_or_cmd_t *command; + int r; + + /* DOCDOC Document the state machine and transitions in this function */ + + /* If we are still in the authentication stage, process traffic as + authentication data: */ + while (EXT_OR_CONN_STATE_IS_AUTHENTICATING(conn->state)) { + log_debug(LD_GENERAL, "Got Extended ORPort authentication data (%u).", + (unsigned int) connection_get_inbuf_len(conn)); + r = connection_ext_or_auth_process_inbuf(or_conn); + if (r < 0) { + connection_mark_for_close(conn); + return -1; + } else if (r == 0) { + return 0; + } + /* if r > 0, loop and process more data (if any). */ + } + + while (1) { + log_debug(LD_GENERAL, "Got Extended ORPort data."); + command = NULL; + r = connection_fetch_ext_or_cmd_from_buf(conn, &command); + if (r < 0) + goto err; + else if (r == 0) + return 0; /* need to wait for more data */ + + /* Got a command! */ + tor_assert(command); + + if (command->cmd == EXT_OR_CMD_TB_DONE) { + if (connection_get_inbuf_len(conn)) { + /* The inbuf isn't empty; the client is misbehaving. */ + goto err; + } + + log_debug(LD_NET, "Received DONE."); + + /* If the transport proxy did not use the TRANSPORT command to + * specify the transport name, mark this as unknown transport. */ + if (!or_conn->ext_or_transport) { + /* We write this string this way to avoid ??>, which is a C + * trigraph. */ + or_conn->ext_or_transport = tor_strdup("<?" "?>"); + } + + connection_write_ext_or_command(conn, EXT_OR_CMD_BT_OKAY, NULL, 0); + + /* can't transition immediately; need to flush first. */ + conn->state = EXT_OR_CONN_STATE_FLUSHING; + connection_stop_reading(conn); + } else if (command->cmd == EXT_OR_CMD_TB_USERADDR) { + if (connection_ext_or_handle_cmd_useraddr(conn, + command->body, command->len) < 0) + goto err; + } else if (command->cmd == EXT_OR_CMD_TB_TRANSPORT) { + if (connection_ext_or_handle_cmd_transport(or_conn, + command->body, command->len) < 0) + goto err; + } else { + log_notice(LD_NET,"Got Extended ORPort command we don't regognize (%u).", + command->cmd); + } + + ext_or_cmd_free(command); + } + + return 0; + + err: + ext_or_cmd_free(command); + connection_mark_for_close(conn); + return -1; +} + +/** <b>conn</b> finished flushing Extended ORPort messages to the + * network, and is now ready to accept OR traffic. This function + * does the transition. */ +int +connection_ext_or_finished_flushing(or_connection_t *conn) +{ + if (conn->base_.state == EXT_OR_CONN_STATE_FLUSHING) { + connection_start_reading(TO_CONN(conn)); + connection_ext_or_transition(conn); + } + return 0; +} + +/** Initiate Extended ORPort authentication, by sending the list of + * supported authentication types to the client. */ +int +connection_ext_or_start_auth(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + const uint8_t authtypes[] = { + /* We only support authtype '1' for now. */ + EXT_OR_AUTHTYPE_SAFECOOKIE, + /* Marks the end of the list. */ + 0 + }; + + log_debug(LD_GENERAL, + "ExtORPort authentication: Sending supported authentication types"); + + connection_write_to_buf((const char *)authtypes, sizeof(authtypes), conn); + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; + + return 0; +} + +/** Free any leftover allocated memory of the ext_orport.c subsystem. */ +void +ext_orport_free_all(void) +{ + if (ext_or_auth_cookie) /* Free the auth cookie */ + tor_free(ext_or_auth_cookie); +} + diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h new file mode 100644 index 000000000..ce45e5f41 --- /dev/null +++ b/src/or/ext_orport.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef EXT_ORPORT_H +#define EXT_ORPORT_H + +int connection_ext_or_start_auth(or_connection_t *or_conn); + +ext_or_cmd_t *ext_or_cmd_new(uint16_t len); +void ext_or_cmd_free(ext_or_cmd_t *cmd); +void connection_or_set_ext_or_identifier(or_connection_t *conn); +void connection_or_remove_from_ext_or_id_map(or_connection_t *conn); +void connection_or_clear_ext_or_id_map(void); +or_connection_t *connection_or_get_by_ext_or_id(const char *id); + +int connection_ext_or_finished_flushing(or_connection_t *conn); +int connection_ext_or_process_inbuf(or_connection_t *or_conn); + +int init_ext_or_cookie_authentication(int is_enabled); +char *get_ext_or_auth_cookie_file_name(void); +void ext_orport_free_all(void); + +#ifdef EXT_ORPORT_PRIVATE +STATIC int connection_write_ext_or_command(connection_t *conn, + uint16_t command, + const char *body, + size_t bodylen); +STATIC int handle_client_auth_nonce(const char *client_nonce, + size_t client_nonce_len, + char **client_hash_out, + char **reply_out, size_t *reply_len_out); +#ifdef TOR_UNIT_TESTS +extern uint8_t *ext_or_auth_cookie; +extern int ext_or_auth_cookie_is_set; +#endif +#endif + +#endif + diff --git a/src/or/geoip.c b/src/or/geoip.c index e2e98e8ec..dc4730c81 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -120,7 +120,7 @@ geoip_add_entry(const tor_addr_t *low, const tor_addr_t *high, /** Add an entry to the GeoIP table indicated by <b>family</b>, * parsing it from <b>line</b>. The format is as for geoip_load_file(). */ -/*private*/ int +STATIC int geoip_parse_entry(const char *line, sa_family_t family) { tor_addr_t low_addr, high_addr; @@ -363,7 +363,7 @@ geoip_load_file(sa_family_t family, const char *filename) * be less than geoip_get_n_countries(). To decode it, call * geoip_get_country_name(). */ -int +STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr) { geoip_ipv4_entry_t *ent; @@ -379,7 +379,7 @@ geoip_get_country_by_ipv4(uint32_t ipaddr) * 0 for the 'unknown country'. The return value will always be less than * geoip_get_n_countries(). To decode it, call geoip_get_country_name(). */ -int +STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr) { geoip_ipv6_entry_t *ent; @@ -461,6 +461,10 @@ geoip_db_digest(sa_family_t family) typedef struct clientmap_entry_t { HT_ENTRY(clientmap_entry_t) node; tor_addr_t addr; + /* Name of pluggable transport used by this client. NULL if no + pluggable transport was used. */ + char *transport_name; + /** Time when we last saw this IP address, in MINUTES since the epoch. * * (This will run out of space around 4011 CE. If Tor is still in use around @@ -482,12 +486,18 @@ static HT_HEAD(clientmap, clientmap_entry_t) client_history = static INLINE unsigned clientmap_entry_hash(const clientmap_entry_t *a) { - return ht_improve_hash(tor_addr_hash(&a->addr)); + unsigned h = tor_addr_hash(&a->addr); + if (a->transport_name) + h += ht_string_hash(a->transport_name); + return ht_improve_hash(h); } /** Hashtable helper: compare two clientmap_entry_t values for equality. */ static INLINE int clientmap_entries_eq(const clientmap_entry_t *a, const clientmap_entry_t *b) { + if (strcmp_opt(a->transport_name, b->transport_name)) + return 0; + return !tor_addr_compare(&a->addr, &b->addr, CMP_EXACT) && a->action == b->action; } @@ -497,6 +507,17 @@ HT_PROTOTYPE(clientmap, clientmap_entry_t, node, clientmap_entry_hash, HT_GENERATE(clientmap, clientmap_entry_t, node, clientmap_entry_hash, clientmap_entries_eq, 0.6, malloc, realloc, free); +/** Free all storage held by <b>ent</b>. */ +static void +clientmap_entry_free(clientmap_entry_t *ent) +{ + if (!ent) + return; + + tor_free(ent->transport_name); + tor_free(ent); +} + /** Clear history of connecting clients used by entry and bridge stats. */ static void client_history_clear(void) @@ -507,7 +528,7 @@ client_history_clear(void) if ((*ent)->action == GEOIP_CLIENT_CONNECT) { this = *ent; next = HT_NEXT_RMV(clientmap, &client_history, ent); - tor_free(this); + clientmap_entry_free(this); } else { next = HT_NEXT(clientmap, &client_history, ent); } @@ -519,10 +540,14 @@ client_history_clear(void) * configured accordingly. */ void geoip_note_client_seen(geoip_client_action_t action, - const tor_addr_t *addr, time_t now) + const tor_addr_t *addr, + const char *transport_name, + time_t now) { const or_options_t *options = get_options(); clientmap_entry_t lookup, *ent; + memset(&lookup, 0, sizeof(clientmap_entry_t)); + if (action == GEOIP_CLIENT_CONNECT) { /* Only remember statistics as entry guard or as bridge. */ if (!options->EntryStatistics && @@ -534,12 +559,20 @@ geoip_note_client_seen(geoip_client_action_t action, return; } + log_debug(LD_GENERAL, "Seen client from '%s' with transport '%s'.", + safe_str_client(fmt_addr((addr))), + transport_name ? transport_name : "<no transport>"); + tor_addr_copy(&lookup.addr, addr); lookup.action = (int)action; + lookup.transport_name = (char*) transport_name; ent = HT_FIND(clientmap, &client_history, &lookup); + if (! ent) { ent = tor_malloc_zero(sizeof(clientmap_entry_t)); tor_addr_copy(&ent->addr, addr); + if (transport_name) + ent->transport_name = tor_strdup(transport_name); ent->action = (int)action; HT_INSERT(clientmap, &client_history, ent); } @@ -566,7 +599,7 @@ remove_old_client_helper_(struct clientmap_entry_t *ent, void *_cutoff) { time_t cutoff = *(time_t*)_cutoff / 60; if (ent->last_seen_in_minutes < cutoff) { - tor_free(ent); + clientmap_entry_free(ent); return 1; } else { return 0; @@ -769,6 +802,106 @@ geoip_change_dirreq_state(uint64_t dirreq_id, dirreq_type_t type, } } +/** Return the bridge-ip-transports string that should be inserted in + * our extra-info descriptor. Return NULL if the bridge-ip-transports + * line should be empty. */ +char * +geoip_get_transport_history(void) +{ + unsigned granularity = IP_GRANULARITY; + /** String hash table <name of transport> -> <number of users>. */ + strmap_t *transport_counts = strmap_new(); + + /** Smartlist that contains copies of the names of the transports + that have been used. */ + smartlist_t *transports_used = smartlist_new(); + + /* Special string to signify that no transport was used for this + connection. Pluggable transport names can't have symbols in their + names, so this string will never collide with a real transport. */ + static const char* no_transport_str = "<OR>"; + + clientmap_entry_t **ent; + const char *transport_name = NULL; + smartlist_t *string_chunks = smartlist_new(); + char *the_string = NULL; + + /* If we haven't seen any clients yet, return NULL. */ + if (HT_EMPTY(&client_history)) + goto done; + + /** We do the following steps to form the transport history string: + * a) Foreach client that uses a pluggable transport, we increase the + * times that transport was used by one. If the client did not use + * a transport, we increase the number of times someone connected + * without obfuscation. + * b) Foreach transport we observed, we write its transport history + * string and push it to string_chunks. So, for example, if we've + * seen 665 obfs2 clients, we write "obfs2=665". + * c) We concatenate string_chunks to form the final string. + */ + + log_debug(LD_GENERAL,"Starting iteration for transport history. %d clients.", + HT_SIZE(&client_history)); + + /* Loop through all clients. */ + HT_FOREACH(ent, clientmap, &client_history) { + uintptr_t val; + void *ptr; + transport_name = (*ent)->transport_name; + if (!transport_name) + transport_name = no_transport_str; + + /* Increase the count for this transport name. */ + ptr = strmap_get(transport_counts, transport_name); + val = (uintptr_t)ptr; + val++; + ptr = (void*)val; + strmap_set(transport_counts, transport_name, ptr); + + /* If it's the first time we see this transport, note it. */ + if (val == 1) + smartlist_add(transports_used, tor_strdup(transport_name)); + + log_debug(LD_GENERAL, "Client from '%s' with transport '%s'. " + "I've now seen %d clients.", + safe_str_client(fmt_addr(&(*ent)->addr)), + transport_name ? transport_name : "<no transport>", + (int)val); + } + + /* Sort the transport names (helps with unit testing). */ + smartlist_sort_strings(transports_used); + + /* Loop through all seen transports. */ + SMARTLIST_FOREACH_BEGIN(transports_used, const char *, transport_name) { + void *transport_count_ptr = strmap_get(transport_counts, transport_name); + uintptr_t transport_count = (uintptr_t) transport_count_ptr; + + log_debug(LD_GENERAL, "We got "U64_FORMAT" clients with transport '%s'.", + U64_PRINTF_ARG((uint64_t)transport_count), transport_name); + + smartlist_add_asprintf(string_chunks, "%s="U64_FORMAT, + transport_name, + U64_PRINTF_ARG(round_uint64_to_next_multiple_of( + (uint64_t)transport_count, + granularity))); + } SMARTLIST_FOREACH_END(transport_name); + + the_string = smartlist_join_strings(string_chunks, ",", 0, NULL); + + log_debug(LD_GENERAL, "Final bridge-ip-transports string: '%s'", the_string); + + done: + strmap_free(transport_counts, NULL); + SMARTLIST_FOREACH(transports_used, char *, s, tor_free(s)); + smartlist_free(transports_used); + SMARTLIST_FOREACH(string_chunks, char *, s, tor_free(s)); + smartlist_free(string_chunks); + + return the_string; +} + /** Return a newly allocated comma-separated string containing statistics * on network status downloads. The string contains the number of completed * requests, timeouts, and still running requests as well as the download @@ -1037,7 +1170,7 @@ geoip_reset_dirreq_stats(time_t now) if ((*ent)->action == GEOIP_CLIENT_NETWORKSTATUS) { this = *ent; next = HT_NEXT_RMV(clientmap, &client_history, ent); - tor_free(this); + clientmap_entry_free(this); } else { next = HT_NEXT(clientmap, &client_history, ent); } @@ -1132,7 +1265,7 @@ geoip_format_dirreq_stats(time_t now) time_t geoip_dirreq_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_dirreq_stats_interval) return 0; /* Not initialized. */ @@ -1146,21 +1279,13 @@ geoip_dirreq_stats_write(time_t now) str = geoip_format_dirreq_stats(now); /* Write dirreq-stats string to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "dirreq-stats", str, "dirreq statistics"); + /* Reset measurement interval start. */ + geoip_reset_dirreq_stats(now); } - filename = get_datadir_fname2("stats", "dirreq-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write dirreq statistics to disk!"); - - /* Reset measurement interval start. */ - geoip_reset_dirreq_stats(now); done: - tor_free(statsdir); - tor_free(filename); tor_free(str); return start_of_dirreq_stats_interval + WRITE_STATS_INTERVAL; } @@ -1197,6 +1322,8 @@ validate_bridge_stats(const char *stats_str, time_t now) const char *BRIDGE_STATS_END = "bridge-stats-end "; const char *BRIDGE_IPS = "bridge-ips "; const char *BRIDGE_IPS_EMPTY_LINE = "bridge-ips\n"; + const char *BRIDGE_TRANSPORTS = "bridge-ip-transports "; + const char *BRIDGE_TRANSPORTS_EMPTY_LINE = "bridge-ip-transports\n"; const char *tmp; time_t stats_end_time; int seconds; @@ -1231,6 +1358,15 @@ validate_bridge_stats(const char *stats_str, time_t now) return 0; } + /* Parse: "bridge-ip-transports PT=N,PT=N,..." */ + tmp = find_str_at_start_of_line(stats_str, BRIDGE_TRANSPORTS); + if (!tmp) { + /* Look if there is an empty "bridge-ip-transports" line */ + tmp = find_str_at_start_of_line(stats_str, BRIDGE_TRANSPORTS_EMPTY_LINE); + if (!tmp) + return 0; + } + return 1; } @@ -1244,7 +1380,8 @@ static char *bridge_stats_extrainfo = NULL; char * geoip_format_bridge_stats(time_t now) { - char *out = NULL, *country_data = NULL, *ipver_data = NULL; + char *out = NULL; + char *country_data = NULL, *ipver_data = NULL, *transport_data = NULL; long duration = now - start_of_bridge_stats_interval; char written[ISO_TIME_LEN+1]; @@ -1255,16 +1392,20 @@ geoip_format_bridge_stats(time_t now) format_iso_time(written, now); geoip_get_client_history(GEOIP_CLIENT_CONNECT, &country_data, &ipver_data); + transport_data = geoip_get_transport_history(); tor_asprintf(&out, "bridge-stats-end %s (%ld s)\n" "bridge-ips %s\n" - "bridge-ip-versions %s\n", + "bridge-ip-versions %s\n" + "bridge-ip-transports %s\n", written, duration, country_data ? country_data : "", - ipver_data ? ipver_data : ""); + ipver_data ? ipver_data : "", + transport_data ? transport_data : ""); tor_free(country_data); tor_free(ipver_data); + tor_free(transport_data); return out; } @@ -1297,7 +1438,7 @@ format_bridge_stats_controller(time_t now) time_t geoip_bridge_stats_write(time_t now) { - char *filename = NULL, *val = NULL, *statsdir = NULL; + char *val = NULL; /* Check if 24 hours have passed since starting measurements. */ if (now < start_of_bridge_stats_interval + WRITE_STATS_INTERVAL) @@ -1317,24 +1458,20 @@ geoip_bridge_stats_write(time_t now) start_of_bridge_stats_interval = now; /* Write it to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) - goto done; - filename = get_datadir_fname2("stats", "bridge-stats"); - - write_str_to_file(filename, bridge_stats_extrainfo, 0); - - /* Tell the controller, "hey, there are clients!" */ - { - char *controller_str = format_bridge_stats_controller(now); - if (controller_str) - control_event_clients_seen(controller_str); - tor_free(controller_str); + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "bridge-stats", + bridge_stats_extrainfo, "bridge statistics"); + + /* Tell the controller, "hey, there are clients!" */ + { + char *controller_str = format_bridge_stats_controller(now); + if (controller_str) + control_event_clients_seen(controller_str); + tor_free(controller_str); + } } - done: - tor_free(filename); - tor_free(statsdir); + done: return start_of_bridge_stats_interval + WRITE_STATS_INTERVAL; } @@ -1436,7 +1573,7 @@ geoip_format_entry_stats(time_t now) time_t geoip_entry_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_entry_stats_interval) return 0; /* Not initialized. */ @@ -1450,21 +1587,14 @@ geoip_entry_stats_write(time_t now) str = geoip_format_entry_stats(now); /* Write entry-stats string to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; - } - filename = get_datadir_fname2("stats", "entry-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write entry statistics to disk!"); + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "entry-stats", str, "entry statistics"); - /* Reset measurement interval start. */ - geoip_reset_entry_stats(now); + /* Reset measurement interval start. */ + geoip_reset_entry_stats(now); + } done: - tor_free(statsdir); - tor_free(filename); tor_free(str); return start_of_entry_stats_interval + WRITE_STATS_INTERVAL; } @@ -1534,7 +1664,7 @@ geoip_free_all(void) for (ent = HT_START(clientmap, &client_history); ent != NULL; ent = next) { this = *ent; next = HT_NEXT_RMV(clientmap, &client_history, ent); - tor_free(this); + clientmap_entry_free(this); } HT_CLEAR(clientmap, &client_history); } @@ -1549,5 +1679,6 @@ geoip_free_all(void) } clear_geoip_db(); + tor_free(bridge_stats_extrainfo); } diff --git a/src/or/geoip.h b/src/or/geoip.h index ebefee5f4..b9b53c300 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -12,10 +12,12 @@ #ifndef TOR_GEOIP_H #define TOR_GEOIP_H +#include "testsupport.h" + #ifdef GEOIP_PRIVATE -int geoip_parse_entry(const char *line, sa_family_t family); -int geoip_get_country_by_ipv4(uint32_t ipaddr); -int geoip_get_country_by_ipv6(const struct in6_addr *addr); +STATIC int geoip_parse_entry(const char *line, sa_family_t family); +STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr); +STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr); #endif int should_record_bridge_info(const or_options_t *options); int geoip_load_file(sa_family_t family, const char *filename); @@ -27,10 +29,12 @@ const char *geoip_db_digest(sa_family_t family); country_t geoip_get_country(const char *countrycode); void geoip_note_client_seen(geoip_client_action_t action, - const tor_addr_t *addr, time_t now); + const tor_addr_t *addr, const char *transport_name, + time_t now); void geoip_remove_old_clients(time_t cutoff); void geoip_note_ns_response(geoip_ns_response_t response); +char *geoip_get_transport_history(void); int geoip_get_client_history(geoip_client_action_t action, char **country_str, char **ipver_str); char *geoip_get_request_history(void); diff --git a/src/or/hibernate.c b/src/or/hibernate.c index a41257133..607dec8cd 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -255,6 +255,13 @@ accounting_get_interval_length(void) return (int)(interval_end_time - interval_start_time); } +/** Return the time at which the current accounting interval will end. */ +time_t +accounting_get_end_time(void) +{ + return interval_end_time; +} + /** Called from main.c to tell us that <b>seconds</b> seconds have * passed, <b>n_read</b> bytes have been read, and <b>n_written</b> * bytes have been written. */ @@ -1010,6 +1017,7 @@ getinfo_helper_accounting(control_connection_t *conn, return 0; } +#ifdef TOR_UNIT_TESTS /** * Manually change the hibernation state. Private; used only by the unit * tests. @@ -1019,4 +1027,5 @@ hibernate_set_state_for_testing_(hibernate_state_t newstate) { hibernate_state = newstate; } +#endif diff --git a/src/or/hibernate.h b/src/or/hibernate.h index d2d6989e1..4f7331ce8 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -15,6 +15,7 @@ int accounting_parse_options(const or_options_t *options, int validate_only); int accounting_is_enabled(const or_options_t *options); int accounting_get_interval_length(void); +time_t accounting_get_end_time(void); void configure_accounting(time_t now); void accounting_run_housekeeping(time_t now); void accounting_add_bytes(size_t n_read, size_t n_written, int seconds); @@ -45,8 +46,10 @@ typedef enum { HIBERNATE_STATE_INITIAL=5 } hibernate_state_t; +#ifdef TOR_UNIT_TESTS void hibernate_set_state_for_testing_(hibernate_state_t newstate); #endif +#endif #endif diff --git a/src/or/include.am b/src/or/include.am index 65dbeff53..47bdd0990 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -1,5 +1,13 @@ bin_PROGRAMS+= src/or/tor -noinst_LIBRARIES+= src/or/libtor.a +noinst_LIBRARIES += \ + src/or/libtor.a +if UNITTESTS_ENABLED +noinst_LIBRARIES += \ + src/or/libtor-testing.a +endif +if COVERAGE_ENABLED +noinst_PROGRAMS+= src/or/tor-cov +endif if BUILD_NT_SERVICES tor_platform_source=src/or/ntmain.c @@ -21,11 +29,12 @@ else onion_ntor_source= endif -src_or_libtor_a_SOURCES = \ +LIBTOR_A_SOURCES = \ src/or/addressmap.c \ src/or/buffers.c \ src/or/channel.c \ src/or/channeltls.c \ + src/or/circpathbias.c \ src/or/circuitbuild.c \ src/or/circuitlist.c \ src/or/circuitmux.c \ @@ -48,6 +57,7 @@ src_or_libtor_a_SOURCES = \ src/or/fp_pair.c \ src/or/geoip.c \ src/or/entrynodes.c \ + src/or/ext_orport.c \ src/or/hibernate.c \ src/or/main.c \ src/or/microdesc.c \ @@ -77,6 +87,9 @@ src_or_libtor_a_SOURCES = \ $(onion_ntor_source) \ src/or/config_codedigest.c +src_or_libtor_a_SOURCES = $(LIBTOR_A_SOURCES) +src_or_libtor_testing_a_SOURCES = $(LIBTOR_A_SOURCES) + #libtor_a_LIBADD = ../common/libor.a ../common/libor-crypto.a \ # ../common/libor-event.a @@ -90,6 +103,9 @@ AM_CPPFLAGS += -DSHARE_DATADIR="\"$(datadir)\"" \ -DLOCALSTATEDIR="\"$(localstatedir)\"" \ -DBINDIR="\"$(bindir)\"" +src_or_libtor_testing_a_CPPFLAGS = -DTOR_UNIT_TESTS $(AM_CPPFLAGS) +src_or_libtor_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + # -L flags need to go in LDFLAGS. -l flags need to go in LDADD. # This seems to matter nowhere but on windows, but I assure you that it # matters a lot there, and is quite hard to debug if you forget to do it. @@ -102,11 +118,24 @@ src_or_tor_LDADD = src/or/libtor.a src/common/libor.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ +if COVERAGE_ENABLED +src_or_tor_cov_SOURCES = src/or/tor_main.c +src_or_tor_cov_CPPFLAGS = -DTOR_UNIT_TESTS $(AM_CPPFLAGS) +src_or_tor_cov_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_or_tor_cov_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@ +src_or_tor_cov_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \ + src/common/libor-crypto-testing.a $(LIBDONNA) \ + src/common/libor-event-testing.a \ + @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ +endif + ORHEADERS = \ src/or/addressmap.h \ src/or/buffers.h \ src/or/channel.h \ src/or/channeltls.h \ + src/or/circpathbias.h \ src/or/circuitbuild.h \ src/or/circuitlist.h \ src/or/circuitmux.h \ @@ -127,6 +156,7 @@ ORHEADERS = \ src/or/dns.h \ src/or/dnsserv.h \ src/or/eventdns_tor.h \ + src/or/ext_orport.h \ src/or/fp_pair.h \ src/or/geoip.h \ src/or/entrynodes.h \ diff --git a/src/or/main.c b/src/or/main.c index deed798e8..e14001f10 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -13,6 +13,7 @@ #define MAIN_PRIVATE #include "or.h" #include "addressmap.h" +#include "backtrace.h" #include "buffers.h" #include "channel.h" #include "channeltls.h" @@ -21,6 +22,7 @@ #include "circuituse.h" #include "command.h" #include "config.h" +#include "confparse.h" #include "connection.h" #include "connection_edge.h" #include "connection_or.h" @@ -52,11 +54,13 @@ #include "routerparse.h" #include "statefile.h" #include "status.h" +#include "ext_orport.h" #ifdef USE_DMALLOC #include <dmalloc.h> #include <openssl/crypto.h> #endif #include "memarea.h" +#include "../common/sandbox.h" #ifdef HAVE_EVENT2_EVENT_H #include <event2/event.h> @@ -155,8 +159,6 @@ int can_complete_circuit=0; /** How often do we 'forgive' undownloadable router descriptors and attempt * to download them again? */ #define DESCRIPTOR_FAILURE_RESET_INTERVAL (60*60) -/** How long do we let a directory connection stall before expiring it? */ -#define DIR_CONN_MAX_STALL (5*60) /** Decides our behavior when no logs are configured/before any * logs have been configured. For 0, we log notice to stdout as normal. @@ -351,6 +353,8 @@ connection_remove(connection_t *conn) (int)conn->s, conn_type_to_string(conn->type), smartlist_len(connection_array)); + control_event_conn_bandwidth(conn); + tor_assert(conn->conn_array_index >= 0); current_index = conn->conn_array_index; connection_unregister_events(conn); /* This is redundant, but cheap. */ @@ -414,6 +418,19 @@ connection_unlink(connection_t *conn) connection_free(conn); } +/** Initialize the global connection list, closeable connection list, + * and active connection list. */ +STATIC void +init_connection_lists(void) +{ + if (!connection_array) + connection_array = smartlist_new(); + if (!closeable_connection_lst) + closeable_connection_lst = smartlist_new(); + if (!active_linked_connection_lst) + active_linked_connection_lst = smartlist_new(); +} + /** Schedule <b>conn</b> to be closed. **/ void add_connection_to_closeable_list(connection_t *conn) @@ -507,8 +524,8 @@ connection_is_reading(connection_t *conn) } /** Tell the main loop to stop notifying <b>conn</b> of any read events. */ -void -connection_stop_reading(connection_t *conn) +MOCK_IMPL(void, +connection_stop_reading,(connection_t *conn)) { tor_assert(conn); @@ -532,8 +549,8 @@ connection_stop_reading(connection_t *conn) } /** Tell the main loop to start notifying <b>conn</b> of any read events. */ -void -connection_start_reading(connection_t *conn) +MOCK_IMPL(void, +connection_start_reading,(connection_t *conn)) { tor_assert(conn); @@ -572,8 +589,8 @@ connection_is_writing(connection_t *conn) } /** Tell the main loop to stop notifying <b>conn</b> of any write events. */ -void -connection_stop_writing(connection_t *conn) +MOCK_IMPL(void, +connection_stop_writing,(connection_t *conn)) { tor_assert(conn); @@ -598,8 +615,8 @@ connection_stop_writing(connection_t *conn) } /** Tell the main loop to start notifying <b>conn</b> of any write events. */ -void -connection_start_writing(connection_t *conn) +MOCK_IMPL(void, +connection_start_writing,(connection_t *conn)) { tor_assert(conn); @@ -687,7 +704,7 @@ connection_stop_reading_from_linked_conn(connection_t *conn) } /** Close all connections that have been scheduled to get closed. */ -static void +STATIC void close_closeable_connections(void) { int i; @@ -1028,9 +1045,11 @@ run_connection_housekeeping(int i, time_t now) * if a server or received if a client) for 5 min */ if (conn->type == CONN_TYPE_DIR && ((DIR_CONN_IS_SERVER(conn) && - conn->timestamp_lastwritten + DIR_CONN_MAX_STALL < now) || + conn->timestamp_lastwritten + + options->TestingDirConnectionMaxStall < now) || (!DIR_CONN_IS_SERVER(conn) && - conn->timestamp_lastread + DIR_CONN_MAX_STALL < now))) { + conn->timestamp_lastread + + options->TestingDirConnectionMaxStall < now))) { log_info(LD_DIR,"Expiring wedged directory conn (fd %d, purpose %d)", (int)conn->s, conn->purpose); /* This check is temporary; it's to let us know whether we should consider @@ -1153,6 +1172,7 @@ run_scheduled_events(time_t now) static time_t time_to_check_v3_certificate = 0; static time_t time_to_check_listeners = 0; static time_t time_to_check_descriptor = 0; + static time_t time_to_download_networkstatus = 0; static time_t time_to_shrink_memory = 0; static time_t time_to_try_getting_descriptors = 0; static time_t time_to_reset_descriptor_failures = 0; @@ -1447,10 +1467,18 @@ run_scheduled_events(time_t now) networkstatus_v2_list_clean(now); /* Remove dead routers. */ routerlist_remove_old_routers(); + } - /* Also, once per minute, check whether we want to download any - * networkstatus documents. - */ + /* 2c. Every minute (or every second if TestingTorNetwork), check + * whether we want to download any networkstatus documents. */ + +/* How often do we check whether we should download network status + * documents? */ +#define networkstatus_dl_check_interval(o) ((o)->TestingTorNetwork ? 1 : 60) + + if (time_to_download_networkstatus < now && !options->DisableNetwork) { + time_to_download_networkstatus = + now + networkstatus_dl_check_interval(options); update_networkstatus_downloads(now); } @@ -1643,6 +1671,9 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written); control_event_stream_bandwidth_used(); + control_event_conn_bandwidth_used(); + control_event_circ_bandwidth_used(); + control_event_circuit_cell_stats(); if (server_mode(options) && !net_is_disabled() && @@ -1870,7 +1901,7 @@ do_hup(void) } /** Tor main loop. */ -/* static */ int +int do_main_loop(void) { int loop_result; @@ -2297,21 +2328,17 @@ handle_signals(int is_parent) /** Main entry point for the Tor command-line client. */ -/* static */ int +int tor_init(int argc, char *argv[]) { - char buf[256]; - int i, quiet = 0; + char progname[256]; + int quiet = 0; + time_of_process_start = time(NULL); - if (!connection_array) - connection_array = smartlist_new(); - if (!closeable_connection_lst) - closeable_connection_lst = smartlist_new(); - if (!active_linked_connection_lst) - active_linked_connection_lst = smartlist_new(); + init_connection_lists(); /* Have the log set up with our application name. */ - tor_snprintf(buf, sizeof(buf), "Tor %s", get_version()); - log_set_application_name(buf); + tor_snprintf(progname, sizeof(progname), "Tor %s", get_version()); + log_set_application_name(progname); /* Initialize the history structures. */ rep_hist_init(); /* Initialize the service cache. */ @@ -2319,17 +2346,31 @@ tor_init(int argc, char *argv[]) addressmap_init(); /* Init the client dns cache. Do it always, since it's * cheap. */ + { /* We search for the "quiet" option first, since it decides whether we * will log anything at all to the command line. */ - for (i=1;i<argc;++i) { - if (!strcmp(argv[i], "--hush")) - quiet = 1; - if (!strcmp(argv[i], "--quiet")) - quiet = 2; - /* --version implies --quiet */ - if (!strcmp(argv[i], "--version")) - quiet = 2; + config_line_t *opts = NULL, *cmdline_opts = NULL; + const config_line_t *cl; + (void) config_parse_commandline(argc, argv, 1, &opts, &cmdline_opts); + for (cl = cmdline_opts; cl; cl = cl->next) { + if (!strcmp(cl->key, "--hush")) + quiet = 1; + if (!strcmp(cl->key, "--quiet") || + !strcmp(cl->key, "--dump-config")) + quiet = 2; + /* --version, --digests, and --help imply --hush */ + if (!strcmp(cl->key, "--version") || !strcmp(cl->key, "--digests") || + !strcmp(cl->key, "--list-torrc-options") || + !strcmp(cl->key, "--library-versions") || + !strcmp(cl->key, "-h") || !strcmp(cl->key, "--help")) { + if (quiet < 1) + quiet = 1; + } + } + config_free_lines(opts); + config_free_lines(cmdline_opts); } + /* give it somewhere to log to initially */ switch (quiet) { case 2: @@ -2351,11 +2392,12 @@ tor_init(int argc, char *argv[]) #else ""; #endif - log_notice(LD_GENERAL, "Tor v%s %srunning on %s with Libevent %s " - "and OpenSSL %s.", version, bev_str, + log_notice(LD_GENERAL, "Tor v%s %srunning on %s with Libevent %s, " + "OpenSSL %s and Zlib %s.", version, bev_str, get_uname(), tor_libevent_get_version_str(), - crypto_openssl_get_version_str()); + crypto_openssl_get_version_str(), + tor_zlib_get_version_str()); log_notice(LD_GENERAL, "Tor can't help you if you use it wrong! " "Learn how to be safe at " @@ -2497,6 +2539,8 @@ tor_free_all(int postfork) memarea_clear_freelist(); nodelist_free_all(); microdesc_free_all(); + ext_orport_free_all(); + control_free_all(); if (!postfork) { config_free_all(); or_state_free_all(); @@ -2563,7 +2607,7 @@ tor_cleanup(void) } /** Read/create keys as needed, and echo our fingerprint to stdout. */ -/* static */ int +static int do_list_fingerprint(void) { char buf[FINGERPRINT_LEN+1]; @@ -2571,7 +2615,7 @@ do_list_fingerprint(void) const char *nickname = get_options()->Nickname; if (!server_mode(get_options())) { log_err(LD_GENERAL, - "Clients don't have long-term identity keys. Exiting.\n"); + "Clients don't have long-term identity keys. Exiting."); return -1; } tor_assert(nickname); @@ -2593,7 +2637,7 @@ do_list_fingerprint(void) /** Entry point for password hashing: take the desired password from * the command line, and print its salted hash to stdout. **/ -/* static */ void +static void do_hash_password(void) { @@ -2609,6 +2653,34 @@ do_hash_password(void) printf("16:%s\n",output); } +/** Entry point for configuration dumping: write the configuration to + * stdout. */ +static int +do_dump_config(void) +{ + const or_options_t *options = get_options(); + const char *arg = options->command_arg; + int how; + char *opts; + if (!strcmp(arg, "short")) { + how = OPTIONS_DUMP_MINIMAL; + } else if (!strcmp(arg, "non-builtin")) { + how = OPTIONS_DUMP_DEFAULTS; + } else if (!strcmp(arg, "full")) { + how = OPTIONS_DUMP_ALL; + } else { + printf("%s is not a recognized argument to --dump-config. " + "Please select 'short', 'non-builtin', or 'full'", arg); + return -1; + } + + opts = options_dump(options, how); + printf("%s", opts); + tor_free(opts); + + return 0; +} + #if defined (WINCE) int find_flashcard_path(PWCHAR path, size_t size) @@ -2634,6 +2706,95 @@ find_flashcard_path(PWCHAR path, size_t size) } #endif +static void +init_addrinfo(void) +{ + char hname[256]; + + // host name to sandbox + gethostname(hname, sizeof(hname)); + sandbox_add_addrinfo(hname); +} + +static sandbox_cfg_t* +sandbox_init_filter(void) +{ + sandbox_cfg_t *cfg = sandbox_cfg_new(); + + sandbox_cfg_allow_openat_filename(&cfg, + get_datadir_fname("cached-status"), 1); + + sandbox_cfg_allow_open_filename_array(&cfg, + get_datadir_fname("cached-certs"), 1, + get_datadir_fname("cached-certs.tmp"), 1, + get_datadir_fname("cached-consensus"), 1, + get_datadir_fname("unverified-consensus"), 1, + get_datadir_fname("unverified-consensus.tmp"), 1, + get_datadir_fname("cached-microdesc-consensus"), 1, + get_datadir_fname("cached-microdesc-consensus.tmp"), 1, + get_datadir_fname("cached-microdescs"), 1, + get_datadir_fname("cached-microdescs.tmp"), 1, + get_datadir_fname("cached-microdescs.new"), 1, + get_datadir_fname("cached-microdescs.new.tmp"), 1, + get_datadir_fname("unverified-microdesc-consensus"), 1, + get_datadir_fname("cached-descriptors"), 1, + get_datadir_fname("cached-descriptors.new"), 1, + get_datadir_fname("cached-descriptors.tmp"), 1, + get_datadir_fname("cached-descriptors.new.tmp"), 1, + get_datadir_fname("cached-descriptors.tmp.tmp"), 1, + get_datadir_fname("cached-extrainfo"), 1, + get_datadir_fname("state.tmp"), 1, + get_datadir_fname("unparseable-desc.tmp"), 1, + get_datadir_fname("unparseable-desc"), 1, + "/dev/srandom", 0, + "/dev/urandom", 0, + "/dev/random", 0, + NULL, 0 + ); + + sandbox_cfg_allow_stat_filename_array(&cfg, + get_datadir_fname(NULL), 1, + get_datadir_fname("lock"), 1, + get_datadir_fname("state"), 1, + get_datadir_fname("router-stability"), 1, + get_datadir_fname("cached-extrainfo.new"), 1, + NULL, 0 + ); + + // orport + if (server_mode(get_options())) { + sandbox_cfg_allow_open_filename_array(&cfg, + get_datadir_fname2("keys", "secret_id_key"), 1, + get_datadir_fname2("keys", "secret_onion_key"), 1, + get_datadir_fname2("keys", "secret_onion_key_ntor"), 1, + get_datadir_fname2("keys", "secret_onion_key_ntor.tmp"), 1, + get_datadir_fname2("keys", "secret_id_key.old"), 1, + get_datadir_fname2("keys", "secret_onion_key.old"), 1, + get_datadir_fname2("keys", "secret_onion_key_ntor.old"), 1, + get_datadir_fname2("keys", "secret_onion_key.tmp"), 1, + get_datadir_fname2("keys", "secret_id_key.tmp"), 1, + get_datadir_fname("fingerprint"), 1, + get_datadir_fname("fingerprint.tmp"), 1, + get_datadir_fname("cached-consensus"), 1, + get_datadir_fname("cached-consensus.tmp"), 1, + "/etc/resolv.conf", 0, + NULL, 0 + ); + + sandbox_cfg_allow_stat_filename_array(&cfg, + get_datadir_fname("keys"), 1, + get_datadir_fname("stats/dirreq-stats"), 1, + NULL, 0 + ); + } + + sandbox_cfg_allow_execve(&cfg, "/usr/local/bin/tor"); + + init_addrinfo(); + + return cfg; +} + /** Main entry point for the Tor process. Called from main(). */ /* This function is distinct from main() only so we can link main.c into * the unittest binary without conflicting with the unittests' main. */ @@ -2680,6 +2841,8 @@ tor_main(int argc, char *argv[]) } #endif + configure_backtrace_handler(get_version()); + update_approx_time(time(NULL)); tor_threads_init(); init_logging(); @@ -2700,6 +2863,22 @@ tor_main(int argc, char *argv[]) #endif if (tor_init(argc, argv)<0) return -1; + + if (get_options()->Sandbox) { + sandbox_cfg_t* cfg = sandbox_init_filter(); + + if (sandbox_init(cfg)) { + log_err(LD_BUG,"Failed to create syscall sandbox filter"); + return -1; + } + + // registering libevent rng +#ifdef HAVE_EVUTIL_SECURE_RNG_SET_URANDOM_DEVICE_FILE + evutil_secure_rng_set_urandom_device_file( + (char*) sandbox_intern_string("/dev/urandom")); +#endif + } + switch (get_options()->command) { case CMD_RUN_TOR: #ifdef NT_SERVICE @@ -2718,6 +2897,9 @@ tor_main(int argc, char *argv[]) printf("Configuration was valid\n"); result = 0; break; + case CMD_DUMP_CONFIG: + result = do_dump_config(); + break; case CMD_RUN_UNITTESTS: /* only set by test.c */ default: log_warn(LD_BUG,"Illegal command number %d: internal error.", diff --git a/src/or/main.h b/src/or/main.h index 338449b6a..df302ffa7 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -36,12 +36,12 @@ typedef enum watchable_events { } watchable_events_t; void connection_watch_events(connection_t *conn, watchable_events_t events); int connection_is_reading(connection_t *conn); -void connection_stop_reading(connection_t *conn); -void connection_start_reading(connection_t *conn); +MOCK_DECL(void,connection_stop_reading,(connection_t *conn)); +MOCK_DECL(void,connection_start_reading,(connection_t *conn)); int connection_is_writing(connection_t *conn); -void connection_stop_writing(connection_t *conn); -void connection_start_writing(connection_t *conn); +MOCK_DECL(void,connection_stop_writing,(connection_t *conn)); +MOCK_DECL(void,connection_start_writing,(connection_t *conn)); void connection_stop_reading_from_linked_conn(connection_t *conn); @@ -66,11 +66,12 @@ void tor_free_all(int postfork); int tor_main(int argc, char *argv[]); -#ifdef MAIN_PRIVATE int do_main_loop(void); -int do_list_fingerprint(void); -void do_hash_password(void); int tor_init(int argc, char **argv); + +#ifdef MAIN_PRIVATE +STATIC void init_connection_lists(void); +STATIC void close_closeable_connections(void); #endif #endif diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 90ac0ac64..11249910c 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -139,7 +139,7 @@ get_microdesc_cache(void) * ending at <b>eos</b>, and store them in <b>cache</b>. If <b>no_save</b>, * mark them as non-writable to disk. If <b>where</b> is SAVED_IN_CACHE, * leave their bodies as pointers to the mmap'd cache. If where is - * <b>SAVED_NOWHERE</b>, do not allow annotations. If listed_at is positive, + * <b>SAVED_NOWHERE</b>, do not allow annotations. If listed_at is not -1, * set the last_listed field of every microdesc to listed_at. If * requested_digests is non-null, then it contains a list of digests we mean * to allow, so we should reject any non-requested microdesc with a different @@ -158,7 +158,7 @@ microdescs_add_to_cache(microdesc_cache_t *cache, descriptors = microdescs_parse_from_string(s, eos, allow_annotations, where); - if (listed_at > 0) { + if (listed_at != (time_t)-1) { SMARTLIST_FOREACH(descriptors, microdesc_t *, md, md->last_listed = listed_at); } @@ -489,7 +489,8 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) "By my count, I'm at "I64_FORMAT ", but I should be at "I64_FORMAT, I64_PRINTF_ARG(off), I64_PRINTF_ARG(off_real)); - off = off_real; + if (off_real >= 0) + off = off_real; } if (md->saved_location != SAVED_IN_CACHE) { tor_free(md->body); @@ -696,7 +697,7 @@ microdesc_list_missing_digest256(networkstatus_t *ns, microdesc_cache_t *cache, continue; if (downloadable_only && !download_status_is_ready(&rs->dl_status, now, - MAX_MICRODESC_DOWNLOAD_FAILURES)) + get_options()->TestingMicrodescMaxDownloadTries)) continue; if (skip && digestmap_get(skip, rs->descriptor_digest)) continue; diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 1b5c6dbb3..a935a4672 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1203,8 +1203,6 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor) return flavor == usable_consensus_flavor(); } -/** How many times will we try to fetch a consensus before we give up? */ -#define CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES 8 /** How long will we hang onto a possibly live consensus for which we're * fetching certs before we check whether there is a better one? */ #define DELAY_WHILE_FETCHING_CERTS (20*60) @@ -1238,7 +1236,7 @@ update_consensus_networkstatus_downloads(time_t now) resource = networkstatus_get_flavor_name(i); if (!download_status_is_ready(&consensus_dl_status[i], now, - CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES)) + options->TestingConsensusMaxDownloadTries)) continue; /* We failed downloading a consensus too recently. */ if (connection_dir_get_by_purpose_and_resource( DIR_PURPOSE_FETCH_CONSENSUS, resource)) @@ -1824,7 +1822,8 @@ networkstatus_set_current_consensus(const char *consensus, * current consensus really alter our view of any OR's rate limits? */ connection_or_update_token_buckets(get_connection_array(), options); - circuit_build_times_new_consensus_params(&circ_times, current_consensus); + circuit_build_times_new_consensus_params(get_circuit_build_times_mutable(), + current_consensus); } if (directory_caches_dir_info(options)) { @@ -2172,9 +2171,17 @@ networkstatus_dump_bridge_status_to_file(time_t now) char *status = networkstatus_getinfo_by_purpose("bridge", now); const or_options_t *options = get_options(); char *fname = NULL; + char *thresholds = NULL, *thresholds_and_status = NULL; + routerlist_t *rl = router_get_routerlist(); + dirserv_compute_bridge_flag_thresholds(rl); + thresholds = dirserv_get_flag_thresholds_line(); + tor_asprintf(&thresholds_and_status, "flag-thresholds %s\n%s", + thresholds, status); tor_asprintf(&fname, "%s"PATH_SEPARATOR"networkstatus-bridges", options->DataDirectory); - write_str_to_file(fname,status,0); + write_str_to_file(fname,thresholds_and_status,0); + tor_free(thresholds); + tor_free(thresholds_and_status); tor_free(fname); tor_free(status); } diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 178f084b6..86219b77c 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -25,6 +25,8 @@ static void nodelist_drop_node(node_t *node, int remove_from_ht); static void node_free(node_t *node); static void update_router_have_minimum_dir_info(void); +static double get_frac_paths_needed_for_circs(const or_options_t *options, + const networkstatus_t *ns); /** A nodelist_t holds a node_t object for every router we're "willing to use * for something". Specifically, it should hold a node_t for every node that @@ -1317,7 +1319,7 @@ count_usable_descriptors(int *num_present, int *num_usable, md ? "microdesc" : "desc", exit_only ? " exits" : "s"); } -/** Return an extimate of which fraction of usable paths through the Tor +/** Return an estimate of which fraction of usable paths through the Tor * network we have available for use. */ static double compute_frac_paths_available(const networkstatus_t *consensus, @@ -1372,13 +1374,14 @@ compute_frac_paths_available(const networkstatus_t *consensus, if (f_myexit < f_exit) f_exit = f_myexit; - tor_asprintf(status_out, - "%d%% of guards bw, " - "%d%% of midpoint bw, and " - "%d%% of exit bw", - (int)(f_guard*100), - (int)(f_mid*100), - (int)(f_exit*100)); + if (status_out) + tor_asprintf(status_out, + "%d%% of guards bw, " + "%d%% of midpoint bw, and " + "%d%% of exit bw", + (int)(f_guard*100), + (int)(f_mid*100), + (int)(f_exit*100)); return f_guard * f_mid * f_exit; } @@ -1391,19 +1394,19 @@ count_loading_descriptors_progress(void) { int num_present = 0, num_usable=0; time_t now = time(NULL); + const or_options_t *options = get_options(); const networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); - double fraction; + double paths, fraction; if (!consensus) return 0; /* can't count descriptors if we have no list of them */ - count_usable_descriptors(&num_present, &num_usable, NULL, - consensus, get_options(), now, NULL, 0); + paths = compute_frac_paths_available(consensus, options, now, + &num_present, &num_usable, + NULL); - if (num_usable == 0) - return 0; /* don't div by 0 */ - fraction = num_present / (num_usable/4.); + fraction = paths / get_frac_paths_needed_for_circs(options,consensus); if (fraction > 1.0) return 0; /* it's not the number of descriptors holding us back */ return BOOTSTRAP_STATUS_LOADING_DESCRIPTORS + (int) diff --git a/src/or/ntmain.c b/src/or/ntmain.c index 8b67b8682..e84831404 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -3,7 +3,6 @@ * Copyright (c) 2007-2013, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define MAIN_PRIVATE #include "or.h" #include "config.h" #include "main.h" @@ -315,6 +314,7 @@ nt_service_main(void) case CMD_LIST_FINGERPRINT: case CMD_HASH_PASSWORD: case CMD_VERIFY_CONFIG: + case CMD_DUMP_CONFIG: log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, " "--hash-password, or --verify-config) in NT service."); break; diff --git a/src/or/onion.c b/src/or/onion.c index 1a0bcf106..3e1d63d4e 100644 --- a/src/or/onion.c +++ b/src/or/onion.c @@ -22,7 +22,6 @@ #include "relay.h" #include "rephist.h" #include "router.h" -#include "tor_queue.h" /** Type for a linked list of circuits that are waiting for a free CPU worker * to process a waiting onion handshake. */ @@ -59,7 +58,7 @@ static void onion_queue_entry_remove(onion_queue_t *victim); * MAX_ONIONSKIN_CHALLENGE/REPLY_LEN." Also, make sure that we can pass * over-large values via EXTEND2/EXTENDED2, for future-compatibility.*/ -/** Return true iff we have room to queue another oninoskin of type +/** Return true iff we have room to queue another onionskin of type * <b>type</b>. */ static int have_room_for_onionskin(uint16_t type) @@ -870,7 +869,7 @@ extend_cell_parse(extend_cell_t *cell_out, const uint8_t command, cell_out->cell_type = RELAY_COMMAND_EXTEND2; ++payload; /* Parse the specifiers. We'll only take the first IPv4 and first IPv6 - * addres, and the node ID, and ignore everything else */ + * address, and the node ID, and ignore everything else */ for (i = 0; i < n_specs; ++i) { if (eop - payload < 2) return -1; diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c index aa034a8bd..8e778dbc6 100644 --- a/src/or/onion_fast.c +++ b/src/or/onion_fast.c @@ -22,7 +22,7 @@ fast_handshake_state_free(fast_handshake_state_t *victim) tor_free(victim); } -/** Create the state needed to perform a CREATE_FAST hasnshake. Return 0 +/** Create the state needed to perform a CREATE_FAST handshake. Return 0 * on success, -1 on failure. */ int fast_onionskin_create(fast_handshake_state_t **handshake_state_out, diff --git a/src/or/or.h b/src/or/or.h index 5318b0fe5..7df6c37f7 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -99,6 +99,7 @@ #include "ht.h" #include "replaycache.h" #include "crypto_curve25519.h" +#include "tor_queue.h" /* These signals are defined to help handle_control_signal work. */ @@ -227,8 +228,14 @@ typedef enum { #define CONN_TYPE_AP_NATD_LISTENER 14 /** Type for sockets listening for DNS requests. */ #define CONN_TYPE_AP_DNS_LISTENER 15 -#define CONN_TYPE_MAX_ 15 -/* !!!! If CONN_TYPE_MAX_ is ever over 15, we must grow the type field in + +/** Type for connections from the Extended ORPort. */ +#define CONN_TYPE_EXT_OR 16 +/** Type for sockets listening for Extended ORPort connections. */ +#define CONN_TYPE_EXT_OR_LISTENER 17 + +#define CONN_TYPE_MAX_ 17 +/* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in * connection_t. */ /* Proxy client types */ @@ -238,7 +245,9 @@ typedef enum { #define PROXY_SOCKS5 3 /* !!!! If there is ever a PROXY_* type over 2, we must grow the proxy_type * field in or_connection_t */ -/* pluggable transports proxy type */ + +/* Pluggable transport proxy type. Don't use this in or_connection_t, + * instead use the actual underlying proxy type (see above). */ #define PROXY_PLUGGABLE 4 /* Proxy client handshake states */ @@ -306,6 +315,25 @@ typedef enum { #define OR_CONN_STATE_OPEN 8 #define OR_CONN_STATE_MAX_ 8 +/** States of the Extended ORPort protocol. Be careful before changing + * the numbers: they matter. */ +#define EXT_OR_CONN_STATE_MIN_ 1 +/** Extended ORPort authentication is waiting for the authentication + * type selected by the client. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE 1 +/** Extended ORPort authentication is waiting for the client nonce. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE 2 +/** Extended ORPort authentication is waiting for the client hash. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH 3 +#define EXT_OR_CONN_STATE_AUTH_MAX 3 +/** Authentication finished and the Extended ORPort is now accepting + * traffic. */ +#define EXT_OR_CONN_STATE_OPEN 4 +/** Extended ORPort is flushing its last messages and preparing to + * start accepting OR connections. */ +#define EXT_OR_CONN_STATE_FLUSHING 5 +#define EXT_OR_CONN_STATE_MAX_ 5 + #define EXIT_CONN_STATE_MIN_ 1 /** State for an exit connection: waiting for response from DNS farm. */ #define EXIT_CONN_STATE_RESOLVING 1 @@ -823,9 +851,15 @@ typedef enum { /** Maximum number of queued cells on a circuit for which we are the * midpoint before we give up and kill it. This must be >= circwindow * to avoid killing innocent circuits, and >= circwindow*2 to give - * leaky-pipe a chance for being useful someday. + * leaky-pipe a chance of working someday. The ORCIRC_MAX_MIDDLE_KILL_THRESH + * ratio controls the margin of error between emitting a warning and + * killing the circuit. + */ +#define ORCIRC_MAX_MIDDLE_CELLS (CIRCWINDOW_START_MAX*2) +/** Ratio of hard (circuit kill) to soft (warning) thresholds for the + * ORCIRC_MAX_MIDDLE_CELLS tests. */ -#define ORCIRC_MAX_MIDDLE_CELLS (21*(CIRCWINDOW_START_MAX)/10) +#define ORCIRC_MAX_MIDDLE_KILL_THRESH (1.1f) /* Cell commands. These values are defined in tor-spec.txt. */ #define CELL_PADDING 0 @@ -846,6 +880,7 @@ typedef enum { #define CELL_AUTH_CHALLENGE 130 #define CELL_AUTHENTICATE 131 #define CELL_AUTHORIZE 132 +#define CELL_COMMAND_MAX_ 132 /** How long to test reachability before complaining to the user. */ #define TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT (20*60) @@ -1073,9 +1108,17 @@ typedef struct var_cell_t { uint8_t payload[FLEXIBLE_ARRAY_MEMBER]; } var_cell_t; +/** A parsed Extended ORPort message. */ +typedef struct ext_or_cmd_t { + uint16_t cmd; /** Command type */ + uint16_t len; /** Body length */ + char body[FLEXIBLE_ARRAY_MEMBER]; /** Message body */ +} ext_or_cmd_t; + /** A cell as packed for writing to the network. */ typedef struct packed_cell_t { - struct packed_cell_t *next; /**< Next cell queued on this circuit. */ + /** Next cell queued on this circuit. */ + TOR_SIMPLEQ_ENTRY(packed_cell_t) next; char body[CELL_MAX_NETWORK_SIZE]; /**< Cell as packed for network. */ uint32_t inserted_time; /**< Time (in milliseconds since epoch, with high * bits truncated) when this cell was inserted. */ @@ -1099,13 +1142,30 @@ typedef struct insertion_time_queue_t { struct insertion_time_elem_t *last; /**< Last element in queue. */ } insertion_time_queue_t; +/** Number of cells with the same command consecutively added to a circuit + * queue; used for cell statistics only if CELL_STATS events are enabled. */ +typedef struct insertion_command_elem_t { + struct insertion_command_elem_t *next; /**< Next element in queue. */ + /** Which command did these consecutively added cells have? */ + uint8_t command; + unsigned counter; /**< How many cells were inserted? */ +} insertion_command_elem_t; + +/** Queue of insertion commands. */ +typedef struct insertion_command_queue_t { + struct insertion_command_elem_t *first; /**< First element in queue. */ + struct insertion_command_elem_t *last; /**< Last element in queue. */ +} insertion_command_queue_t; + /** A queue of cells on a circuit, waiting to be added to the * or_connection_t's outbuf. */ typedef struct cell_queue_t { - packed_cell_t *head; /**< The first cell, or NULL if the queue is empty. */ - packed_cell_t *tail; /**< The last cell, or NULL if the queue is empty. */ + /** Linked list of packed_cell_t*/ + TOR_SIMPLEQ_HEAD(cell_simpleq, packed_cell_t) head; int n; /**< The number of cells in the queue. */ insertion_time_queue_t *insertion_times; /**< Insertion times of cells. */ + /** Commands of inserted cells. */ + insertion_command_queue_t *insertion_commands; } cell_queue_t; /** Beginning of a RELAY cell payload. */ @@ -1158,7 +1218,7 @@ typedef struct connection_t { * *_CONNECTION_MAGIC. */ uint8_t state; /**< Current state of this connection. */ - unsigned int type:4; /**< What kind of connection is this? */ + unsigned int type:5; /**< What kind of connection is this? */ unsigned int purpose:5; /**< Only used for DIR and EXIT types currently. */ /* The next fields are all one-bit booleans. Some are only applicable to @@ -1242,6 +1302,14 @@ typedef struct connection_t { /** Unique identifier for this connection on this Tor instance. */ uint64_t global_identifier; + + /** Bytes read since last call to control_event_conn_bandwidth_used(). + * Only used if we're configured to emit CONN_BW events. */ + uint32_t n_read_conn_bw; + + /** Bytes written since last call to control_event_conn_bandwidth_used(). + * Only used if we're configured to emit CONN_BW events. */ + uint32_t n_written_conn_bw; } connection_t; /** Subtype of connection_t; used for a listener socket. */ @@ -1403,6 +1471,9 @@ typedef struct or_handshake_state_t { /**@}*/ } or_handshake_state_t; +/** Length of Extended ORPort connection identifier. */ +#define EXT_OR_CONN_ID_LEN DIGEST_LEN /* 20 */ + /** Subtype of connection_t for an "OR connection" -- that is, one that speaks * cells over TLS. */ typedef struct or_connection_t { @@ -1411,6 +1482,20 @@ typedef struct or_connection_t { /** Hash of the public RSA key for the other side's identity key, or zeroes * if the other side hasn't shown us a valid identity key. */ char identity_digest[DIGEST_LEN]; + + /** Extended ORPort connection identifier. */ + char *ext_or_conn_id; + /** This is the ClientHash value we expect to receive from the + * client during the Extended ORPort authentication protocol. We + * compute it upon receiving the ClientNoce from the client, and we + * compare it with the acual ClientHash value sent by the + * client. */ + char *ext_or_auth_correct_client_hash; + /** String carrying the name of the pluggable transport + * (e.g. "obfs2") that is obfuscating this connection. If no + * pluggable transports are used, it's NULL. */ + char *ext_or_transport; + char *nickname; /**< Nickname of OR on other side (if any). */ tor_tls_t *tls; /**< TLS connection state. */ @@ -1468,6 +1553,12 @@ typedef struct or_connection_t { struct or_connection_t *next_with_same_id; /**< Next connection with same * identity digest as this one. */ + /** Last emptied read token bucket in msec since midnight; only used if + * TB_EMPTY events are enabled. */ + uint32_t read_emptied_time; + /** Last emptied write token bucket in msec since midnight; only used if + * TB_EMPTY events are enabled. */ + uint32_t write_emptied_time; } or_connection_t; /** Subtype of connection_t for an "edge connection" -- that is, an entry (ap) @@ -2294,14 +2385,6 @@ typedef struct node_t { } node_t; -/** How many times will we try to download a router's descriptor before giving - * up? */ -#define MAX_ROUTERDESC_DOWNLOAD_FAILURES 8 - -/** How many times will we try to download a microdescriptor before giving - * up? */ -#define MAX_MICRODESC_DOWNLOAD_FAILURES 8 - /** Contents of a v2 (non-consensus, non-vote) network status object. */ typedef struct networkstatus_v2_t { /** When did we receive the network-status document? */ @@ -2511,10 +2594,6 @@ typedef struct desc_store_t { * filename for a temporary file when rebuilding the store, and .new to this * filename for the journal. */ const char *fname_base; - /** Alternative (obsolete) value for fname_base: if the file named by - * fname_base isn't present, we read from here instead, but we never write - * here. */ - const char *fname_alt_base; /** Human-readable description of what this store contains. */ const char *description; @@ -2591,9 +2670,6 @@ typedef struct authority_cert_t { uint32_t addr; /** This authority's directory port. */ uint16_t dir_port; - /** True iff this certificate was cross-certified by signing the identity - * key with the signing key. */ - uint8_t is_cross_certified; } authority_cert_t; /** Bitfield enum type listing types of information that directory authorities @@ -2743,6 +2819,19 @@ typedef struct { struct create_cell_t; +/** Entry in the cell stats list of a circuit; used only if CELL_STATS + * events are enabled. */ +typedef struct testing_cell_stats_entry_t { + uint8_t command; /**< cell command number. */ + /** Waiting time in centiseconds if this event is for a removed cell, + * or 0 if this event is for adding a cell to the queue. 22 bits can + * store more than 11 hours, enough to assume that a circuit with this + * delay would long have been closed. */ + unsigned int waiting_time:22; + unsigned int removed:1; /**< 0 for added to, 1 for removed from queue. */ + unsigned int exitward:1; /**< 0 for app-ward, 1 for exit-ward. */ +} testing_cell_stats_entry_t; + /** * A circuit is a path over the onion routing * network. Applications can connect to one end of the circuit, and can @@ -2804,6 +2893,16 @@ typedef struct circuit_t { * allowing n_streams to add any more cells. (OR circuit only.) */ unsigned int streams_blocked_on_p_chan : 1; + /** True iff we have queued a delete backwards on this circuit, but not put + * it on the output buffer. */ + unsigned int p_delete_pending : 1; + /** True iff we have queued a delete forwards on this circuit, but not put + * it on the output buffer. */ + unsigned int n_delete_pending : 1; + + /** True iff this circuit has received a DESTROY cell in either direction */ + unsigned int received_destroy : 1; + uint8_t state; /**< Current status of this circuit. */ uint8_t purpose; /**< Why are we creating this circuit? */ @@ -2858,7 +2957,8 @@ typedef struct circuit_t { /** Unique ID for measuring tunneled network status requests. */ uint64_t dirreq_id; - struct circuit_t *next; /**< Next circuit in linked list of all circuits. */ + /** Next circuit in linked list of all circuits (global_circuitlist). */ + TOR_LIST_ENTRY(circuit_t) head; /** Next circuit in the doubly-linked ring of circuits waiting to add * cells to n_conn. NULL if we have no cells pending, or if we're not @@ -2868,6 +2968,11 @@ typedef struct circuit_t { * cells to n_conn. NULL if we have no cells pending, or if we're not * linked to an OR connection. */ struct circuit_t *prev_active_on_n_chan; + + /** Various statistics about cells being added to or removed from this + * circuit's queues; used only if CELL_STATS events are enabled and + * cleared after being sent to control port. */ + smartlist_t *testing_cell_stats; } circuit_t; /** Largest number of relay_early cells that we can send on a given @@ -2938,6 +3043,17 @@ typedef struct origin_circuit_t { /** Linked list of AP streams (or EXIT streams if hidden service) * associated with this circuit. */ edge_connection_t *p_streams; + + /** Bytes read from any attached stream since last call to + * control_event_circ_bandwidth_used(). Only used if we're configured + * to emit CIRC_BW events. */ + uint32_t n_read_circ_bw; + + /** Bytes written to any attached stream since last call to + * control_event_circ_bandwidth_used(). Only used if we're configured + * to emit CIRC_BW events. */ + uint32_t n_written_circ_bw; + /** Build state for this circuit. It includes the intended path * length, the chosen exit router, rendezvous information, etc. */ @@ -3184,6 +3300,12 @@ typedef struct or_circuit_t { * exit-ward queues of this circuit; reset every time when writing * buffer stats to disk. */ uint64_t total_cell_waiting_time; + + /** Maximum cell queue size for a middle relay; this is stored per circuit + * so append_cell_to_circuit_queue() can adjust it if it changes. If set + * to zero, it is initialized to the default value. + */ + uint32_t max_middle_cells; } or_circuit_t; /** Convert a circuit subtype to a circuit_t. */ @@ -3342,9 +3464,9 @@ typedef struct { /** What should the tor process actually do? */ enum { CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD, - CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS + CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG } command; - const char *command_arg; /**< Argument for command-line option. */ + char *command_arg; /**< Argument for command-line option. */ config_line_t *Logs; /**< New-style list of configuration lines * for logs */ @@ -3425,6 +3547,8 @@ typedef struct { 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; /** Ports to listen on for SOCKS connections. */ config_line_t *SocksPort_lines; /** Ports to listen on for transparent pf/netfilter connections. */ @@ -3460,6 +3584,7 @@ typedef struct { unsigned int ControlPort_set : 1; unsigned int DirPort_set : 1; unsigned int DNSPort_set : 1; + unsigned int ExtORPort_set : 1; /**@}*/ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ @@ -3499,6 +3624,9 @@ typedef struct { /** List of TCP/IP addresses that transports should listen at. */ config_line_t *ServerTransportListenAddr; + /** List of options that must be passed to pluggable transports. */ + config_line_t *ServerTransportOptions; + int BridgeRelay; /**< Boolean: are we acting as a bridge relay? We make * this explicit so we can change how we behave in the * future. */ @@ -3736,7 +3864,10 @@ typedef struct { int CookieAuthentication; /**< Boolean: do we enable cookie-based auth for * the control system? */ - char *CookieAuthFile; /**< Location of a cookie authentication file. */ + char *CookieAuthFile; /**< Filesystem location of a ControlPort + * authentication cookie. */ + char *ExtORPortCookieAuthFile; /**< Filesystem location of Extended + * ORPort authentication cookie. */ int CookieAuthFileGroupReadable; /**< Boolean: Is the CookieAuthFile g+r? */ int LeaveStreamsUnattached; /**< Boolean: Does Tor attach new streams to * circuits itself (0), or does it expect a controller @@ -3758,6 +3889,7 @@ typedef struct { SAFELOG_SCRUB_ALL, SAFELOG_SCRUB_RELAY, SAFELOG_SCRUB_NONE } SafeLogging_; + int Sandbox; /**< Boolean: should sandboxing be enabled? */ int SafeSocks; /**< Boolean: should we outright refuse application * connections that use socks4 or socks5-with-local-dns? */ #define LOG_PROTOCOL_WARN (get_options()->ProtocolWarnings ? \ @@ -3929,6 +4061,10 @@ typedef struct { * signatures. Only altered on testing networks.*/ int TestingV3AuthInitialDistDelay; + /** Offset in seconds added to the starting time for consensus + voting. Only altered on testing networks. */ + int TestingV3AuthVotingStartOffset; + /** If an authority has been around for less than this amount of time, it * does not believe its reachability information is accurate. Only * altered on testing networks. */ @@ -3939,6 +4075,51 @@ typedef struct { * networks. */ int TestingEstimatedDescriptorPropagationTime; + /** Schedule for when servers should download things in general. Only + * altered on testing networks. */ + smartlist_t *TestingServerDownloadSchedule; + + /** Schedule for when clients should download things in general. Only + * altered on testing networks. */ + smartlist_t *TestingClientDownloadSchedule; + + /** Schedule for when servers should download consensuses. Only altered + * on testing networks. */ + smartlist_t *TestingServerConsensusDownloadSchedule; + + /** Schedule for when clients should download consensuses. Only altered + * on testing networks. */ + smartlist_t *TestingClientConsensusDownloadSchedule; + + /** Schedule for when clients should download bridge descriptors. Only + * altered on testing networks. */ + smartlist_t *TestingBridgeDownloadSchedule; + + /** When directory clients have only a few descriptors to request, they + * batch them until they have more, or until this amount of time has + * passed. Only altered on testing networks. */ + int TestingClientMaxIntervalWithoutRequest; + + /** How long do we let a directory connection stall before expiring + * it? Only altered on testing networks. */ + int TestingDirConnectionMaxStall; + + /** How many times will we try to fetch a consensus before we give + * up? Only altered on testing networks. */ + int TestingConsensusMaxDownloadTries; + + /** How many times will we try to download a router's descriptor before + * giving up? Only altered on testing networks. */ + int TestingDescriptorMaxDownloadTries; + + /** How many times will we try to download a microdescriptor before + * giving up? Only altered on testing networks. */ + int TestingMicrodescMaxDownloadTries; + + /** How many times will we try to fetch a certificate before giving + * up? Only altered on testing networks. */ + int TestingCertMaxDownloadTries; + /** If true, we take part in a testing network. Change the defaults of a * couple of other configuration options and allow to change the values * of certain configuration options. */ @@ -3950,6 +4131,19 @@ typedef struct { /** Minimum value for the Fast flag threshold on testing networks. */ uint64_t TestingMinFastFlagThreshold; + /** Relays in a testing network which should be voted Guard + * regardless of uptime and bandwidth. */ + routerset_t *TestingDirAuthVoteGuard; + + /** Enable CONN_BW events. Only altered on testing networks. */ + int TestingEnableConnBwEvent; + + /** Enable CELL_STATS events. Only altered on testing networks. */ + int TestingEnableCellStatsEvent; + + /** Enable TB_EMPTY events. Only altered on testing networks. */ + int TestingEnableTbEmptyEvent; + /** If true, and we have GeoIP data, and we're a bridge, keep a per-country * count of how many client addresses have contacted us so that we can help * the bridge authority guess which countries have blocked access to us. */ @@ -4362,30 +4556,7 @@ typedef struct { int after_firsthop_idx; } network_liveness_t; -/** Structure for circuit build times history */ -typedef struct { - /** The circular array of recorded build times in milliseconds */ - build_time_t circuit_build_times[CBT_NCIRCUITS_TO_OBSERVE]; - /** Current index in the circuit_build_times circular array */ - int build_times_idx; - /** Total number of build times accumulated. Max CBT_NCIRCUITS_TO_OBSERVE */ - int total_build_times; - /** Information about the state of our local network connection */ - network_liveness_t liveness; - /** Last time we built a circuit. Used to decide to build new test circs */ - time_t last_circ_at; - /** "Minimum" value of our pareto distribution (actually mode) */ - build_time_t Xm; - /** alpha exponent for pareto dist. */ - double alpha; - /** Have we computed a timeout? */ - int have_computed_timeout; - /** The exact value for that timeout in milliseconds. Stored as a double - * to maintain precision from calculations to and from quantile value. */ - double timeout_ms; - /** How long we wait before actually closing the circuit. */ - double close_ms; -} circuit_build_times_t; +typedef struct circuit_build_times_s circuit_build_times_t; /********************************* config.c ***************************/ diff --git a/src/or/relay.c b/src/or/relay.c index 63119cbf0..b5e4ff7cc 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -15,6 +15,7 @@ #include "addressmap.h" #include "buffers.h" #include "channel.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -58,6 +59,9 @@ static void adjust_exit_policy_from_exitpolicy_failure(origin_circuit_t *circ, entry_connection_t *conn, node_t *node, const tor_addr_t *addr); +#if 0 +static int get_max_middle_cells(void); +#endif /** Stop reading on edge connections when we have this many cells * waiting on the appropriate queue. */ @@ -966,7 +970,7 @@ remap_event_helper(entry_connection_t *conn, const tor_addr_t *new_addr) * <b>addr_out</b> to the address we're connected to, and <b>ttl_out</b> to * the ttl of that address, in seconds, and return 0. On failure, return * -1. */ -int +STATIC int connected_cell_parse(const relay_header_t *rh, const cell_t *cell, tor_addr_t *addr_out, int *ttl_out) { @@ -1104,8 +1108,9 @@ connection_edge_process_relay_cell_not_open( break; case DIR_PURPOSE_FETCH_SERVERDESC: case DIR_PURPOSE_FETCH_MICRODESC: - control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, - count_loading_descriptors_progress()); + if (TO_DIR_CONN(dirconn)->router_purpose == ROUTER_PURPOSE_GENERAL) + control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, + count_loading_descriptors_progress()); break; } } @@ -1495,7 +1500,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (layer_hint) { if (layer_hint->package_window + CIRCWINDOW_INCREMENT > CIRCWINDOW_START_MAX) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + static struct ratelim_t exit_warn_ratelim = RATELIM_INIT(600); + log_fn_ratelim(&exit_warn_ratelim, LOG_WARN, LD_PROTOCOL, "Unexpected sendme cell from exit relay. " "Closing circ."); return -END_CIRC_REASON_TORPROTOCOL; @@ -1507,7 +1513,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } else { if (circ->package_window + CIRCWINDOW_INCREMENT > CIRCWINDOW_START_MAX) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + static struct ratelim_t client_warn_ratelim = RATELIM_INIT(600); + log_fn_ratelim(&client_warn_ratelim, LOG_WARN, LD_PROTOCOL, "Unexpected sendme cell from client. " "Closing circ (window %d).", circ->package_window); @@ -2044,6 +2051,10 @@ static mp_pool_t *cell_pool = NULL; * statistics. */ static mp_pool_t *it_pool = NULL; +/** Memory pool to allocate insertion_command_elem_t objects used for cell + * statistics if CELL_STATS events are enabled. */ +static mp_pool_t *ic_pool = NULL; + /** Allocate structures to hold cells. */ void init_cell_pool(void) @@ -2052,8 +2063,8 @@ init_cell_pool(void) cell_pool = mp_pool_new(sizeof(packed_cell_t), 128*1024); } -/** Free all storage used to hold cells (and insertion times if we measure - * cell statistics). */ +/** Free all storage used to hold cells (and insertion times/commands if we + * measure cell statistics and/or if CELL_STATS events are enabled). */ void free_cell_pool(void) { @@ -2066,6 +2077,10 @@ free_cell_pool(void) mp_pool_destroy(it_pool); it_pool = NULL; } + if (ic_pool) { + mp_pool_destroy(ic_pool); + ic_pool = NULL; + } } /** Free excess storage in cell pool. */ @@ -2085,7 +2100,7 @@ packed_cell_free_unchecked(packed_cell_t *cell) } /** Allocate and return a new packed_cell_t. */ -static INLINE packed_cell_t * +STATIC packed_cell_t * packed_cell_new(void) { ++total_cells_allocated; @@ -2096,6 +2111,8 @@ packed_cell_new(void) void packed_cell_free(packed_cell_t *cell) { + if (!cell) + return; packed_cell_free_unchecked(cell); } @@ -2107,7 +2124,7 @@ dump_cell_pool_usage(int severity) circuit_t *c; int n_circs = 0; int n_cells = 0; - for (c = circuit_get_global_list_(); c; c = c->next) { + TOR_LIST_FOREACH(c, circuit_get_global_list(), head) { n_cells += c->n_chan_cells.n; if (!CIRCUIT_IS_ORIGIN(c)) n_cells += TO_OR_CIRCUIT(c)->p_chan_cells.n; @@ -2125,7 +2142,6 @@ packed_cell_copy(const cell_t *cell, int wide_circ_ids) { packed_cell_t *c = packed_cell_new(); cell_pack(c, cell, wide_circ_ids); - c->next = NULL; return c; } @@ -2133,21 +2149,69 @@ packed_cell_copy(const cell_t *cell, int wide_circ_ids) void cell_queue_append(cell_queue_t *queue, packed_cell_t *cell) { - if (queue->tail) { - tor_assert(!queue->tail->next); - queue->tail->next = cell; + TOR_SIMPLEQ_INSERT_TAIL(&queue->head, cell, next); + ++queue->n; +} + +/** Append command of type <b>command</b> in direction to <b>queue</b> for + * CELL_STATS event. */ +static void +cell_command_queue_append(cell_queue_t *queue, uint8_t command) +{ + insertion_command_queue_t *ic_queue = queue->insertion_commands; + if (!ic_pool) + ic_pool = mp_pool_new(sizeof(insertion_command_elem_t), 1024); + if (!ic_queue) { + ic_queue = tor_malloc_zero(sizeof(insertion_command_queue_t)); + queue->insertion_commands = ic_queue; + } + if (ic_queue->last && ic_queue->last->command == command) { + ic_queue->last->counter++; } else { - queue->head = cell; + insertion_command_elem_t *elem = mp_pool_get(ic_pool); + elem->next = NULL; + elem->command = command; + elem->counter = 1; + if (ic_queue->last) { + ic_queue->last->next = elem; + ic_queue->last = elem; + } else { + ic_queue->first = ic_queue->last = elem; + } } - queue->tail = cell; - cell->next = NULL; - ++queue->n; } -/** Append a newly allocated copy of <b>cell</b> to the end of <b>queue</b> */ +/** Retrieve oldest command from <b>queue</b> and write it to + * <b>command</b> for CELL_STATS event. Return 0 for success, -1 + * otherwise. */ +static int +cell_command_queue_pop(uint8_t *command, cell_queue_t *queue) +{ + int res = -1; + insertion_command_queue_t *ic_queue = queue->insertion_commands; + if (ic_queue && ic_queue->first) { + insertion_command_elem_t *ic_elem = ic_queue->first; + ic_elem->counter--; + if (ic_elem->counter < 1) { + ic_queue->first = ic_elem->next; + if (ic_elem == ic_queue->last) + ic_queue->last = NULL; + mp_pool_release(ic_elem); + } + *command = ic_elem->command; + res = 0; + } + return res; +} + +/** Append a newly allocated copy of <b>cell</b> to the end of the + * <b>exitward</b> (or app-ward) <b>queue</b> of <b>circ</b>. If + * <b>use_stats</b> is true, record statistics about the cell. + */ void -cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell, - int wide_circ_ids) +cell_queue_append_packed_copy(circuit_t *circ, cell_queue_t *queue, + int exitward, const cell_t *cell, + int wide_circ_ids, int use_stats) { struct timeval now; packed_cell_t *copy = packed_cell_copy(cell, wide_circ_ids); @@ -2156,7 +2220,8 @@ cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell, /* Remember the time when this cell was put in the queue. */ /*XXXX This may be obsoleted by inserted_time */ - if (get_options()->CellStatistics) { + if ((get_options()->CellStatistics || + get_options()->TestingEnableCellStatsEvent) && use_stats) { uint32_t added; insertion_time_queue_t *it_queue = queue->insertion_times; if (!it_pool) @@ -2184,21 +2249,39 @@ cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell, } } } + /* Remember that we added a cell to the queue, and remember the cell + * command. */ + if (get_options()->TestingEnableCellStatsEvent && circ) { + testing_cell_stats_entry_t *ent = + tor_malloc_zero(sizeof(testing_cell_stats_entry_t)); + ent->command = cell->command; + ent->exitward = exitward; + if (!circ->testing_cell_stats) + circ->testing_cell_stats = smartlist_new(); + smartlist_add(circ->testing_cell_stats, ent); + cell_command_queue_append(queue, cell->command); + } cell_queue_append(queue, copy); } +/** Initialize <b>queue</b> as an empty cell queue. */ +void +cell_queue_init(cell_queue_t *queue) +{ + memset(queue, 0, sizeof(cell_queue_t)); + TOR_SIMPLEQ_INIT(&queue->head); +} + /** Remove and free every cell in <b>queue</b>. */ void cell_queue_clear(cell_queue_t *queue) { - packed_cell_t *cell, *next; - cell = queue->head; - while (cell) { - next = cell->next; + packed_cell_t *cell; + while ((cell = TOR_SIMPLEQ_FIRST(&queue->head))) { + TOR_SIMPLEQ_REMOVE_HEAD(&queue->head, next); packed_cell_free_unchecked(cell); - cell = next; } - queue->head = queue->tail = NULL; + TOR_SIMPLEQ_INIT(&queue->head); queue->n = 0; if (queue->insertion_times) { while (queue->insertion_times->first) { @@ -2212,17 +2295,13 @@ cell_queue_clear(cell_queue_t *queue) /** Extract and return the cell at the head of <b>queue</b>; return NULL if * <b>queue</b> is empty. */ -static INLINE packed_cell_t * +STATIC packed_cell_t * cell_queue_pop(cell_queue_t *queue) { - packed_cell_t *cell = queue->head; + packed_cell_t *cell = TOR_SIMPLEQ_FIRST(&queue->head); if (!cell) return NULL; - queue->head = cell->next; - if (cell == queue->tail) { - tor_assert(!queue->head); - queue->tail = NULL; - } + TOR_SIMPLEQ_REMOVE_HEAD(&queue->head, next); --queue->n; return cell; } @@ -2373,7 +2452,7 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) { circuitmux_t *cmux = NULL; int n_flushed = 0; - cell_queue_t *queue; + cell_queue_t *queue, *destroy_queue=NULL; circuit_t *circ; or_circuit_t *or_circ; int streams_blocked; @@ -2386,7 +2465,18 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) /* Main loop: pick a circuit, send a cell, update the cmux */ while (n_flushed < max) { - circ = circuitmux_get_first_active_circuit(cmux); + circ = circuitmux_get_first_active_circuit(cmux, &destroy_queue); + if (destroy_queue) { + /* this code is duplicated from some of the logic below. Ugly! XXXX */ + tor_assert(destroy_queue->n > 0); + cell = cell_queue_pop(destroy_queue); + channel_write_packed_cell(chan, cell); + /* Update the cmux destroy counter */ + circuitmux_notify_xmit_destroy(cmux); + cell = NULL; + ++n_flushed; + continue; + } /* If it returns NULL, no cells left to send */ if (!circ) break; assert_cmux_ok_paranoid(chan); @@ -2412,7 +2502,8 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) cell = cell_queue_pop(queue); /* Calculate the exact time that this cell has spent in the queue. */ - if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) { + if (get_options()->CellStatistics || + get_options()->TestingEnableCellStatsEvent) { struct timeval tvnow; uint32_t flushed; uint32_t cell_waiting_time; @@ -2426,7 +2517,6 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) "recently enabled."); } else { insertion_time_elem_t *elem = it_queue->first; - or_circ = TO_OR_CIRCUIT(circ); cell_waiting_time = (uint32_t)((flushed * 10L + SECONDS_IN_A_DAY * 1000L - elem->insertion_time * 10L) % @@ -2439,8 +2529,30 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) it_queue->last = NULL; mp_pool_release(elem); } - or_circ->total_cell_waiting_time += cell_waiting_time; - or_circ->processed_cells++; + if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) { + or_circ = TO_OR_CIRCUIT(circ); + or_circ->total_cell_waiting_time += cell_waiting_time; + or_circ->processed_cells++; + } + if (get_options()->TestingEnableCellStatsEvent) { + uint8_t command; + if (cell_command_queue_pop(&command, queue) < 0) { + log_info(LD_GENERAL, "Cannot determine command of cell. " + "Looks like the CELL_STATS event was " + "recently enabled."); + } else { + testing_cell_stats_entry_t *ent = + tor_malloc_zero(sizeof(testing_cell_stats_entry_t)); + ent->command = command; + ent->waiting_time = (unsigned int)cell_waiting_time / 10; + ent->removed = 1; + if (circ->n_chan == chan) + ent->exitward = 1; + if (!circ->testing_cell_stats) + circ->testing_cell_stats = smartlist_new(); + smartlist_add(circ->testing_cell_stats, ent); + } + } } } @@ -2487,6 +2599,20 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) return n_flushed; } +#if 0 +/** Indicate the current preferred cap for middle circuits; zero disables + * the cap. Right now it's just a constant, ORCIRC_MAX_MIDDLE_CELLS, but + * the logic in append_cell_to_circuit_queue() is written to be correct + * if we want to base it on a consensus param or something that might change + * in the future. + */ +static int +get_max_middle_cells(void) +{ + return ORCIRC_MAX_MIDDLE_CELLS; +} +#endif + /** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b> * transmitting in <b>direction</b>. */ void @@ -2497,11 +2623,16 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, or_circuit_t *orcirc = NULL; cell_queue_t *queue; int streams_blocked; +#if 0 + uint32_t tgt_max_middle_cells, p_len, n_len, tmp, hard_max_middle_cells; +#endif + int exitward; if (circ->marked_for_close) return; - if (direction == CELL_DIRECTION_OUT) { + exitward = (direction == CELL_DIRECTION_OUT); + if (exitward) { queue = &circ->n_chan_cells; streams_blocked = circ->streams_blocked_on_n_chan; } else { @@ -2518,28 +2649,82 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, if ((circ->n_chan != NULL) && CIRCUIT_IS_ORCIRC(circ)) { orcirc = TO_OR_CIRCUIT(circ); if (orcirc->p_chan) { - if (queue->n + 1 >= ORCIRC_MAX_MIDDLE_CELLS) { - /* Queueing this cell would put queue over the cap */ - log_warn(LD_CIRC, - "Got a cell exceeding the cap of %u in the %s direction " - "on middle circ ID %u on chan ID " U64_FORMAT - "; killing the circuit.", - ORCIRC_MAX_MIDDLE_CELLS, - (direction == CELL_DIRECTION_OUT) ? "n" : "p", - (direction == CELL_DIRECTION_OUT) ? - circ->n_circ_id : orcirc->p_circ_id, - U64_PRINTF_ARG( + /* We are a middle circuit if we have both n_chan and p_chan */ + /* We'll need to know the current preferred maximum */ + tgt_max_middle_cells = get_max_middle_cells(); + if (tgt_max_middle_cells > 0) { + /* Do we need to initialize middle_max_cells? */ + if (orcirc->max_middle_cells == 0) { + orcirc->max_middle_cells = tgt_max_middle_cells; + } else { + if (tgt_max_middle_cells > orcirc->max_middle_cells) { + /* If we want to increase the cap, we can do so right away */ + orcirc->max_middle_cells = tgt_max_middle_cells; + } else if (tgt_max_middle_cells < orcirc->max_middle_cells) { + /* + * If we're shrinking the cap, we can't shrink past either queue; + * compare tgt_max_middle_cells rather than tgt_max_middle_cells * + * ORCIRC_MAX_MIDDLE_KILL_THRESH so the queues don't shrink enough + * to generate spurious warnings, either. + */ + n_len = circ->n_chan_cells.n; + p_len = orcirc->p_chan_cells.n; + tmp = tgt_max_middle_cells; + if (tmp < n_len) tmp = n_len; + if (tmp < p_len) tmp = p_len; + orcirc->max_middle_cells = tmp; + } + /* else no change */ + } + } else { + /* tgt_max_middle_cells == 0 indicates we should disable the cap */ + orcirc->max_middle_cells = 0; + } + + /* Now we know orcirc->max_middle_cells is set correctly */ + if (orcirc->max_middle_cells > 0) { + hard_max_middle_cells = + (uint32_t)(((double)orcirc->max_middle_cells) * + ORCIRC_MAX_MIDDLE_KILL_THRESH); + + if ((unsigned)queue->n + 1 >= hard_max_middle_cells) { + /* Queueing this cell would put queue over the kill theshold */ + log_warn(LD_CIRC, + "Got a cell exceeding the hard cap of %u in the " + "%s direction on middle circ ID %u on chan ID " + U64_FORMAT "; killing the circuit.", + hard_max_middle_cells, + (direction == CELL_DIRECTION_OUT) ? "n" : "p", + (direction == CELL_DIRECTION_OUT) ? + circ->n_circ_id : orcirc->p_circ_id, + U64_PRINTF_ARG( + (direction == CELL_DIRECTION_OUT) ? + circ->n_chan->global_identifier : + orcirc->p_chan->global_identifier)); + circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); + return; + } else if ((unsigned)queue->n + 1 == orcirc->max_middle_cells) { + /* Only use ==, not >= for this test so we don't spam the log */ + log_warn(LD_CIRC, + "While trying to queue a cell, reached the soft cap of %u " + "in the %s direction on middle circ ID %u " + "on chan ID " U64_FORMAT ".", + orcirc->max_middle_cells, + (direction == CELL_DIRECTION_OUT) ? "n" : "p", (direction == CELL_DIRECTION_OUT) ? - circ->n_chan->global_identifier : - orcirc->p_chan->global_identifier)); - circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); - return; + circ->n_circ_id : orcirc->p_circ_id, + U64_PRINTF_ARG( + (direction == CELL_DIRECTION_OUT) ? + circ->n_chan->global_identifier : + orcirc->p_chan->global_identifier)); + } } } } #endif - cell_queue_append_packed_copy(queue, cell, chan->wide_circ_ids); + cell_queue_append_packed_copy(circ, queue, exitward, cell, + chan->wide_circ_ids, 1); if (PREDICT_UNLIKELY(cell_queues_check_size())) { /* We ran the OOM handler */ diff --git a/src/or/relay.h b/src/or/relay.h index 1fef10a7d..20eecfb40 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -51,10 +51,12 @@ size_t packed_cell_mem_cost(void); /* For channeltls.c */ void packed_cell_free(packed_cell_t *cell); +void cell_queue_init(cell_queue_t *queue); void cell_queue_clear(cell_queue_t *queue); void cell_queue_append(cell_queue_t *queue, packed_cell_t *cell); -void cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell, - int wide_circ_ids); +void cell_queue_append_packed_copy(circuit_t *circ, cell_queue_t *queue, + int exitward, const cell_t *cell, + int wide_circ_ids, int use_stats); void append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, cell_t *cell, cell_direction_t direction, @@ -75,11 +77,14 @@ void circuit_clear_cell_queue(circuit_t *circ, channel_t *chan); void stream_choice_seed_weak_rng(void); -#ifdef RELAY_PRIVATE int relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, crypt_path_t **layer_hint, char *recognized); -int connected_cell_parse(const relay_header_t *rh, const cell_t *cell, + +#ifdef RELAY_PRIVATE +STATIC int connected_cell_parse(const relay_header_t *rh, const cell_t *cell, tor_addr_t *addr_out, int *ttl_out); +STATIC packed_cell_t *packed_cell_new(void); +STATIC packed_cell_t *cell_queue_pop(cell_queue_t *queue); #endif #endif diff --git a/src/or/rendclient.c b/src/or/rendclient.c index bb4bd9bfd..f00303f18 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -8,6 +8,7 @@ **/ #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -376,7 +377,7 @@ rend_client_close_other_intros(const char *onion_address) { circuit_t *c; /* abort parallel intro circs, if any */ - for (c = circuit_get_global_list_(); c; c = c->next) { + TOR_LIST_FOREACH(c, circuit_get_global_list(), head) { if ((c->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || c->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) && !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 8a4a11e47..cb2f96299 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -10,6 +10,7 @@ #define RENDSERVICE_PRIVATE #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -543,7 +544,7 @@ rend_config_services(const or_options_t *options, int validate_only) /* XXXX it would be nicer if we had a nicer abstraction to use here, * so we could just iterate over the list of services to close, but * once again, this isn't critical-path code. */ - for (circ = circuit_get_global_list_(); circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN && (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -1208,7 +1209,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, /* check for replay of PK-encrypted portion. */ replay = replaycache_add_test_and_elapsed( intro_point->accepted_intro_rsa_parts, - parsed_req->ciphertext, (int)parsed_req->ciphertext_len, + parsed_req->ciphertext, parsed_req->ciphertext_len, &elapsed); if (replay) { @@ -2376,7 +2377,7 @@ count_established_intro_points(const char *query) { int num_ipos = 0; circuit_t *circ; - for (circ = circuit_get_global_list_(); circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN && (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || diff --git a/src/or/rephist.c b/src/or/rephist.c index 131e531b1..13404badf 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -2313,7 +2313,7 @@ rep_hist_format_exit_stats(time_t now) time_t rep_hist_exit_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_exit_stats_interval) return 0; /* Not initialized. */ @@ -2329,19 +2329,12 @@ rep_hist_exit_stats_write(time_t now) rep_hist_reset_exit_stats(now); /* Try to write to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "exit-stats", str, "exit port statistics"); } - filename = get_datadir_fname2("stats", "exit-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write exit port statistics to disk!"); done: tor_free(str); - tor_free(statsdir); - tor_free(filename); return start_of_exit_stats_interval + WRITE_STATS_INTERVAL; } @@ -2598,7 +2591,7 @@ time_t rep_hist_buffer_stats_write(time_t now) { circuit_t *circ; - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_buffer_stats_interval) return 0; /* Not initialized. */ @@ -2606,7 +2599,7 @@ rep_hist_buffer_stats_write(time_t now) goto done; /* Not ready to write */ /* Add open circuits to the history. */ - for (circ = circuit_get_global_list_(); circ; circ = circ->next) { + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) { rep_hist_buffer_stats_add_circ(circ, now); } @@ -2617,19 +2610,12 @@ rep_hist_buffer_stats_write(time_t now) rep_hist_reset_buffer_stats(now); /* Try to write to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "buffer-stats", str, "buffer statistics"); } - filename = get_datadir_fname2("stats", "buffer-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write buffer stats to disk!"); done: tor_free(str); - tor_free(filename); - tor_free(statsdir); return start_of_buffer_stats_interval + WRITE_STATS_INTERVAL; } @@ -2741,7 +2727,7 @@ rep_hist_format_desc_stats(time_t now) time_t rep_hist_desc_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *filename = NULL, *str = NULL; if (!start_of_served_descs_stats_interval) return 0; /* We're not collecting stats. */ @@ -2751,10 +2737,8 @@ rep_hist_desc_stats_write(time_t now) str = rep_hist_format_desc_stats(now); tor_assert(str != NULL); - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (check_or_create_data_subdir("stats") < 0) { + goto done; } filename = get_datadir_fname2("stats", "served-desc-stats"); if (append_bytes_to_file(filename, str, strlen(str), 0) < 0) @@ -2763,7 +2747,6 @@ rep_hist_desc_stats_write(time_t now) rep_hist_reset_desc_stats(now); done: - tor_free(statsdir); tor_free(filename); tor_free(str); return start_of_served_descs_stats_interval + WRITE_STATS_INTERVAL; @@ -2981,7 +2964,7 @@ rep_hist_format_conn_stats(time_t now) time_t rep_hist_conn_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_conn_stats_interval) return 0; /* Not initialized. */ @@ -2995,19 +2978,12 @@ rep_hist_conn_stats_write(time_t now) rep_hist_reset_conn_stats(now); /* Try to write to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "conn-stats", str, "connection statistics"); } - filename = get_datadir_fname2("stats", "conn-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write conn stats to disk!"); done: tor_free(str); - tor_free(filename); - tor_free(statsdir); return start_of_conn_stats_interval + WRITE_STATS_INTERVAL; } diff --git a/src/or/replaycache.c b/src/or/replaycache.c index 59b98489b..90f87c12d 100644 --- a/src/or/replaycache.c +++ b/src/or/replaycache.c @@ -63,9 +63,9 @@ replaycache_new(time_t horizon, time_t interval) /** See documentation for replaycache_add_and_test() */ -int +STATIC int replaycache_add_and_test_internal( - time_t present, replaycache_t *r, const void *data, int len, + time_t present, replaycache_t *r, const void *data, size_t len, time_t *elapsed) { int rv = 0; @@ -73,7 +73,7 @@ replaycache_add_and_test_internal( time_t *access_time; /* sanity check */ - if (present <= 0 || !r || !data || len <= 0) { + if (present <= 0 || !r || !data || len == 0) { log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid" " parameters; please fix this."); goto done; @@ -127,14 +127,13 @@ replaycache_add_and_test_internal( /** See documentation for replaycache_scrub_if_needed() */ -void +STATIC void replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) { digestmap_iter_t *itr = NULL; const char *digest; void *valp; time_t *access_time; - char scrub_this; /* sanity check */ if (!r || !(r->digests_seen)) { @@ -152,20 +151,10 @@ replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) /* okay, scrub time */ itr = digestmap_iter_init(r->digests_seen); while (!digestmap_iter_done(itr)) { - scrub_this = 0; digestmap_iter_get(itr, &digest, &valp); access_time = (time_t *)valp; - if (access_time) { - /* aged out yet? */ - if (*access_time < present - r->horizon) scrub_this = 1; - } else { - /* Buh? Get rid of it, anyway */ - log_info(LD_BUG, "replaycache_scrub_if_needed_internal() saw a NULL" - " entry in the digestmap."); - scrub_this = 1; - } - - if (scrub_this) { + /* aged out yet? */ + if (*access_time < present - r->horizon) { /* Advance the iterator and remove this one */ itr = digestmap_iter_next_rmv(r->digests_seen, itr); /* Free the value removed */ @@ -187,7 +176,7 @@ replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) */ int -replaycache_add_and_test(replaycache_t *r, const void *data, int len) +replaycache_add_and_test(replaycache_t *r, const void *data, size_t len) { return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL); } @@ -198,7 +187,7 @@ replaycache_add_and_test(replaycache_t *r, const void *data, int len) int replaycache_add_test_and_elapsed( - replaycache_t *r, const void *data, int len, time_t *elapsed) + replaycache_t *r, const void *data, size_t len, time_t *elapsed) { return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed); } diff --git a/src/or/replaycache.h b/src/or/replaycache.h index de20cab62..cd713fe89 100644 --- a/src/or/replaycache.h +++ b/src/or/replaycache.h @@ -45,10 +45,10 @@ replaycache_t * replaycache_new(time_t horizon, time_t interval); * testing. For everything else, use the wrappers below instead. */ -int replaycache_add_and_test_internal( - time_t present, replaycache_t *r, const void *data, int len, +STATIC int replaycache_add_and_test_internal( + time_t present, replaycache_t *r, const void *data, size_t len, time_t *elapsed); -void replaycache_scrub_if_needed_internal( +STATIC void replaycache_scrub_if_needed_internal( time_t present, replaycache_t *r); #endif /* REPLAYCACHE_PRIVATE */ @@ -57,9 +57,9 @@ void replaycache_scrub_if_needed_internal( * replaycache_t methods */ -int replaycache_add_and_test(replaycache_t *r, const void *data, int len); +int replaycache_add_and_test(replaycache_t *r, const void *data, size_t len); int replaycache_add_test_and_elapsed( - replaycache_t *r, const void *data, int len, time_t *elapsed); + replaycache_t *r, const void *data, size_t len, time_t *elapsed); void replaycache_scrub_if_needed(replaycache_t *r); #endif diff --git a/src/or/router.c b/src/or/router.c index eabd9c3f5..959e5e34c 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -232,7 +232,8 @@ get_server_identity_key(void) return server_identitykey; } -/** Return true iff the server identity key has been set. */ +/** Return true iff we are a server and the server identity key + * has been set. */ int server_identity_key_is_set(void) { @@ -2249,7 +2250,7 @@ router_guess_address_from_dir_headers(uint32_t *guess) * string describing the version of Tor and the operating system we're * currently running on. */ -void +STATIC void get_platform_str(char *platform, size_t len) { tor_snprintf(platform, len, "Tor %s on %s", diff --git a/src/or/router.h b/src/or/router.h index 60095d087..1079ec78c 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -12,6 +12,8 @@ #ifndef TOR_ROUTER_H #define TOR_ROUTER_H +#include "testsupport.h" + crypto_pk_t *get_onion_key(void); time_t get_onion_key_set_at(void); void set_server_identity_key(crypto_pk_t *k); @@ -146,7 +148,7 @@ smartlist_t *router_get_all_orports(const routerinfo_t *ri); #ifdef ROUTER_PRIVATE /* Used only by router.c and test.c */ -void get_platform_str(char *platform, size_t len); +STATIC void get_platform_str(char *platform, size_t len); #endif #endif diff --git a/src/or/routerlist.c b/src/or/routerlist.c index c28de24b6..c0c4d9a4c 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -37,7 +37,7 @@ #include "routerlist.h" #include "routerparse.h" #include "routerset.h" - +#include "../common/sandbox.h" // #define DEBUG_ROUTERLIST /****************************************************************************/ @@ -449,7 +449,7 @@ trusted_dirs_flush_certs_to_disk(void) } DIGESTMAP_FOREACH_END; filename = get_datadir_fname("cached-certs"); - if (write_chunks_to_file(filename, chunks, 0)) { + if (write_chunks_to_file(filename, chunks, 0, 0)) { log_warn(LD_FS, "Error writing certificates to disk."); } tor_free(filename); @@ -650,9 +650,6 @@ authority_cert_dl_looks_uncertain(const char *id_digest) return n_failures >= N_AUTH_CERT_DL_FAILURES_TO_BUG_USER; } -/** How many times will we try to fetch a certificate before giving up? */ -#define MAX_CERT_DL_FAILURES 8 - /** Try to download any v3 authority certificates that we may be missing. If * <b>status</b> is provided, try to get all the ones that were used to sign * <b>status</b>. Additionally, try to have a non-expired certificate for @@ -724,7 +721,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) } SMARTLIST_FOREACH_END(cert); if (!found && download_status_is_ready(&(cl->dl_status_by_id), now, - MAX_CERT_DL_FAILURES) && + get_options()->TestingCertMaxDownloadTries) && !digestmap_get(pending_id, ds->v3_identity_digest)) { log_info(LD_DIR, "No current certificate known for authority %s " @@ -786,7 +783,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) } if (download_status_is_ready_by_sk_in_cl( cl, sig->signing_key_digest, - now, MAX_CERT_DL_FAILURES) && + now, get_options()->TestingCertMaxDownloadTries) && !fp_pair_map_get_by_digests(pending_cert, voter->identity_digest, sig->signing_key_digest)) { @@ -1072,7 +1069,7 @@ router_rebuild_store(int flags, desc_store_t *store) smartlist_add(chunk_list, c); } SMARTLIST_FOREACH_END(sd); - if (write_chunks_to_file(fname_tmp, chunk_list, 1)<0) { + if (write_chunks_to_file(fname_tmp, chunk_list, 1, 1)<0) { log_warn(LD_FS, "Error writing router store to disk."); goto done; } @@ -1147,32 +1144,18 @@ router_rebuild_store(int flags, desc_store_t *store) static int router_reload_router_list_impl(desc_store_t *store) { - char *fname = NULL, *altname = NULL, *contents = NULL; + char *fname = NULL, *contents = NULL; struct stat st; - int read_from_old_location = 0; int extrainfo = (store->type == EXTRAINFO_STORE); - time_t now = time(NULL); store->journal_len = store->store_len = 0; fname = get_datadir_fname(store->fname_base); - if (store->fname_alt_base) - altname = get_datadir_fname(store->fname_alt_base); if (store->mmap) /* get rid of it first */ tor_munmap_file(store->mmap); store->mmap = NULL; store->mmap = tor_mmap_file(fname); - if (!store->mmap && altname && file_status(altname) == FN_FILE) { - read_from_old_location = 1; - log_notice(LD_DIR, "Couldn't read %s; trying to load routers from old " - "location %s.", fname, altname); - if ((store->mmap = tor_mmap_file(altname))) - read_from_old_location = 1; - } - if (altname && !read_from_old_location) { - remove_file_if_very_old(altname, now); - } if (store->mmap) { store->store_len = store->mmap->size; if (extrainfo) @@ -1189,14 +1172,6 @@ router_reload_router_list_impl(desc_store_t *store) fname = get_datadir_fname_suffix(store->fname_base, ".new"); if (file_status(fname) == FN_FILE) contents = read_file_to_str(fname, RFTS_BIN|RFTS_IGNORE_MISSING, &st); - if (read_from_old_location) { - tor_free(altname); - altname = get_datadir_fname_suffix(store->fname_alt_base, ".new"); - if (!contents) - contents = read_file_to_str(altname, RFTS_BIN|RFTS_IGNORE_MISSING, &st); - else - remove_file_if_very_old(altname, now); - } if (contents) { if (extrainfo) router_load_extrainfo_from_string(contents, NULL,SAVED_IN_JOURNAL, @@ -1209,9 +1184,8 @@ router_reload_router_list_impl(desc_store_t *store) } tor_free(fname); - tor_free(altname); - if (store->journal_len || read_from_old_location) { + if (store->journal_len) { /* Always clear the journal on startup.*/ router_rebuild_store(RRS_FORCE, store); } else if (!extrainfo) { @@ -1848,7 +1822,7 @@ router_get_advertised_bandwidth_capped(const routerinfo_t *router) * doubles, convert them to uint64_t, and try to scale them linearly so as to * much of the range of uint64_t. If <b>total_out</b> is provided, set it to * the sum of all elements in the array _before_ scaling. */ -/* private */ void +STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, uint64_t *total_out) { @@ -1891,7 +1865,7 @@ gt_i64_timei(uint64_t a, uint64_t b) * value, and return the index of that element. If all elements are 0, choose * an index at random. Return -1 on error. */ -/* private */ int +STATIC int choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries) { int i, i_chosen=-1, n_chosen=0; @@ -2591,19 +2565,6 @@ router_is_named(const routerinfo_t *router) tor_memeq(digest, router->cache_info.identity_digest, DIGEST_LEN)); } -/** Return true iff the digest of <b>router</b>'s identity key, - * encoded in hexadecimal, matches <b>hexdigest</b> (which is - * optionally prefixed with a single dollar sign). Return false if - * <b>hexdigest</b> is malformed, or it doesn't match. */ -static INLINE int -router_hex_digest_matches(const routerinfo_t *router, const char *hexdigest) -{ - return hex_digest_nickname_matches(hexdigest, - router->cache_info.identity_digest, - router->nickname, - router_is_named(router)); -} - /** Return true iff <b>digest</b> is the digest of the identity key of a * trusted directory matching at least one bit of <b>type</b>. If <b>type</b> * is zero, any authority is okay. */ @@ -2798,7 +2759,6 @@ router_get_routerlist(void) routerlist->extra_info_map = eimap_new(); routerlist->desc_store.fname_base = "cached-descriptors"; - routerlist->desc_store.fname_alt_base = "cached-routers"; routerlist->extrainfo_store.fname_base = "cached-extrainfo"; routerlist->desc_store.type = ROUTER_STORE; @@ -4502,12 +4462,8 @@ initiate_descriptor_downloads(const routerstatus_t *source, * try to split our requests into at least this many requests. */ #define MIN_REQUESTS 3 /** If we want fewer than this many descriptors, wait until we - * want more, or until MAX_CLIENT_INTERVAL_WITHOUT_REQUEST has - * passed. */ + * want more, or until TestingClientMaxIntervalWithoutRequest has passed. */ #define MAX_DL_TO_DELAY 16 -/** When directory clients have only a few servers to request, they batch - * them until they have more, or until this amount of time has passed. */ -#define MAX_CLIENT_INTERVAL_WITHOUT_REQUEST (10*60) /** Given a <b>purpose</b> (FETCH_MICRODESC or FETCH_SERVERDESC) and a list of * router descriptor digests or microdescriptor digest256s in @@ -4539,7 +4495,7 @@ launch_descriptor_downloads(int purpose, should_delay = 0; } else { should_delay = (last_descriptor_download_attempted + - MAX_CLIENT_INTERVAL_WITHOUT_REQUEST) > now; + options->TestingClientMaxIntervalWithoutRequest) > now; if (!should_delay && n_downloadable) { if (last_descriptor_download_attempted) { log_info(LD_DIR, @@ -4812,7 +4768,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, continue; /* We have an in-progress download. */ } if (!download_status_is_ready(&rs->dl_status, now, - MAX_ROUTERDESC_DOWNLOAD_FAILURES)) { + options->TestingDescriptorMaxDownloadTries)) { ++n_delayed; /* Not ready for retry. */ continue; } @@ -4972,7 +4928,7 @@ update_extrainfo_downloads(time_t now) continue; } if (!download_status_is_ready(&sd->ei_dl_status, now, - MAX_ROUTERDESC_DOWNLOAD_FAILURES)) { + options->TestingDescriptorMaxDownloadTries)) { ++n_delay; continue; } diff --git a/src/or/routerlist.h b/src/or/routerlist.h index ce0f0f2e3..0162297ca 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -11,6 +11,8 @@ #ifndef TOR_ROUTERLIST_H #define TOR_ROUTERLIST_H +#include "testsupport.h" + int get_n_authorities(dirinfo_type_t type); int trusted_dirs_reload_certs(void); @@ -206,9 +208,10 @@ typedef union u64_dbl_t { double dbl; } u64_dbl_t; -int choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries); -void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, - uint64_t *total_out); +STATIC int choose_array_element_by_weight(const u64_dbl_t *entries, + int n_entries); +STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, + uint64_t *total_out); #endif #endif diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 3aa4bdf8a..571167fad 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -386,7 +386,7 @@ static token_rule_t dir_footer_token_table[] = { T1("dir-key-published",K_DIR_KEY_PUBLISHED, CONCAT_ARGS, NO_OBJ), \ T1("dir-key-expires", K_DIR_KEY_EXPIRES, CONCAT_ARGS, NO_OBJ), \ T1("dir-signing-key", K_DIR_SIGNING_KEY, NO_ARGS, NEED_KEY ),\ - T01("dir-key-crosscert", K_DIR_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),\ + T1("dir-key-crosscert", K_DIR_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),\ T1("dir-key-certification", K_DIR_KEY_CERTIFICATION, \ NO_ARGS, NEED_OBJ), \ T01("dir-address", K_DIR_ADDRESS, GE(1), NO_OBJ), @@ -1728,7 +1728,6 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) log_debug(LD_DIR, "We already checked the signature on this " "certificate; no need to do so again."); found = 1; - cert->is_cross_certified = old_cert->is_cross_certified; } } if (!found) { @@ -1737,18 +1736,14 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) goto err; } - if ((tok = find_opt_by_keyword(tokens, K_DIR_KEY_CROSSCERT))) { - /* XXXX Once all authorities generate cross-certified certificates, - * make this field mandatory. */ - if (check_signature_token(cert->cache_info.identity_digest, - DIGEST_LEN, - tok, - cert->signing_key, - CST_NO_CHECK_OBJTYPE, - "key cross-certification")) { - goto err; - } - cert->is_cross_certified = 1; + tok = find_by_keyword(tokens, K_DIR_KEY_CROSSCERT); + if (check_signature_token(cert->cache_info.identity_digest, + DIGEST_LEN, + tok, + cert->signing_key, + CST_NO_CHECK_OBJTYPE, + "key cross-certification")) { + goto err; } } diff --git a/src/or/statefile.c b/src/or/statefile.c index bcb7b0741..8ab04763d 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -4,6 +4,7 @@ * Copyright (c) 2007-2013, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#define STATEFILE_PRIVATE #include "or.h" #include "circuitstats.h" #include "config.h" @@ -90,8 +91,11 @@ static config_var_t state_vars_[] = { #undef VAR #undef V -static int or_state_validate(or_state_t *old_options, or_state_t *options, - int from_setconf, char **msg); +static int or_state_validate(or_state_t *state, char **msg); + +static int or_state_validate_cb(void *old_options, void *options, + void *default_options, + int from_setconf, char **msg); /** Magic value for or_state_t. */ #define OR_STATE_MAGIC 0x57A73f57 @@ -109,7 +113,7 @@ static const config_format_t state_format = { STRUCT_OFFSET(or_state_t, magic_), state_abbrevs_, state_vars_, - (validate_fn_t)or_state_validate, + or_state_validate_cb, &state_extra_var, }; @@ -117,8 +121,8 @@ static const config_format_t state_format = { static or_state_t *global_state = NULL; /** Return the persistent state struct for this Tor. */ -or_state_t * -get_or_state(void) +MOCK_IMPL(or_state_t *, +get_or_state, (void)) { tor_assert(global_state); return global_state; @@ -194,21 +198,27 @@ validate_transports_in_state(or_state_t *state) return 0; } -/** Return 0 if every setting in <b>state</b> is reasonable, and a - * permissible transition from <b>old_state</b>. Else warn and return -1. - * Should have no side effects, except for normalizing the contents of - * <b>state</b>. - */ -/* XXX from_setconf is here because of bug 238 */ static int -or_state_validate(or_state_t *old_state, or_state_t *state, - int from_setconf, char **msg) +or_state_validate_cb(void *old_state, void *state, void *default_state, + int from_setconf, char **msg) { /* We don't use these; only options do. Still, we need to match that * signature. */ (void) from_setconf; + (void) default_state; (void) old_state; + return or_state_validate(state, msg); +} + +/** Return 0 if every setting in <b>state</b> is reasonable, and a + * permissible transition from <b>old_state</b>. Else warn and return -1. + * Should have no side effects, except for normalizing the contents of + * <b>state</b>. + */ +static int +or_state_validate(or_state_t *state, char **msg) +{ if (entry_guards_parse_state(state, 0, msg)<0) return -1; @@ -237,7 +247,8 @@ or_state_set(or_state_t *new_state) tor_free(err); ret = -1; } - if (circuit_build_times_parse_state(&circ_times, global_state) < 0) { + if (circuit_build_times_parse_state( + get_circuit_build_times_mutable(),global_state) < 0) { ret = -1; } return ret; @@ -322,7 +333,7 @@ or_state_load(void) } } - if (!badstate && or_state_validate(NULL, new_state, 1, &errmsg) < 0) + if (!badstate && or_state_validate(new_state, &errmsg) < 0) badstate = 1; if (errmsg) { @@ -404,7 +415,7 @@ or_state_save(time_t now) * to avoid redundant writes. */ entry_guards_update_state(global_state); rep_hist_update_state(global_state); - circuit_build_times_update_state(&circ_times, global_state); + circuit_build_times_update_state(get_circuit_build_times(), global_state); if (accounting_is_enabled(get_options())) accounting_run_housekeeping(now); @@ -449,7 +460,7 @@ or_state_save(time_t now) /** Return the config line for transport <b>transport</b> in the current state. * Return NULL if there is no config line for <b>transport</b>. */ -static config_line_t * +STATIC config_line_t * get_transport_in_state_by_name(const char *transport) { or_state_t *or_state = get_or_state(); diff --git a/src/or/statefile.h b/src/or/statefile.h index dcdee6c60..c1413ff95 100644 --- a/src/or/statefile.h +++ b/src/or/statefile.h @@ -7,7 +7,7 @@ #ifndef TOR_STATEFILE_H #define TOR_STATEFILE_H -or_state_t *get_or_state(void); +MOCK_DECL(or_state_t *,get_or_state,(void)); int did_last_state_file_write_fail(void); int or_state_save(time_t now); @@ -18,5 +18,9 @@ int or_state_load(void); int or_state_loaded(void); void or_state_free_all(void); +#ifdef STATEFILE_PRIVATE +STATIC config_line_t *get_transport_in_state_by_name(const char *transport); +#endif + #endif diff --git a/src/or/status.c b/src/or/status.c index d239e6ee7..6e2206e5e 100644 --- a/src/or/status.c +++ b/src/or/status.c @@ -15,6 +15,9 @@ #include "circuitlist.h" #include "main.h" #include "hibernate.h" +#include "statefile.h" + +static void log_accounting(const time_t now, const or_options_t *options); /** Return the total number of circuits. */ static int @@ -23,7 +26,7 @@ count_circuits(void) circuit_t *circ; int nr=0; - for (circ = circuit_get_global_list_(); circ; circ = circ->next) + TOR_LIST_FOREACH(circ, circuit_get_global_list(), head) nr++; return nr; @@ -111,6 +114,10 @@ log_heartbeat(time_t now) uptime, count_circuits(),bw_sent,bw_rcvd, hibernating?" We are currently hibernating.":""); + if (server_mode(options) && accounting_is_enabled(options) && !hibernating) { + log_accounting(now, options); + } + if (stats_n_data_cells_packaged && !hibernating) log_notice(LD_HEARTBEAT, "Average packaged cell fullness: %2.3f%%", 100*(U64_TO_DBL(stats_n_data_bytes_packaged) / @@ -128,3 +135,27 @@ log_heartbeat(time_t now) return 0; } +static void +log_accounting(const time_t now, const or_options_t *options) +{ + or_state_t *state = get_or_state(); + char *acc_rcvd = bytes_to_usage(state->AccountingBytesReadInInterval); + char *acc_sent = bytes_to_usage(state->AccountingBytesWrittenInInterval); + char *acc_max = bytes_to_usage(options->AccountingMax); + time_t interval_end = accounting_get_end_time(); + char end_buf[ISO_TIME_LEN + 1]; + char *remaining = NULL; + format_local_iso_time(end_buf, interval_end); + remaining = secs_to_uptime(interval_end - now); + + log_notice(LD_HEARTBEAT, "Heartbeat: Accounting enabled. " + "Sent: %s / %s, Received: %s / %s. The " + "current accounting interval ends on %s, in %s.", + acc_sent, acc_max, acc_rcvd, acc_max, end_buf, remaining); + + tor_free(acc_rcvd); + tor_free(acc_sent); + tor_free(acc_max); + tor_free(remaining); +} + diff --git a/src/or/transports.c b/src/or/transports.c index 3749d6bb2..8b4a11882 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -95,18 +95,17 @@ #include "util.h" #include "router.h" #include "statefile.h" +#include "entrynodes.h" +#include "connection_or.h" +#include "ext_orport.h" +#include "control.h" static process_environment_t * create_managed_proxy_environment(const managed_proxy_t *mp); static INLINE int proxy_configuration_finished(const managed_proxy_t *mp); -static void managed_proxy_destroy(managed_proxy_t *mp, - int also_terminate_process); - static void handle_finished_proxy(managed_proxy_t *mp); -static int configure_proxy(managed_proxy_t *mp); - static void parse_method_error(const char *line, int is_server_method); #define parse_server_method_error(l) parse_method_error(l, 1) #define parse_client_method_error(l) parse_method_error(l, 0) @@ -136,7 +135,8 @@ static smartlist_t *transport_list = NULL; SOCKS version <b>socks_ver</b>. */ static transport_t * transport_new(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver) + const char *name, int socks_ver, + const char *extra_info_args) { transport_t *t = tor_malloc_zero(sizeof(transport_t)); @@ -144,6 +144,8 @@ transport_new(const tor_addr_t *addr, uint16_t port, t->port = port; t->name = tor_strdup(name); t->socks_version = socks_ver; + if (extra_info_args) + t->extra_info_args = tor_strdup(extra_info_args); return t; } @@ -156,6 +158,7 @@ transport_free(transport_t *transport) return; tor_free(transport->name); + tor_free(transport->extra_info_args); tor_free(transport); } @@ -323,7 +326,7 @@ int transport_add_from_config(const tor_addr_t *addr, uint16_t port, const char *name, int socks_ver) { - transport_t *t = transport_new(addr, port, name, socks_ver); + transport_t *t = transport_new(addr, port, name, socks_ver, NULL); int r = transport_add(t); @@ -549,7 +552,7 @@ pt_configure_remaining_proxies(void) assert_unconfigured_count_ok(); SMARTLIST_FOREACH_BEGIN(tmp, managed_proxy_t *, mp) { - tor_assert(mp->conf_state != PT_PROTO_BROKEN || + tor_assert(mp->conf_state != PT_PROTO_BROKEN && mp->conf_state != PT_PROTO_FAILED_LAUNCH); if (mp->got_hup) { @@ -589,7 +592,7 @@ pt_configure_remaining_proxies(void) * Return 1 if the transport configuration finished, and return 0 * otherwise (if we still have more configuring to do for this * proxy). */ -static int +STATIC int configure_proxy(managed_proxy_t *mp) { int configuration_finished = 0; @@ -657,6 +660,7 @@ register_server_proxy(const managed_proxy_t *mp) save_transport_to_state(t->name, &t->addr, t->port); log_notice(LD_GENERAL, "Registered server transport '%s' at '%s'", t->name, fmt_addrport(&t->addr, t->port)); + control_event_transport_launched("server", t->name, &t->addr, t->port); } SMARTLIST_FOREACH_END(t); } @@ -679,9 +683,11 @@ register_client_proxy(const managed_proxy_t *mp) break; case 0: log_info(LD_GENERAL, "Successfully registered transport %s", t->name); + control_event_transport_launched("client", t->name, &t->addr, t->port); break; case 1: log_info(LD_GENERAL, "Successfully registered transport %s", t->name); + control_event_transport_launched("client", t->name, &t->addr, t->port); transport_free(transport_tmp); break; } @@ -699,7 +705,7 @@ register_proxy(const managed_proxy_t *mp) } /** Free memory allocated by managed proxy <b>mp</b>. */ -static void +STATIC void managed_proxy_destroy(managed_proxy_t *mp, int also_terminate_process) { @@ -713,7 +719,8 @@ managed_proxy_destroy(managed_proxy_t *mp, smartlist_free(mp->transports_to_launch); /* remove it from the list of managed proxies */ - smartlist_remove(managed_proxy_list, mp); + if (managed_proxy_list) + smartlist_remove(managed_proxy_list, mp); /* free the argv */ free_execve_args(mp->argv); @@ -750,7 +757,6 @@ handle_finished_proxy(managed_proxy_t *mp) } unconfigured_proxies_n--; - tor_assert(unconfigured_proxies_n >= 0); } /** Return true if the configuration of the managed proxy <b>mp</b> is @@ -781,7 +787,7 @@ handle_methods_done(const managed_proxy_t *mp) /** Handle a configuration protocol <b>line</b> received from a * managed proxy <b>mp</b>. */ -void +STATIC void handle_proxy_line(const char *line, managed_proxy_t *mp) { log_info(LD_GENERAL, "Got a line from managed proxy '%s': (%s)", @@ -882,7 +888,7 @@ handle_proxy_line(const char *line, managed_proxy_t *mp) } /** Parses an ENV-ERROR <b>line</b> and warns the user accordingly. */ -void +STATIC void parse_env_error(const char *line) { /* (Length of the protocol string) plus (a space) and (the first char of @@ -898,7 +904,7 @@ parse_env_error(const char *line) /** Handles a VERSION <b>line</b>. Updates the configuration protocol * version in <b>mp</b>. */ -int +STATIC int parse_version(const char *line, managed_proxy_t *mp) { if (strlen(line) < (strlen(PROTO_NEG_SUCCESS) + 2)) { @@ -939,14 +945,14 @@ parse_method_error(const char *line, int is_server) /** Parses an SMETHOD <b>line</b> and if well-formed it registers the * new transport in <b>mp</b>. */ -int +STATIC int parse_smethod_line(const char *line, managed_proxy_t *mp) { int r; smartlist_t *items = NULL; char *method_name=NULL; - + char *args_string=NULL; char *addrport=NULL; tor_addr_t tor_addr; char *address=NULL; @@ -963,6 +969,9 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) goto err; } + /* Example of legit SMETHOD line: + SMETHOD obfs2 0.0.0.0:25612 ARGS:secret=supersekrit,key=superkey */ + tor_assert(!strcmp(smartlist_get(items,0),PROTO_SMETHOD)); method_name = smartlist_get(items,1); @@ -990,7 +999,19 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) goto err; } - transport = transport_new(&tor_addr, port, method_name, PROXY_NONE); + if (smartlist_len(items) > 3) { + /* Seems like there are also some [options] in the SMETHOD line. + Let's see if we can parse them. */ + char *options_string = smartlist_get(items, 3); + log_debug(LD_CONFIG, "Got options_string: %s", options_string); + if (!strcmpstart(options_string, "ARGS:")) { + args_string = options_string+strlen("ARGS:"); + log_debug(LD_CONFIG, "Got ARGS: %s", args_string); + } + } + + transport = transport_new(&tor_addr, port, method_name, + PROXY_NONE, args_string); if (!transport) goto err; @@ -1016,7 +1037,7 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) /** Parses a CMETHOD <b>line</b>, and if well-formed it registers * the new transport in <b>mp</b>. */ -int +STATIC int parse_cmethod_line(const char *line, managed_proxy_t *mp) { int r; @@ -1082,7 +1103,7 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) goto err; } - transport = transport_new(&tor_addr, port, method_name, socks_ver); + transport = transport_new(&tor_addr, port, method_name, socks_ver, NULL); if (!transport) goto err; @@ -1105,6 +1126,50 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) return r; } +/** Return a newly allocated string that tor should place in + * TOR_PT_SERVER_TRANSPORT_OPTIONS while configuring the server + * manged proxy in <b>mp</b>. Return NULL if no such options are found. */ +STATIC char * +get_transport_options_for_server_proxy(const managed_proxy_t *mp) +{ + char *options_string = NULL; + smartlist_t *string_sl = smartlist_new(); + + tor_assert(mp->is_server); + + /** Loop over the transports of the proxy. If we have options for + any of them, format them appropriately and place them in our + smartlist. Finally, join our smartlist to get the final + string. */ + SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, const char *, transport) { + smartlist_t *options_tmp_sl = NULL; + options_tmp_sl = get_options_for_server_transport(transport); + if (!options_tmp_sl) + continue; + + /** Loop over the options of this transport, escape them, and + place them in the smartlist. */ + SMARTLIST_FOREACH_BEGIN(options_tmp_sl, const char *, options) { + char *escaped_opts = tor_escape_str_for_pt_args(options, ":;\\"); + smartlist_add_asprintf(string_sl, "%s:%s", + transport, escaped_opts); + tor_free(escaped_opts); + } SMARTLIST_FOREACH_END(options); + + SMARTLIST_FOREACH(options_tmp_sl, char *, c, tor_free(c)); + smartlist_free(options_tmp_sl); + } SMARTLIST_FOREACH_END(transport); + + if (smartlist_len(string_sl)) { + options_string = smartlist_join_strings(string_sl, ";", 0, NULL); + } + + SMARTLIST_FOREACH(string_sl, char *, t, tor_free(t)); + smartlist_free(string_sl); + + return options_string; +} + /** Return the string that tor should place in TOR_PT_SERVER_BINDADDR * while configuring the server managed proxy in <b>mp</b>. The * string is stored in the heap, and it's the the responsibility of @@ -1139,6 +1204,8 @@ get_bindaddr_for_server_proxy(const managed_proxy_t *mp) static process_environment_t * create_managed_proxy_environment(const managed_proxy_t *mp) { + const or_options_t *options = get_options(); + /* Environment variables to be added to or set in mp's environment. */ smartlist_t *envs = smartlist_new(); /* XXXX The next time someone touches this code, shorten the name of @@ -1186,13 +1253,39 @@ create_managed_proxy_environment(const managed_proxy_t *mp) tor_free(bindaddr_tmp); } + { + char *server_transport_options = + get_transport_options_for_server_proxy(mp); + if (server_transport_options) { + smartlist_add_asprintf(envs, "TOR_PT_SERVER_TRANSPORT_OPTIONS=%s", + server_transport_options); + tor_free(server_transport_options); + } + } + /* XXX024 Remove the '=' here once versions of obfsproxy which * assert that this env var exists are sufficiently dead. * * (If we remove this line entirely, some joker will stick this * variable in Tor's environment and crash PTs that try to parse * it even when not run in server mode.) */ - smartlist_add(envs, tor_strdup("TOR_PT_EXTENDED_SERVER_PORT=")); + + if (options->ExtORPort_lines) { + char *ext_or_addrport_tmp = + get_first_listener_addrport_string(CONN_TYPE_EXT_OR_LISTENER); + char *cookie_file_loc = get_ext_or_auth_cookie_file_name(); + + smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT=%s", + ext_or_addrport_tmp); + smartlist_add_asprintf(envs, "TOR_PT_AUTH_COOKIE_FILE=%s", + cookie_file_loc); + + tor_free(ext_or_addrport_tmp); + tor_free(cookie_file_loc); + + } else { + smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT="); + } } SMARTLIST_FOREACH_BEGIN(envs, const char *, env_var) { @@ -1216,7 +1309,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp) * <b>proxy_argv</b>. * * Requires that proxy_argv have at least one element. */ -static managed_proxy_t * +STATIC managed_proxy_t * managed_proxy_create(const smartlist_t *transport_list, char **proxy_argv, int is_server) { @@ -1390,6 +1483,8 @@ pt_get_extra_info_descriptor_string(void) tor_assert(mp->transports); SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + char *transport_args = NULL; + /* If the transport proxy returned "0.0.0.0" as its address, and * we know our external IP address, use it. Otherwise, use the * returned address. */ @@ -1405,9 +1500,16 @@ pt_get_extra_info_descriptor_string(void) addrport = fmt_addrport(&t->addr, t->port); } + /* If this transport has any arguments with it, prepend a space + to them so that we can add them to the transport line. */ + if (t->extra_info_args) + tor_asprintf(&transport_args, " %s", t->extra_info_args); + smartlist_add_asprintf(string_chunks, - "transport %s %s", - t->name, addrport); + "transport %s %s%s", + t->name, addrport, + transport_args ? transport_args : ""); + tor_free(transport_args); } SMARTLIST_FOREACH_END(t); } SMARTLIST_FOREACH_END(mp); @@ -1426,6 +1528,57 @@ pt_get_extra_info_descriptor_string(void) return the_string; } +/** Stringify the SOCKS arguments in <b>socks_args</b> according to + * 180_pluggable_transport.txt. The string is allocated on the heap + * and it's the responsibility of the caller to free it after use. */ +char * +pt_stringify_socks_args(const smartlist_t *socks_args) +{ + /* tmp place to store escaped socks arguments, so that we can + concatenate them up afterwards */ + smartlist_t *sl_tmp = NULL; + char *escaped_string = NULL; + char *new_string = NULL; + + tor_assert(socks_args); + tor_assert(smartlist_len(socks_args) > 0); + + sl_tmp = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(socks_args, const char *, s) { + /* Escape ';' and '\'. */ + escaped_string = tor_escape_str_for_pt_args(s, ";\\"); + if (!escaped_string) + goto done; + + smartlist_add(sl_tmp, escaped_string); + } SMARTLIST_FOREACH_END(s); + + new_string = smartlist_join_strings(sl_tmp, ";", 0, NULL); + + done: + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + + return new_string; +} + +/** Return a string of the SOCKS arguments that we should pass to the + * pluggable transports proxy in <b>addr</b>:<b>port</b> according to + * 180_pluggable_transport.txt. The string is allocated on the heap + * and it's the responsibility of the caller to free it after use. */ +char * +pt_get_socks_args_for_proxy_addrport(const tor_addr_t *addr, uint16_t port) +{ + const smartlist_t *socks_args = NULL; + + socks_args = get_socks_args_by_bridge_addrport(addr, port); + if (!socks_args) + return NULL; + + return pt_stringify_socks_args(socks_args); +} + /** The tor config was read. * Destroy all managed proxies that were marked by a previous call to * prepare_proxy_list_for_config_read() and are not used by the new diff --git a/src/or/transports.h b/src/or/transports.h index 6ee82f455..7b524f207 100644 --- a/src/or/transports.h +++ b/src/or/transports.h @@ -25,6 +25,9 @@ typedef struct transport_t { /** Boolean: We are re-parsing our transport list, and we are going to remove * this one if we don't find it in the list of configured transports. */ unsigned marked_for_removal : 1; + /** Arguments for this transport that must be written to the + extra-info descriptor. */ + char *extra_info_args; } transport_t; void mark_transport_list(void); @@ -55,6 +58,10 @@ void pt_prepare_proxy_list_for_config_read(void); void sweep_proxy_list(void); smartlist_t *get_transport_proxy_ports(void); +char *pt_stringify_socks_args(const smartlist_t *socks_args); + +char *pt_get_socks_args_for_proxy_addrport(const tor_addr_t *addr, + uint16_t port); #ifdef PT_PRIVATE /** State of the managed proxy configuration protocol. */ @@ -100,12 +107,21 @@ typedef struct { smartlist_t *transports; } managed_proxy_t; -int parse_cmethod_line(const char *line, managed_proxy_t *mp); -int parse_smethod_line(const char *line, managed_proxy_t *mp); +STATIC int parse_cmethod_line(const char *line, managed_proxy_t *mp); +STATIC int parse_smethod_line(const char *line, managed_proxy_t *mp); + +STATIC int parse_version(const char *line, managed_proxy_t *mp); +STATIC void parse_env_error(const char *line); +STATIC void handle_proxy_line(const char *line, managed_proxy_t *mp); +STATIC char *get_transport_options_for_server_proxy(const managed_proxy_t *mp); + +STATIC void managed_proxy_destroy(managed_proxy_t *mp, + int also_terminate_process); + +STATIC managed_proxy_t *managed_proxy_create(const smartlist_t *transport_list, + char **proxy_argv, int is_server); -int parse_version(const char *line, managed_proxy_t *mp); -void parse_env_error(const char *line); -void handle_proxy_line(const char *line, managed_proxy_t *mp); +STATIC int configure_proxy(managed_proxy_t *mp); #endif diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 562c8df8b..6479f9d39 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -12,9 +12,9 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ crypt32.lib gdi32.lib user32.lib TEST_OBJECTS = test.obj test_addr.obj test_containers.obj \ - test_crypto.obj test_data.obj test_dir.obj test_microdesc.obj \ - test_pt.obj test_util.obj test_config.obj test_cell_formats.obj \ - test_replay.obj test_introduce.obj tinytest.obj + test_controller_events.ogj test_crypto.obj test_data.obj test_dir.obj \ + test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ + test_cell_formats.obj test_replay.obj test_introduce.obj tinytest.obj tinytest.obj: ..\ext\tinytest.c $(CC) $(CFLAGS) /D snprintf=_snprintf /c ..\ext\tinytest.c diff --git a/src/test/bench.c b/src/test/bench.c index 5a8d21d17..e650569a7 100644 --- a/src/test/bench.c +++ b/src/test/bench.c @@ -14,9 +14,6 @@ const char tor_git_revision[] = ""; #include "orconfig.h" -#define RELAY_PRIVATE -#define CONFIG_PRIVATE - #include "or.h" #include "onion_tap.h" #include "relay.h" @@ -204,6 +201,7 @@ bench_onion_ntor(void) for (i = 0; i < iters; ++i) { onion_skin_ntor_create(nodeid, &keypair1.pubkey, &state, os); ntor_handshake_state_free(state); + state = NULL; } end = perftime(); printf("Client-side, part 1: %f usec.\n", NANOCOUNT(start, end, iters)/1e3); diff --git a/src/test/bt_test.py b/src/test/bt_test.py new file mode 100755 index 000000000..2de9924a5 --- /dev/null +++ b/src/test/bt_test.py @@ -0,0 +1,42 @@ +# Copyright 2013, The Tor Project, Inc +# See LICENSE for licensing information + +""" +bt_test.py + +This file tests the output from test-bt-cl to make sure it's as expected. + +Example usage: + +$ ./src/test/test-bt-cl crash | ./src/test/bt_test.py +OK +$ ./src/test/test-bt-cl assert | ./src/test/bt_test.py +OK + +""" + +import sys + + +def matches(lines, funcs): + if len(lines) < len(funcs): + return False + try: + for l, f in zip(lines, funcs): + l.index(f) + except ValueError: + return False + else: + return True + +FUNCNAMES = "crash oh_what a_tangled_web we_weave main".split() + +LINES = sys.stdin.readlines() + +for I in range(len(LINES)): + if matches(LINES[I:], FUNCNAMES): + print "OK" + break +else: + print "BAD" + diff --git a/src/test/include.am b/src/test/include.am index 112d1a79d..c16dd14fe 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -1,11 +1,15 @@ -TESTS+= src/test/test +TESTS += src/test/test -noinst_PROGRAMS+= src/test/test src/test/test-child src/test/bench +noinst_PROGRAMS+= src/test/bench +if UNITTESTS_ENABLED +noinst_PROGRAMS+= src/test/test src/test/test-child +endif src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ -DLOCALSTATEDIR="\"$(localstatedir)\"" \ -DBINDIR="\"$(bindir)\"" \ - -I"$(top_srcdir)/src/or" -I"$(top_srcdir)/src/ext" + -I"$(top_srcdir)/src/or" -I"$(top_srcdir)/src/ext" \ + -DTOR_UNIT_TESTS # -L flags need to go in LDFLAGS. -l flags need to go in LDADD. # This seems to matter nowhere but on Windows, but I assure you that it @@ -14,31 +18,40 @@ src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ src_test_test_SOURCES = \ src/test/test.c \ src/test/test_addr.c \ + src/test/test_buffers.c \ src/test/test_cell_formats.c \ + src/test/test_circuitlist.c \ + src/test/test_circuitmux.c \ src/test/test_containers.c \ + src/test/test_controller_events.c \ src/test/test_crypto.c \ + src/test/test_cell_queue.c \ src/test/test_data.c \ src/test/test_dir.c \ + src/test/test_extorport.c \ src/test/test_introduce.c \ + src/test/test_logging.c \ src/test/test_microdesc.c \ + src/test/test_options.c \ src/test/test_pt.c \ src/test/test_replay.c \ + src/test/test_socks.c \ src/test/test_util.c \ src/test/test_config.c \ src/ext/tinytest.c +src_test_test_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + src_test_test_CPPFLAGS= $(src_test_AM_CPPFLAGS) src_test_bench_SOURCES = \ src/test/bench.c -src_test_bench_CPPFLAGS= $(src_test_AM_CPPFLAGS) - src_test_test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ -src_test_test_LDADD = src/or/libtor.a src/common/libor.a \ - src/common/libor-crypto.a $(LIBDONNA) \ - src/common/libor-event.a \ +src_test_test_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \ + src/common/libor-crypto-testing.a $(LIBDONNA) \ + src/common/libor-event-testing.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @@ -63,6 +76,33 @@ src_test_test_ntor_cl_LDADD = src/or/libtor.a src/common/libor.a \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ src_test_test_ntor_cl_AM_CPPFLAGS = \ -I"$(top_srcdir)/src/or" +NTOR_TEST_DEPS=src/test/test-ntor-cl +else +NTOR_TEST_DEPS= +endif +if COVERAGE_ENABLED +CMDLINE_TEST_TOR = ./src/or/tor-cov +else +CMDLINE_TEST_TOR = ./src/or/tor endif +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 \ + @TOR_LIB_MATH@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ +src_test_test_bt_cl_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_test_test_bt_cl_CPPFLAGS= $(src_test_AM_CPPFLAGS) + + +check-local: $(NTOR_TEST_DEPS) $(CMDLINE_TEST_TOR) +if USEPYTHON + $(PYTHON) $(top_srcdir)/src/test/test_cmdline_args.py $(CMDLINE_TEST_TOR) "${top_srcdir}" +if CURVE25519_ENABLED + $(PYTHON) $(top_srcdir)/src/test/ntor_ref.py test-tor + $(PYTHON) $(top_srcdir)/src/test/ntor_ref.py self-test +endif + ./src/test/test-bt-cl assert | $(PYTHON) $(top_srcdir)/src/test/bt_test.py + ./src/test/test-bt-cl crash | $(PYTHON) $(top_srcdir)/src/test/bt_test.py +endif diff --git a/src/test/ntor_ref.py b/src/test/ntor_ref.py index ade468da7..2a5f98460 100644..100755 --- a/src/test/ntor_ref.py +++ b/src/test/ntor_ref.py @@ -1,3 +1,4 @@ +#!/usr/bin/python # Copyright 2012-2013, The Tor Project, Inc # See LICENSE for licensing information @@ -27,7 +28,13 @@ commands: """ import binascii -import curve25519 +try: + import curve25519 +except ImportError: + curve25519 = None + print "SKIPPING: No Python curve25519 module installed" + import sys + sys.exit(0) import hashlib import hmac import subprocess @@ -286,6 +293,7 @@ def demo(node_id="iToldYouAboutStairs.", server_key=PrivateKey()): assert len(skeys) == 72 assert len(ckeys) == 72 assert skeys == ckeys + print "OK" # ====================================================================== def timing(): @@ -368,13 +376,15 @@ def test_tor(): assert c_keys == s_keys assert len(c_keys) == 90 - print "We just interoperated." + print "OK" # ====================================================================== if __name__ == '__main__': import sys - if sys.argv[1] == 'gen_kdf_vectors': + if len(sys.argv) < 2: + print __doc__ + elif sys.argv[1] == 'gen_kdf_vectors': kdf_vectors() elif sys.argv[1] == 'timing': timing() diff --git a/src/test/test-network.sh b/src/test/test-network.sh new file mode 100755 index 000000000..7b5986416 --- /dev/null +++ b/src/test/test-network.sh @@ -0,0 +1,47 @@ +#! /bin/sh + +until [ -z $1 ] +do + case $1 in + --chutney-path) + export CHUTNEY_PATH="$2" + shift + ;; + --tor-path) + export TOR_DIR="$2" + shift + ;; + --flavo?r|--network-flavo?r) + export NETWORK_FLAVOUR="$2" + shift + ;; + *) + echo "Sorry, I don't know what to do with '$1'." + exit 2 + ;; + esac + shift +done + +TOR_DIR="${TOR_DIR:-$PWD}" +NETWORK_FLAVOUR=${NETWORK_FLAVOUR:-basic} +CHUTNEY_NETWORK=networks/$NETWORK_FLAVOUR +myname=$(basename $0) + +[ -d "$CHUTNEY_PATH" ] && [ -x "$CHUTNEY_PATH/chutney" ] || { + echo "$myname: missing 'chutney' in CHUTNEY_PATH ($CHUTNEY_PATH)" + exit 1 +} +cd "$CHUTNEY_PATH" +# For picking up the right tor binaries. +PATH="$TOR_DIR/src/or:$TOR_DIR/src/tools:$PATH" +./tools/bootstrap-network.sh $NETWORK_FLAVOUR || exit 2 + +# Sleep some, waiting for the network to bootstrap. +# TODO: Add chutney command 'bootstrap-status' and use that instead. +BOOTSTRAP_TIME=18 +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 diff --git a/src/test/test.c b/src/test/test.c index 4ec879234..6ea41b140 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -28,11 +28,10 @@ const char tor_git_revision[] = ""; /* These macros pull in declarations for some functions and structures that * are typically file-private. */ -#define BUFFERS_PRIVATE -#define CONFIG_PRIVATE #define GEOIP_PRIVATE #define ROUTER_PRIVATE #define CIRCUITSTATS_PRIVATE +#define CIRCUITLIST_PRIVATE /* * Linux doesn't provide lround in math.h by default, but mac os does... @@ -55,6 +54,7 @@ double fabs(double x); #include "mempool.h" #include "memarea.h" #include "onion.h" +#include "onion_ntor.h" #include "onion_tap.h" #include "policies.h" #include "rephist.h" @@ -218,667 +218,138 @@ free_pregenerated_keys(void) } } -typedef struct socks_test_data_t { - socks_request_t *req; - buf_t *buf; -} socks_test_data_t; - -static void * -socks_test_setup(const struct testcase_t *testcase) -{ - socks_test_data_t *data = tor_malloc(sizeof(socks_test_data_t)); - (void)testcase; - data->buf = buf_new_with_capacity(256); - data->req = socks_request_new(); - config_register_addressmaps(get_options()); - return data; -} -static int -socks_test_cleanup(const struct testcase_t *testcase, void *ptr) -{ - socks_test_data_t *data = ptr; - (void)testcase; - buf_free(data->buf); - socks_request_free(data->req); - tor_free(data); - return 1; -} - -const struct testcase_setup_t socks_setup = { - socks_test_setup, socks_test_cleanup -}; - -#define SOCKS_TEST_INIT() \ - socks_test_data_t *testdata = ptr; \ - buf_t *buf = testdata->buf; \ - socks_request_t *socks = testdata->req; -#define ADD_DATA(buf, s) \ - write_to_buf(s, sizeof(s)-1, buf) - -static void -socks_request_clear(socks_request_t *socks) -{ - tor_free(socks->username); - tor_free(socks->password); - memset(socks, 0, sizeof(socks_request_t)); -} - -/** Perform unsupported SOCKS 4 commands */ -static void -test_socks_4_unsupported_commands(void *ptr) -{ - SOCKS_TEST_INIT(); - - /* SOCKS 4 Send BIND [02] to IP address 2.2.2.2:4369 */ - ADD_DATA(buf, "\x04\x02\x11\x11\x02\x02\x02\x02\x00"); - test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == -1); - test_eq(4, socks->socks_version); - test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ - - done: - ; -} - -/** Perform supported SOCKS 4 commands */ -static void -test_socks_4_supported_commands(void *ptr) -{ - SOCKS_TEST_INIT(); - - test_eq(0, buf_datalen(buf)); - - /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4370 */ - ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x03\x00"); - test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); - test_eq(4, socks->socks_version); - test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ - test_eq(SOCKS_COMMAND_CONNECT, socks->command); - test_streq("2.2.2.3", socks->address); - test_eq(4370, socks->port); - test_assert(socks->got_auth == 0); - test_assert(! socks->username); - - test_eq(0, buf_datalen(buf)); - socks_request_clear(socks); - - /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4369 with userid*/ - ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x04me\x00"); - test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); - test_eq(4, socks->socks_version); - test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ - test_eq(SOCKS_COMMAND_CONNECT, socks->command); - test_streq("2.2.2.4", socks->address); - test_eq(4370, socks->port); - test_assert(socks->got_auth == 1); - test_assert(socks->username); - test_eq(2, socks->usernamelen); - test_memeq("me", socks->username, 2); - - test_eq(0, buf_datalen(buf)); - socks_request_clear(socks); - - /* SOCKS 4a Send RESOLVE [F0] request for torproject.org */ - ADD_DATA(buf, "\x04\xF0\x01\x01\x00\x00\x00\x02me\x00torproject.org\x00"); - test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); - test_eq(4, socks->socks_version); - test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ - test_streq("torproject.org", socks->address); - - test_eq(0, buf_datalen(buf)); - - done: - ; -} - -/** Perform unsupported SOCKS 5 commands */ -static void -test_socks_5_unsupported_commands(void *ptr) -{ - SOCKS_TEST_INIT(); - - /* SOCKS 5 Send unsupported BIND [02] command */ - ADD_DATA(buf, "\x05\x02\x00\x01"); - - test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks), 0); - test_eq(0, buf_datalen(buf)); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(0, socks->reply[1]); - ADD_DATA(buf, "\x05\x02\x00\x01\x02\x02\x02\x01\x01\x01"); - test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks), -1); - /* XXX: shouldn't tor reply 'command not supported' [07]? */ - - buf_clear(buf); - socks_request_clear(socks); - - /* SOCKS 5 Send unsupported UDP_ASSOCIATE [03] command */ - ADD_DATA(buf, "\x05\x03\x00\x01\x02"); - test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks), 0); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(2, socks->reply[1]); - ADD_DATA(buf, "\x05\x03\x00\x01\x02\x02\x02\x01\x01\x01"); - test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks), -1); - /* XXX: shouldn't tor reply 'command not supported' [07]? */ - - done: - ; -} - -/** Perform supported SOCKS 5 commands */ -static void -test_socks_5_supported_commands(void *ptr) -{ - SOCKS_TEST_INIT(); - - /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ - ADD_DATA(buf, "\x05\x01\x00"); - test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks), 0); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(0, socks->reply[1]); - - ADD_DATA(buf, "\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"); - test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks), 1); - test_streq("2.2.2.2", socks->address); - test_eq(4369, socks->port); - - test_eq(0, buf_datalen(buf)); - socks_request_clear(socks); - - /* SOCKS 5 Send CONNECT [01] to FQDN torproject.org:4369 */ - ADD_DATA(buf, "\x05\x01\x00"); - ADD_DATA(buf, "\x05\x01\x00\x03\x0Etorproject.org\x11\x11"); - test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks), 1); - - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(0, socks->reply[1]); - test_streq("torproject.org", socks->address); - test_eq(4369, socks->port); - - test_eq(0, buf_datalen(buf)); - socks_request_clear(socks); - - /* SOCKS 5 Send RESOLVE [F0] request for torproject.org:4369 */ - ADD_DATA(buf, "\x05\x01\x00"); - ADD_DATA(buf, "\x05\xF0\x00\x03\x0Etorproject.org\x01\x02"); - test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(0, socks->reply[1]); - test_streq("torproject.org", socks->address); - - test_eq(0, buf_datalen(buf)); - socks_request_clear(socks); - - /* SOCKS 5 Send RESOLVE_PTR [F1] for IP address 2.2.2.5 */ - ADD_DATA(buf, "\x05\x01\x00"); - ADD_DATA(buf, "\x05\xF1\x00\x01\x02\x02\x02\x05\x01\x03"); - test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(0, socks->reply[1]); - test_streq("2.2.2.5", socks->address); - - test_eq(0, buf_datalen(buf)); - - done: - ; -} - -/** Perform SOCKS 5 authentication */ -static void -test_socks_5_no_authenticate(void *ptr) -{ - SOCKS_TEST_INIT(); - - /*SOCKS 5 No Authentication */ - ADD_DATA(buf,"\x05\x01\x00"); - test_assert(!fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks)); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(SOCKS_NO_AUTH, socks->reply[1]); - - test_eq(0, buf_datalen(buf)); - - /*SOCKS 5 Send username/password anyway - pretend to be broken */ - ADD_DATA(buf,"\x01\x02\x01\x01\x02\x01\x01"); - test_assert(!fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks)); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(1, socks->reply[0]); - test_eq(0, socks->reply[1]); - - test_eq(2, socks->usernamelen); - test_eq(2, socks->passwordlen); - - test_memeq("\x01\x01", socks->username, 2); - test_memeq("\x01\x01", socks->password, 2); - - done: - ; -} - -/** Perform SOCKS 5 authentication */ -static void -test_socks_5_authenticate(void *ptr) -{ - SOCKS_TEST_INIT(); - - /* SOCKS 5 Negotiate username/password authentication */ - ADD_DATA(buf, "\x05\x01\x02"); - - test_assert(!fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks)); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(SOCKS_USER_PASS, socks->reply[1]); - test_eq(5, socks->socks_version); - - test_eq(0, buf_datalen(buf)); - - /* SOCKS 5 Send username/password */ - ADD_DATA(buf, "\x01\x02me\x08mypasswd"); - test_assert(!fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks)); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(1, socks->reply[0]); - test_eq(0, socks->reply[1]); - - test_eq(2, socks->usernamelen); - test_eq(8, socks->passwordlen); - - test_memeq("me", socks->username, 2); - test_memeq("mypasswd", socks->password, 8); - - done: - ; -} - -/** Perform SOCKS 5 authentication and send data all in one go */ -static void -test_socks_5_authenticate_with_data(void *ptr) -{ - SOCKS_TEST_INIT(); - - /* SOCKS 5 Negotiate username/password authentication */ - ADD_DATA(buf, "\x05\x01\x02"); - - test_assert(!fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks)); - test_eq(2, socks->replylen); - test_eq(5, socks->reply[0]); - test_eq(SOCKS_USER_PASS, socks->reply[1]); - test_eq(5, socks->socks_version); - - test_eq(0, buf_datalen(buf)); - - /* SOCKS 5 Send username/password */ - /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ - ADD_DATA(buf, "\x01\x02me\x03you\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"); - test_assert(fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks) == 1); - test_eq(5, socks->socks_version); - test_eq(2, socks->replylen); - test_eq(1, socks->reply[0]); - test_eq(0, socks->reply[1]); - - test_streq("2.2.2.2", socks->address); - test_eq(4369, socks->port); - - test_eq(2, socks->usernamelen); - test_eq(3, socks->passwordlen); - test_memeq("me", socks->username, 2); - test_memeq("you", socks->password, 3); - - done: - ; -} - -/** Perform SOCKS 5 authentication before method negotiated */ -static void -test_socks_5_auth_before_negotiation(void *ptr) -{ - SOCKS_TEST_INIT(); - - /* SOCKS 5 Send username/password */ - ADD_DATA(buf, "\x01\x02me\x02me"); - test_assert(fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks) == -1); - test_eq(0, socks->socks_version); - test_eq(0, socks->replylen); - test_eq(0, socks->reply[0]); - test_eq(0, socks->reply[1]); - - done: - ; -} - +/** Run unit tests for the onion handshake code. */ static void -test_buffer_copy(void *arg) +test_onion_handshake(void) { - generic_buffer_t *buf=NULL, *buf2=NULL; - const char *s; - size_t len; - char b[256]; + /* client-side */ + crypto_dh_t *c_dh = NULL; + char c_buf[TAP_ONIONSKIN_CHALLENGE_LEN]; + char c_keys[40]; + /* server-side */ + char s_buf[TAP_ONIONSKIN_REPLY_LEN]; + char s_keys[40]; int i; - (void)arg; - - buf = generic_buffer_new(); - tt_assert(buf); - - /* Copy an empty buffer. */ - tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); - tt_assert(buf2); - tt_int_op(0, ==, generic_buffer_len(buf2)); - - /* Now try with a short buffer. */ - s = "And now comes an act of enormous enormance!"; - len = strlen(s); - generic_buffer_add(buf, s, len); - tt_int_op(len, ==, generic_buffer_len(buf)); - /* Add junk to buf2 so we can test replacing.*/ - generic_buffer_add(buf2, "BLARG", 5); - tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); - tt_int_op(len, ==, generic_buffer_len(buf2)); - generic_buffer_get(buf2, b, len); - test_mem_op(b, ==, s, len); - /* Now free buf2 and retry so we can test allocating */ - generic_buffer_free(buf2); - buf2 = NULL; - tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); - tt_int_op(len, ==, generic_buffer_len(buf2)); - generic_buffer_get(buf2, b, len); - test_mem_op(b, ==, s, len); - /* Clear buf for next test */ - generic_buffer_get(buf, b, len); - tt_int_op(generic_buffer_len(buf),==,0); - - /* Okay, now let's try a bigger buffer. */ - s = "Quis autem vel eum iure reprehenderit qui in ea voluptate velit " - "esse quam nihil molestiae consequatur, vel illum qui dolorem eum " - "fugiat quo voluptas nulla pariatur?"; - len = strlen(s); - for (i = 0; i < 256; ++i) { - b[0]=i; - generic_buffer_add(buf, b, 1); - generic_buffer_add(buf, s, len); - } - tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); - tt_int_op(generic_buffer_len(buf2), ==, generic_buffer_len(buf)); - for (i = 0; i < 256; ++i) { - generic_buffer_get(buf2, b, len+1); - tt_int_op((unsigned char)b[0],==,i); - test_mem_op(b+1, ==, s, len); - } - - done: - if (buf) - generic_buffer_free(buf); - if (buf2) - generic_buffer_free(buf2); -} - -/** Run unit tests for buffers.c */ -static void -test_buffers(void) -{ - char str[256]; - char str2[256]; - - buf_t *buf = NULL, *buf2 = NULL; - const char *cp; + /* shared */ + crypto_pk_t *pk = NULL, *pk2 = NULL; - int j; - size_t r; + pk = pk_generate(0); + pk2 = pk_generate(1); - /**** - * buf_new - ****/ - if (!(buf = buf_new())) - test_fail(); + /* client handshake 1. */ + memset(c_buf, 0, TAP_ONIONSKIN_CHALLENGE_LEN); + test_assert(! onion_skin_TAP_create(pk, &c_dh, c_buf)); - //test_eq(buf_capacity(buf), 4096); - test_eq(buf_datalen(buf), 0); + for (i = 1; i <= 3; ++i) { + crypto_pk_t *k1, *k2; + if (i==1) { + /* server handshake: only one key known. */ + k1 = pk; k2 = NULL; + } else if (i==2) { + /* server handshake: try the right key first. */ + k1 = pk; k2 = pk2; + } else { + /* server handshake: try the right key second. */ + k1 = pk2; k2 = pk; + } - /**** - * General pointer frobbing - */ - for (j=0;j<256;++j) { - str[j] = (char)j; - } - write_to_buf(str, 256, buf); - write_to_buf(str, 256, buf); - test_eq(buf_datalen(buf), 512); - fetch_from_buf(str2, 200, buf); - test_memeq(str, str2, 200); - test_eq(buf_datalen(buf), 312); - memset(str2, 0, sizeof(str2)); - - fetch_from_buf(str2, 256, buf); - test_memeq(str+200, str2, 56); - test_memeq(str, str2+56, 200); - test_eq(buf_datalen(buf), 56); - memset(str2, 0, sizeof(str2)); - /* Okay, now we should be 512 bytes into the 4096-byte buffer. If we add - * another 3584 bytes, we hit the end. */ - for (j=0;j<15;++j) { - write_to_buf(str, 256, buf); - } - assert_buf_ok(buf); - test_eq(buf_datalen(buf), 3896); - fetch_from_buf(str2, 56, buf); - test_eq(buf_datalen(buf), 3840); - test_memeq(str+200, str2, 56); - for (j=0;j<15;++j) { - memset(str2, 0, sizeof(str2)); - fetch_from_buf(str2, 256, buf); - test_memeq(str, str2, 256); - } - test_eq(buf_datalen(buf), 0); - buf_free(buf); - buf = NULL; - - /* Okay, now make sure growing can work. */ - buf = buf_new_with_capacity(16); - //test_eq(buf_capacity(buf), 16); - write_to_buf(str+1, 255, buf); - //test_eq(buf_capacity(buf), 256); - fetch_from_buf(str2, 254, buf); - test_memeq(str+1, str2, 254); - //test_eq(buf_capacity(buf), 256); - assert_buf_ok(buf); - write_to_buf(str, 32, buf); - //test_eq(buf_capacity(buf), 256); - assert_buf_ok(buf); - write_to_buf(str, 256, buf); - assert_buf_ok(buf); - //test_eq(buf_capacity(buf), 512); - test_eq(buf_datalen(buf), 33+256); - fetch_from_buf(str2, 33, buf); - test_eq(*str2, str[255]); - - test_memeq(str2+1, str, 32); - //test_eq(buf_capacity(buf), 512); - test_eq(buf_datalen(buf), 256); - fetch_from_buf(str2, 256, buf); - test_memeq(str, str2, 256); - - /* now try shrinking: case 1. */ - buf_free(buf); - buf = buf_new_with_capacity(33668); - for (j=0;j<67;++j) { - write_to_buf(str,255, buf); - } - //test_eq(buf_capacity(buf), 33668); - test_eq(buf_datalen(buf), 17085); - for (j=0; j < 40; ++j) { - fetch_from_buf(str2, 255,buf); - test_memeq(str2, str, 255); - } + memset(s_buf, 0, TAP_ONIONSKIN_REPLY_LEN); + memset(s_keys, 0, 40); + test_assert(! onion_skin_TAP_server_handshake(c_buf, k1, k2, + s_buf, s_keys, 40)); - /* now try shrinking: case 2. */ - buf_free(buf); - buf = buf_new_with_capacity(33668); - for (j=0;j<67;++j) { - write_to_buf(str,255, buf); - } - for (j=0; j < 20; ++j) { - fetch_from_buf(str2, 255,buf); - test_memeq(str2, str, 255); - } - for (j=0;j<80;++j) { - write_to_buf(str,255, buf); - } - //test_eq(buf_capacity(buf),33668); - for (j=0; j < 120; ++j) { - fetch_from_buf(str2, 255,buf); - test_memeq(str2, str, 255); - } + /* client handshake 2 */ + memset(c_keys, 0, 40); + test_assert(! onion_skin_TAP_client_handshake(c_dh, s_buf, c_keys, 40)); - /* Move from buf to buf. */ - buf_free(buf); - buf = buf_new_with_capacity(4096); - buf2 = buf_new_with_capacity(4096); - for (j=0;j<100;++j) - write_to_buf(str, 255, buf); - test_eq(buf_datalen(buf), 25500); - for (j=0;j<100;++j) { - r = 10; - move_buf_to_buf(buf2, buf, &r); - test_eq(r, 0); - } - test_eq(buf_datalen(buf), 24500); - test_eq(buf_datalen(buf2), 1000); - for (j=0;j<3;++j) { - fetch_from_buf(str2, 255, buf2); - test_memeq(str2, str, 255); - } - r = 8192; /*big move*/ - move_buf_to_buf(buf2, buf, &r); - test_eq(r, 0); - r = 30000; /* incomplete move */ - move_buf_to_buf(buf2, buf, &r); - test_eq(r, 13692); - for (j=0;j<97;++j) { - fetch_from_buf(str2, 255, buf2); - test_memeq(str2, str, 255); + test_memeq(c_keys, s_keys, 40); + memset(s_buf, 0, 40); + test_memneq(c_keys, s_buf, 40); } - buf_free(buf); - buf_free(buf2); - buf = buf2 = NULL; - - buf = buf_new_with_capacity(5); - cp = "Testing. This is a moderately long Testing string."; - for (j = 0; cp[j]; j++) - write_to_buf(cp+j, 1, buf); - test_eq(0, buf_find_string_offset(buf, "Testing", 7)); - test_eq(1, buf_find_string_offset(buf, "esting", 6)); - test_eq(1, buf_find_string_offset(buf, "est", 3)); - test_eq(39, buf_find_string_offset(buf, "ing str", 7)); - test_eq(35, buf_find_string_offset(buf, "Testing str", 11)); - test_eq(32, buf_find_string_offset(buf, "ng ", 3)); - test_eq(43, buf_find_string_offset(buf, "string.", 7)); - test_eq(-1, buf_find_string_offset(buf, "shrdlu", 6)); - test_eq(-1, buf_find_string_offset(buf, "Testing thing", 13)); - test_eq(-1, buf_find_string_offset(buf, "ngx", 3)); - buf_free(buf); - buf = NULL; - - /* Try adding a string too long for any freelist. */ - { - char *cp = tor_malloc_zero(65536); - buf = buf_new(); - write_to_buf(cp, 65536, buf); - tor_free(cp); - - tt_int_op(buf_datalen(buf), ==, 65536); - buf_free(buf); - buf = NULL; - } - done: - if (buf) - buf_free(buf); - if (buf2) - buf_free(buf2); + crypto_dh_free(c_dh); + crypto_pk_free(pk); + crypto_pk_free(pk2); } -/** Run unit tests for the onion handshake code. */ static void -test_onion_handshake(void) +test_bad_onion_handshake(void *arg) { + char junk_buf[TAP_ONIONSKIN_CHALLENGE_LEN]; + char junk_buf2[TAP_ONIONSKIN_CHALLENGE_LEN]; /* client-side */ crypto_dh_t *c_dh = NULL; char c_buf[TAP_ONIONSKIN_CHALLENGE_LEN]; char c_keys[40]; - /* server-side */ char s_buf[TAP_ONIONSKIN_REPLY_LEN]; char s_keys[40]; - /* shared */ - crypto_pk_t *pk = NULL; + crypto_pk_t *pk = NULL, *pk2 = NULL; + + (void)arg; pk = pk_generate(0); + pk2 = pk_generate(1); - /* client handshake 1. */ + /* Server: Case 1: the encrypted data is degenerate. */ + memset(junk_buf, 0, sizeof(junk_buf)); + crypto_pk_public_hybrid_encrypt(pk, junk_buf2, TAP_ONIONSKIN_CHALLENGE_LEN, + junk_buf, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1); + tt_int_op(-1, ==, + onion_skin_TAP_server_handshake(junk_buf2, pk, NULL, + s_buf, s_keys, 40)); + + /* Server: Case 2: the encrypted data is not long enough. */ + memset(junk_buf, 0, sizeof(junk_buf)); + memset(junk_buf2, 0, sizeof(junk_buf2)); + crypto_pk_public_encrypt(pk, junk_buf2, sizeof(junk_buf2), + junk_buf, 48, PK_PKCS1_OAEP_PADDING); + tt_int_op(-1, ==, + onion_skin_TAP_server_handshake(junk_buf2, pk, NULL, + s_buf, s_keys, 40)); + + /* client handshake 1: do it straight. */ memset(c_buf, 0, TAP_ONIONSKIN_CHALLENGE_LEN); test_assert(! onion_skin_TAP_create(pk, &c_dh, c_buf)); - /* server handshake */ - memset(s_buf, 0, TAP_ONIONSKIN_REPLY_LEN); - memset(s_keys, 0, 40); - test_assert(! onion_skin_TAP_server_handshake(c_buf, pk, NULL, + /* Server: Case 3: we just don't have the right key. */ + tt_int_op(-1, ==, + onion_skin_TAP_server_handshake(c_buf, pk2, NULL, s_buf, s_keys, 40)); - /* client handshake 2 */ - memset(c_keys, 0, 40); - test_assert(! onion_skin_TAP_client_handshake(c_dh, s_buf, c_keys, 40)); + /* Server: Case 4: The RSA-encrypted portion is corrupt. */ + c_buf[64] ^= 33; + tt_int_op(-1, ==, + onion_skin_TAP_server_handshake(c_buf, pk, NULL, + s_buf, s_keys, 40)); + c_buf[64] ^= 33; - if (memcmp(c_keys, s_keys, 40)) { - puts("Aiiiie"); - exit(1); - } - test_memeq(c_keys, s_keys, 40); - memset(s_buf, 0, 40); - test_memneq(c_keys, s_buf, 40); + /* (Let the server procede) */ + tt_int_op(0, ==, + onion_skin_TAP_server_handshake(c_buf, pk, NULL, + s_buf, s_keys, 40)); + + /* Client: Case 1: The server sent back junk. */ + s_buf[64] ^= 33; + tt_int_op(-1, ==, + onion_skin_TAP_client_handshake(c_dh, s_buf, c_keys, 40)); + s_buf[64] ^= 33; + + /* Let the client finish; make sure it can. */ + tt_int_op(0, ==, + onion_skin_TAP_client_handshake(c_dh, s_buf, c_keys, 40)); + test_memeq(s_keys, c_keys, 40); + + /* Client: Case 2: The server sent back a degenerate DH. */ + memset(s_buf, 0, sizeof(s_buf)); + tt_int_op(-1, ==, + onion_skin_TAP_client_handshake(c_dh, s_buf, c_keys, 40)); done: - if (c_dh) - crypto_dh_free(c_dh); - if (pk) - crypto_pk_free(pk); + crypto_dh_free(c_dh); + crypto_pk_free(pk); + crypto_pk_free(pk2); } #ifdef CURVE25519_ENABLED @@ -956,10 +427,12 @@ test_onion_queues(void) test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_pending_add(circ1, create1)); + create1 = NULL; test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); test_eq(0, onion_pending_add(circ2, create2)); + create2 = NULL; test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); test_eq_ptr(circ2, onion_next_task(&onionskin)); @@ -971,11 +444,10 @@ test_onion_queues(void) test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); done: - ; -// circuit_free(circ1); -// circuit_free(circ2); - /* and free create1 and create2 */ - /* XXX leaks everything here */ + circuit_free(TO_CIRCUIT(circ1)); + circuit_free(TO_CIRCUIT(circ2)); + tor_free(create1); + tor_free(create2); } static void @@ -1579,6 +1051,34 @@ test_rend_fns(void) tor_free(intro_points_encrypted); } + /* Record odd numbered fake-IPs using ipv6, even numbered fake-IPs + * using ipv4. Since our fake geoip database is the same between + * ipv4 and ipv6, we should get the same result no matter which + * address family we pick for each IP. */ +#define SET_TEST_ADDRESS(i) do { \ + if ((i) & 1) { \ + SET_TEST_IPV6(i); \ + tor_addr_from_in6(&addr, &in6); \ + } else { \ + tor_addr_from_ipv4h(&addr, (uint32_t) i); \ + } \ + } while (0) + + /* Make sure that country ID actually works. */ +#define SET_TEST_IPV6(i) \ + do { \ + set_uint32(in6.s6_addr + 12, htonl((uint32_t) (i))); \ + } while (0) +#define CHECK_COUNTRY(country, val) do { \ + /* test ipv4 country lookup */ \ + test_streq(country, \ + geoip_get_country_name(geoip_get_country_by_ipv4(val))); \ + /* test ipv6 country lookup */ \ + SET_TEST_IPV6(val); \ + test_streq(country, \ + geoip_get_country_name(geoip_get_country_by_ipv6(&in6))); \ + } while (0) + /** Run unit tests for GeoIP code. */ static void test_geoip(void) @@ -1589,7 +1089,8 @@ test_geoip(void) const char *bridge_stats_1 = "bridge-stats-end 2010-08-12 13:27:30 (86400 s)\n" "bridge-ips zz=24,xy=8\n" - "bridge-ip-versions v4=16,v6=16\n", + "bridge-ip-versions v4=16,v6=16\n" + "bridge-ip-transports <OR>=24\n", *dirreq_stats_1 = "dirreq-stats-end 2010-08-12 13:27:30 (86400 s)\n" "dirreq-v3-ips ab=8\n" @@ -1653,21 +1154,6 @@ test_geoip(void) test_eq(4, geoip_get_n_countries()); memset(&in6, 0, sizeof(in6)); - /* Make sure that country ID actually works. */ -#define SET_TEST_IPV6(i) \ - do { \ - set_uint32(in6.s6_addr + 12, htonl((uint32_t) (i))); \ - } while (0) -#define CHECK_COUNTRY(country, val) do { \ - /* test ipv4 country lookup */ \ - test_streq(country, \ - geoip_get_country_name(geoip_get_country_by_ipv4(val))); \ - /* test ipv6 country lookup */ \ - SET_TEST_IPV6(val); \ - test_streq(country, \ - geoip_get_country_name(geoip_get_country_by_ipv6(&in6))); \ - } while (0) - CHECK_COUNTRY("??", 3); CHECK_COUNTRY("ab", 32); CHECK_COUNTRY("??", 5); @@ -1680,40 +1166,25 @@ test_geoip(void) SET_TEST_IPV6(3); test_eq(0, geoip_get_country_by_ipv6(&in6)); -#undef CHECK_COUNTRY - - /* Record odd numbered fake-IPs using ipv6, even numbered fake-IPs - * using ipv4. Since our fake geoip database is the same between - * ipv4 and ipv6, we should get the same result no matter which - * address family we pick for each IP. */ -#define SET_TEST_ADDRESS(i) do { \ - if ((i) & 1) { \ - SET_TEST_IPV6(i); \ - tor_addr_from_in6(&addr, &in6); \ - } else { \ - tor_addr_from_ipv4h(&addr, (uint32_t) i); \ - } \ - } while (0) - get_options_mutable()->BridgeRelay = 1; get_options_mutable()->BridgeRecordUsageByCountry = 1; /* Put 9 observations in AB... */ for (i=32; i < 40; ++i) { SET_TEST_ADDRESS(i); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now-7200); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now-7200); } SET_TEST_ADDRESS(225); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now-7200); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now-7200); /* and 3 observations in XY, several times. */ for (j=0; j < 10; ++j) for (i=52; i < 55; ++i) { SET_TEST_ADDRESS(i); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now-3600); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now-3600); } /* and 17 observations in ZZ... */ for (i=110; i < 127; ++i) { SET_TEST_ADDRESS(i); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now); } geoip_get_client_history(GEOIP_CLIENT_CONNECT, &s, &v); test_assert(s); @@ -1762,7 +1233,7 @@ test_geoip(void) /* Start testing dirreq statistics by making sure that we don't collect * dirreq stats without initializing them. */ SET_TEST_ADDRESS(100); - geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, NULL, now); s = geoip_format_dirreq_stats(now + 86400); test_assert(!s); @@ -1770,7 +1241,7 @@ test_geoip(void) * dirreq-stats history string. */ geoip_dirreq_stats_init(now); SET_TEST_ADDRESS(100); - geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, NULL, now); s = geoip_format_dirreq_stats(now + 86400); test_streq(dirreq_stats_1, s); tor_free(s); @@ -1779,7 +1250,7 @@ test_geoip(void) * don't generate a history string. */ geoip_dirreq_stats_term(); SET_TEST_ADDRESS(101); - geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, NULL, now); s = geoip_format_dirreq_stats(now + 86400); test_assert(!s); @@ -1787,7 +1258,7 @@ test_geoip(void) * that we get an all empty history string. */ geoip_dirreq_stats_init(now); SET_TEST_ADDRESS(100); - geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, NULL, now); geoip_reset_dirreq_stats(now); s = geoip_format_dirreq_stats(now + 86400); test_streq(dirreq_stats_2, s); @@ -1814,7 +1285,7 @@ test_geoip(void) /* Start testing entry statistics by making sure that we don't collect * anything without initializing entry stats. */ SET_TEST_ADDRESS(100); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now); s = geoip_format_entry_stats(now + 86400); test_assert(!s); @@ -1822,7 +1293,7 @@ test_geoip(void) * entry-stats history string. */ geoip_entry_stats_init(now); SET_TEST_ADDRESS(100); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now); s = geoip_format_entry_stats(now + 86400); test_streq(entry_stats_1, s); tor_free(s); @@ -1831,7 +1302,7 @@ test_geoip(void) * don't generate a history string. */ geoip_entry_stats_term(); SET_TEST_ADDRESS(101); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now); s = geoip_format_entry_stats(now + 86400); test_assert(!s); @@ -1839,15 +1310,12 @@ test_geoip(void) * that we get an all empty history string. */ geoip_entry_stats_init(now); SET_TEST_ADDRESS(100); - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, now); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now); geoip_reset_entry_stats(now); s = geoip_format_entry_stats(now + 86400); test_streq(entry_stats_2, s); tor_free(s); -#undef SET_TEST_ADDRESS -#undef SET_TEST_IPV6 - /* Stop collecting entry statistics. */ geoip_entry_stats_term(); get_options_mutable()->EntryStatistics = 0; @@ -1857,6 +1325,79 @@ test_geoip(void) tor_free(v); } +static void +test_geoip_with_pt(void) +{ + time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */ + char *s = NULL; + int i; + tor_addr_t addr; + struct in6_addr in6; + + get_options_mutable()->BridgeRelay = 1; + get_options_mutable()->BridgeRecordUsageByCountry = 1; + + /* No clients seen yet. */ + s = geoip_get_transport_history(); + tor_assert(!s); + + /* 4 connections without a pluggable transport */ + for (i=0; i < 4; ++i) { + SET_TEST_ADDRESS(i); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now-7200); + } + + /* 9 connections with "alpha" */ + for (i=4; i < 13; ++i) { + SET_TEST_ADDRESS(i); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, "alpha", now-7200); + } + + /* one connection with "beta" */ + SET_TEST_ADDRESS(13); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, "beta", now-7200); + + /* 14 connections with "charlie" */ + for (i=14; i < 28; ++i) { + SET_TEST_ADDRESS(i); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, "charlie", now-7200); + } + + /* 131 connections with "ddr" */ + for (i=28; i < 159; ++i) { + SET_TEST_ADDRESS(i); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, "ddr", now-7200); + } + + /* 8 connections with "entropy" */ + for (i=159; i < 167; ++i) { + SET_TEST_ADDRESS(i); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, "entropy", now-7200); + } + + /* 2 connections from the same IP with two different transports. */ + SET_TEST_ADDRESS(++i); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, "fire", now-7200); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, "google", now-7200); + + /* Test the transport history string. */ + s = geoip_get_transport_history(); + tor_assert(s); + test_streq(s, "<OR>=8,alpha=16,beta=8,charlie=16,ddr=136," + "entropy=8,fire=8,google=8"); + + /* Stop collecting entry statistics. */ + geoip_entry_stats_term(); + get_options_mutable()->EntryStatistics = 0; + + done: + tor_free(s); +} + +#undef SET_TEST_ADDRESS +#undef SET_TEST_IPV6 +#undef CHECK_COUNTRY + /** Run unit tests for stats code. */ static void test_stats(void) @@ -2047,9 +1588,8 @@ const struct testcase_setup_t legacy_setup = { { #name, legacy_test_helper, TT_FORK, &legacy_setup, test_ ## name } static struct testcase_t test_array[] = { - ENT(buffers), - { "buffer_copy", test_buffer_copy, 0, NULL, NULL }, ENT(onion_handshake), + { "bad_onion_handshake", test_bad_onion_handshake, 0, NULL, NULL }, ENT(onion_queues), #ifdef CURVE25519_ENABLED { "ntor_handshake", test_ntor_handshake, 0, NULL, NULL }, @@ -2058,29 +1598,14 @@ static struct testcase_t test_array[] = { ENT(policies), ENT(rend_fns), ENT(geoip), + FORK(geoip_with_pt), FORK(stats), END_OF_TESTCASES }; -#define SOCKSENT(name) \ - { #name, test_socks_##name, TT_FORK, &socks_setup, NULL } - -static struct testcase_t socks_tests[] = { - SOCKSENT(4_unsupported_commands), - SOCKSENT(4_supported_commands), - - SOCKSENT(5_unsupported_commands), - SOCKSENT(5_supported_commands), - SOCKSENT(5_no_authenticate), - SOCKSENT(5_auth_before_negotiation), - SOCKSENT(5_authenticate), - SOCKSENT(5_authenticate_with_data), - - END_OF_TESTCASES -}; - extern struct testcase_t addr_tests[]; +extern struct testcase_t buffer_tests[]; extern struct testcase_t crypto_tests[]; extern struct testcase_t container_tests[]; extern struct testcase_t util_tests[]; @@ -2091,21 +1616,38 @@ extern struct testcase_t config_tests[]; extern struct testcase_t introduce_tests[]; extern struct testcase_t replaycache_tests[]; extern struct testcase_t cell_format_tests[]; +extern struct testcase_t circuitlist_tests[]; +extern struct testcase_t circuitmux_tests[]; +extern struct testcase_t cell_queue_tests[]; +extern struct testcase_t options_tests[]; +extern struct testcase_t socks_tests[]; +extern struct testcase_t extorport_tests[]; +extern struct testcase_t controller_event_tests[]; +extern struct testcase_t logging_tests[]; +extern struct testcase_t backtrace_tests[]; static struct testgroup_t testgroups[] = { { "", test_array }, + { "buffer/", buffer_tests }, { "socks/", socks_tests }, { "addr/", addr_tests }, { "crypto/", crypto_tests }, { "container/", container_tests }, { "util/", util_tests }, + { "util/logging/", logging_tests }, { "cellfmt/", cell_format_tests }, + { "cellqueue/", cell_queue_tests }, { "dir/", dir_tests }, { "dir/md/", microdesc_tests }, { "pt/", pt_tests }, { "config/", config_tests }, { "replaycache/", replaycache_tests }, { "introduce/", introduce_tests }, + { "circuitlist/", circuitlist_tests }, + { "circuitmux/", circuitmux_tests }, + { "options/", options_tests }, + { "extorport/", extorport_tests }, + { "control/", controller_event_tests }, END_OF_GROUPS }; diff --git a/src/test/test_addr.c b/src/test/test_addr.c index fec85a469..4bc602df8 100644 --- a/src/test/test_addr.c +++ b/src/test/test_addr.c @@ -44,6 +44,10 @@ test_addr_basic(void) test_eq(u32, 0x7f000001u); test_eq(u16, 0); tor_free(cp); + + test_assert(addr_port_lookup(LOG_WARN, "localhost:3", &cp, &u32, NULL)); + tor_free(cp); + test_eq(0, addr_mask_get_bits(0x0u)); test_eq(32, addr_mask_get_bits(0xFFFFFFFFu)); test_eq(16, addr_mask_get_bits(0xFFFF0000u)); @@ -217,11 +221,12 @@ test_addr_ip6_helpers(void) /* ==== Converting to and from sockaddr_t. */ sin = (struct sockaddr_in *)&sa_storage; sin->sin_family = AF_INET; - sin->sin_port = 9090; + sin->sin_port = htons(9090); sin->sin_addr.s_addr = htonl(0x7f7f0102); /*127.127.1.2*/ - tor_addr_from_sockaddr(&t1, (struct sockaddr *)sin, NULL); + tor_addr_from_sockaddr(&t1, (struct sockaddr *)sin, &port1); test_eq(tor_addr_family(&t1), AF_INET); test_eq(tor_addr_to_ipv4h(&t1), 0x7f7f0102); + tt_int_op(port1, ==, 9090); memset(&sa_storage, 0, sizeof(sa_storage)); test_eq(sizeof(struct sockaddr_in), @@ -235,8 +240,9 @@ test_addr_ip6_helpers(void) sin6->sin6_family = AF_INET6; sin6->sin6_port = htons(7070); sin6->sin6_addr.s6_addr[0] = 128; - tor_addr_from_sockaddr(&t1, (struct sockaddr *)sin6, NULL); + tor_addr_from_sockaddr(&t1, (struct sockaddr *)sin6, &port1); test_eq(tor_addr_family(&t1), AF_INET6); + tt_int_op(port1, ==, 7070); p1 = tor_addr_to_str(buf, &t1, sizeof(buf), 0); test_streq(p1, "8000::"); @@ -464,6 +470,9 @@ test_addr_ip6_helpers(void) test_eq(0, i); i = tor_addr_parse_PTR_name(&t1, "Foobar.baz", AF_UNSPEC, 1); test_eq(0, i); + i = tor_addr_parse_PTR_name(&t1, "9999999999999999999999999999.in-addr.arpa", + AF_UNSPEC, 1); + test_eq(-1, i); i = tor_addr_parse_PTR_name(&t1, "1.0.168.192.in-addr.arpa", AF_UNSPEC, 1); test_eq(1, i); @@ -844,6 +853,90 @@ test_virtaddrmap(void *data) } static void +test_addr_localname(void *arg) +{ + (void)arg; + tt_assert(tor_addr_hostname_is_local("localhost")); + tt_assert(tor_addr_hostname_is_local("LOCALHOST")); + tt_assert(tor_addr_hostname_is_local("LocalHost")); + tt_assert(tor_addr_hostname_is_local("local")); + tt_assert(tor_addr_hostname_is_local("LOCAL")); + tt_assert(tor_addr_hostname_is_local("here.now.local")); + tt_assert(tor_addr_hostname_is_local("here.now.LOCAL")); + + tt_assert(!tor_addr_hostname_is_local(" localhost")); + tt_assert(!tor_addr_hostname_is_local("www.torproject.org")); + done: + ; +} + +static void +test_addr_dup_ip(void *arg) +{ + char *v = NULL; + (void)arg; +#define CHECK(ip, s) do { \ + v = tor_dup_ip(ip); \ + tt_str_op(v,==,(s)); \ + tor_free(v); \ + } while (0) + + CHECK(0xffffffff, "255.255.255.255"); + CHECK(0x00000000, "0.0.0.0"); + CHECK(0x7f000001, "127.0.0.1"); + CHECK(0x01020304, "1.2.3.4"); + +#undef CHECK + done: + tor_free(v); +} + +static void +test_addr_sockaddr_to_str(void *arg) +{ + char *v = NULL; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr_storage ss; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un sun; +#endif +#define CHECK(sa, s) do { \ + v = tor_sockaddr_to_str((const struct sockaddr*) &(sa)); \ + tt_str_op(v,==,(s)); \ + tor_free(v); \ + } while (0) + (void)arg; + + memset(&ss,0,sizeof(ss)); + ss.ss_family = AF_UNSPEC; + CHECK(ss, "unspec"); + + memset(&sin,0,sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f808001); + sin.sin_port = htons(1234); + CHECK(sin, "127.128.128.1:1234"); + +#ifdef HAVE_SYS_UN_H + memset(&sun,0,sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, "/here/is/a/path", sizeof(sun.sun_path)); + CHECK(sun, "unix:/here/is/a/path"); +#endif + + memset(&sin6,0,sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(sin6.sin6_addr.s6_addr, "\x20\x00\x00\x00\x00\x00\x00\x00" + "\x00\x1a\x2b\x3c\x4d\x5e\x00\x01", 16); + sin6.sin6_port = htons(1234); + CHECK(sin6, "[2000::1a:2b3c:4d5e:1]:1234"); + + done: + tor_free(v); +} + +static void test_addr_is_loopback(void *data) { static const struct loopback_item { @@ -886,6 +979,9 @@ struct testcase_t addr_tests[] = { ADDR_LEGACY(ip6_helpers), ADDR_LEGACY(parse), { "virtaddr", test_virtaddrmap, 0, NULL, NULL }, + { "localname", test_addr_localname, 0, NULL, NULL }, + { "dup_ip", test_addr_dup_ip, 0, NULL, NULL }, + { "sockaddr_to_str", test_addr_sockaddr_to_str, 0, NULL, NULL }, { "is_loopback", test_addr_is_loopback, 0, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_bt_cl.c b/src/test/test_bt_cl.c new file mode 100644 index 000000000..45ae82fb8 --- /dev/null +++ b/src/test/test_bt_cl.c @@ -0,0 +1,109 @@ +/* Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include <stdio.h> +#include <stdlib.h> + +#include "or.h" +#include "util.h" +#include "backtrace.h" +#include "torlog.h" + +/* -1: no crash. + * 0: crash with a segmentation fault. + * 1x: crash with an assertion failure. */ +static int crashtype = 0; + +#ifdef __GNUC__ +#define NOINLINE __attribute__((noinline)) +#define NORETURN __attribute__((noreturn)) +#endif + +int crash(int x) NOINLINE; +int oh_what(int x) NOINLINE; +int a_tangled_web(int x) NOINLINE; +int we_weave(int x) NOINLINE; +static void abort_handler(int s) NORETURN; + +int +crash(int x) +{ + if (crashtype == 0) { + *(volatile int *)0 = 0; + } else if (crashtype == 1) { + tor_assert(1 == 0); + } else if (crashtype == -1) { + ; + } + + crashtype *= x; + return crashtype; +} + +int +oh_what(int x) +{ + /* We call crash() twice here, so that the compiler won't try to do a + * tail-call optimization. Only the first call will actually happen, but + * telling the compiler to maybe do the second call will prevent it from + * replacing the first call with a jump. */ + return crash(x) + crash(x*2); +} + +int +a_tangled_web(int x) +{ + return oh_what(x) * 99 + oh_what(x); +} + +int +we_weave(int x) +{ + return a_tangled_web(x) + a_tangled_web(x+1); +} + +static void +abort_handler(int s) +{ + (void)s; + exit(0); +} + +int +main(int argc, char **argv) +{ + log_severity_list_t severity; + + if (argc < 2) { + puts("I take an argument. It should be \"assert\" or \"crash\" or " + "\"none\""); + return 1; + } + if (!strcmp(argv[1], "assert")) { + crashtype = 1; + } else if (!strcmp(argv[1], "crash")) { + crashtype = 0; + } else if (!strcmp(argv[1], "none")) { + crashtype = -1; + } else { + puts("Argument should be \"assert\" or \"crash\" or \"none\""); + return 1; + } + + init_logging(); + set_log_severity_config(LOG_WARN, LOG_ERR, &severity); + add_stream_log(&severity, "stdout", STDOUT_FILENO); + tor_log_update_sigsafe_err_fds(); + + configure_backtrace_handler(NULL); + + signal(SIGABRT, abort_handler); + + printf("%d\n", we_weave(2)); + + clean_up_backtrace_handler(); + + return 0; +} + diff --git a/src/test/test_buffers.c b/src/test/test_buffers.c new file mode 100644 index 000000000..a009faa0b --- /dev/null +++ b/src/test/test_buffers.c @@ -0,0 +1,342 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define BUFFERS_PRIVATE +#include "or.h" +#include "buffers.h" +#include "ext_orport.h" +#include "test.h" + +/** Run unit tests for buffers.c */ +static void +test_buffers_basic(void *arg) +{ + char str[256]; + char str2[256]; + + buf_t *buf = NULL, *buf2 = NULL; + const char *cp; + + int j; + size_t r; + (void) arg; + + /**** + * buf_new + ****/ + if (!(buf = buf_new())) + test_fail(); + + //test_eq(buf_capacity(buf), 4096); + test_eq(buf_datalen(buf), 0); + + /**** + * General pointer frobbing + */ + for (j=0;j<256;++j) { + str[j] = (char)j; + } + write_to_buf(str, 256, buf); + write_to_buf(str, 256, buf); + test_eq(buf_datalen(buf), 512); + fetch_from_buf(str2, 200, buf); + test_memeq(str, str2, 200); + test_eq(buf_datalen(buf), 312); + memset(str2, 0, sizeof(str2)); + + fetch_from_buf(str2, 256, buf); + test_memeq(str+200, str2, 56); + test_memeq(str, str2+56, 200); + test_eq(buf_datalen(buf), 56); + memset(str2, 0, sizeof(str2)); + /* Okay, now we should be 512 bytes into the 4096-byte buffer. If we add + * another 3584 bytes, we hit the end. */ + for (j=0;j<15;++j) { + write_to_buf(str, 256, buf); + } + assert_buf_ok(buf); + test_eq(buf_datalen(buf), 3896); + fetch_from_buf(str2, 56, buf); + test_eq(buf_datalen(buf), 3840); + test_memeq(str+200, str2, 56); + for (j=0;j<15;++j) { + memset(str2, 0, sizeof(str2)); + fetch_from_buf(str2, 256, buf); + test_memeq(str, str2, 256); + } + test_eq(buf_datalen(buf), 0); + buf_free(buf); + buf = NULL; + + /* Okay, now make sure growing can work. */ + buf = buf_new_with_capacity(16); + //test_eq(buf_capacity(buf), 16); + write_to_buf(str+1, 255, buf); + //test_eq(buf_capacity(buf), 256); + fetch_from_buf(str2, 254, buf); + test_memeq(str+1, str2, 254); + //test_eq(buf_capacity(buf), 256); + assert_buf_ok(buf); + write_to_buf(str, 32, buf); + //test_eq(buf_capacity(buf), 256); + assert_buf_ok(buf); + write_to_buf(str, 256, buf); + assert_buf_ok(buf); + //test_eq(buf_capacity(buf), 512); + test_eq(buf_datalen(buf), 33+256); + fetch_from_buf(str2, 33, buf); + test_eq(*str2, str[255]); + + test_memeq(str2+1, str, 32); + //test_eq(buf_capacity(buf), 512); + test_eq(buf_datalen(buf), 256); + fetch_from_buf(str2, 256, buf); + test_memeq(str, str2, 256); + + /* now try shrinking: case 1. */ + buf_free(buf); + buf = buf_new_with_capacity(33668); + for (j=0;j<67;++j) { + write_to_buf(str,255, buf); + } + //test_eq(buf_capacity(buf), 33668); + test_eq(buf_datalen(buf), 17085); + for (j=0; j < 40; ++j) { + fetch_from_buf(str2, 255,buf); + test_memeq(str2, str, 255); + } + + /* now try shrinking: case 2. */ + buf_free(buf); + buf = buf_new_with_capacity(33668); + for (j=0;j<67;++j) { + write_to_buf(str,255, buf); + } + for (j=0; j < 20; ++j) { + fetch_from_buf(str2, 255,buf); + test_memeq(str2, str, 255); + } + for (j=0;j<80;++j) { + write_to_buf(str,255, buf); + } + //test_eq(buf_capacity(buf),33668); + for (j=0; j < 120; ++j) { + fetch_from_buf(str2, 255,buf); + test_memeq(str2, str, 255); + } + + /* Move from buf to buf. */ + buf_free(buf); + buf = buf_new_with_capacity(4096); + buf2 = buf_new_with_capacity(4096); + for (j=0;j<100;++j) + write_to_buf(str, 255, buf); + test_eq(buf_datalen(buf), 25500); + for (j=0;j<100;++j) { + r = 10; + move_buf_to_buf(buf2, buf, &r); + test_eq(r, 0); + } + test_eq(buf_datalen(buf), 24500); + test_eq(buf_datalen(buf2), 1000); + for (j=0;j<3;++j) { + fetch_from_buf(str2, 255, buf2); + test_memeq(str2, str, 255); + } + r = 8192; /*big move*/ + move_buf_to_buf(buf2, buf, &r); + test_eq(r, 0); + r = 30000; /* incomplete move */ + move_buf_to_buf(buf2, buf, &r); + test_eq(r, 13692); + for (j=0;j<97;++j) { + fetch_from_buf(str2, 255, buf2); + test_memeq(str2, str, 255); + } + buf_free(buf); + buf_free(buf2); + buf = buf2 = NULL; + + buf = buf_new_with_capacity(5); + cp = "Testing. This is a moderately long Testing string."; + for (j = 0; cp[j]; j++) + write_to_buf(cp+j, 1, buf); + test_eq(0, buf_find_string_offset(buf, "Testing", 7)); + test_eq(1, buf_find_string_offset(buf, "esting", 6)); + test_eq(1, buf_find_string_offset(buf, "est", 3)); + test_eq(39, buf_find_string_offset(buf, "ing str", 7)); + test_eq(35, buf_find_string_offset(buf, "Testing str", 11)); + test_eq(32, buf_find_string_offset(buf, "ng ", 3)); + test_eq(43, buf_find_string_offset(buf, "string.", 7)); + test_eq(-1, buf_find_string_offset(buf, "shrdlu", 6)); + test_eq(-1, buf_find_string_offset(buf, "Testing thing", 13)); + test_eq(-1, buf_find_string_offset(buf, "ngx", 3)); + buf_free(buf); + buf = NULL; + + /* Try adding a string too long for any freelist. */ + { + char *cp = tor_malloc_zero(65536); + buf = buf_new(); + write_to_buf(cp, 65536, buf); + tor_free(cp); + + tt_int_op(buf_datalen(buf), ==, 65536); + buf_free(buf); + buf = NULL; + } + + done: + if (buf) + buf_free(buf); + if (buf2) + buf_free(buf2); +} +static void +test_buffer_copy(void *arg) +{ + generic_buffer_t *buf=NULL, *buf2=NULL; + const char *s; + size_t len; + char b[256]; + int i; + (void)arg; + + buf = generic_buffer_new(); + tt_assert(buf); + + /* Copy an empty buffer. */ + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_assert(buf2); + tt_int_op(0, ==, generic_buffer_len(buf2)); + + /* Now try with a short buffer. */ + s = "And now comes an act of enormous enormance!"; + len = strlen(s); + generic_buffer_add(buf, s, len); + tt_int_op(len, ==, generic_buffer_len(buf)); + /* Add junk to buf2 so we can test replacing.*/ + generic_buffer_add(buf2, "BLARG", 5); + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_int_op(len, ==, generic_buffer_len(buf2)); + generic_buffer_get(buf2, b, len); + test_mem_op(b, ==, s, len); + /* Now free buf2 and retry so we can test allocating */ + generic_buffer_free(buf2); + buf2 = NULL; + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_int_op(len, ==, generic_buffer_len(buf2)); + generic_buffer_get(buf2, b, len); + test_mem_op(b, ==, s, len); + /* Clear buf for next test */ + generic_buffer_get(buf, b, len); + tt_int_op(generic_buffer_len(buf),==,0); + + /* Okay, now let's try a bigger buffer. */ + s = "Quis autem vel eum iure reprehenderit qui in ea voluptate velit " + "esse quam nihil molestiae consequatur, vel illum qui dolorem eum " + "fugiat quo voluptas nulla pariatur?"; + len = strlen(s); + for (i = 0; i < 256; ++i) { + b[0]=i; + generic_buffer_add(buf, b, 1); + generic_buffer_add(buf, s, len); + } + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_int_op(generic_buffer_len(buf2), ==, generic_buffer_len(buf)); + for (i = 0; i < 256; ++i) { + generic_buffer_get(buf2, b, len+1); + tt_int_op((unsigned char)b[0],==,i); + test_mem_op(b+1, ==, s, len); + } + + done: + if (buf) + generic_buffer_free(buf); + if (buf2) + generic_buffer_free(buf2); +} + +static void +test_buffer_ext_or_cmd(void *arg) +{ + ext_or_cmd_t *cmd = NULL; + generic_buffer_t *buf = generic_buffer_new(); + char *tmp = NULL; + (void) arg; + + /* Empty -- should give "not there. */ + tt_int_op(0, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tt_ptr_op(NULL, ==, cmd); + + /* Three bytes: shouldn't work. */ + generic_buffer_add(buf, "\x00\x20\x00", 3); + tt_int_op(0, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tt_ptr_op(NULL, ==, cmd); + tt_int_op(3, ==, generic_buffer_len(buf)); + + /* 0020 0000: That's a nil command. It should work. */ + generic_buffer_add(buf, "\x00", 1); + tt_int_op(1, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tt_ptr_op(NULL, !=, cmd); + tt_int_op(0x20, ==, cmd->cmd); + tt_int_op(0, ==, cmd->len); + tt_int_op(0, ==, generic_buffer_len(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + /* Now try a length-6 command with one byte missing. */ + generic_buffer_add(buf, "\x10\x21\x00\x06""abcde", 9); + tt_int_op(0, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tt_ptr_op(NULL, ==, cmd); + generic_buffer_add(buf, "f", 1); + tt_int_op(1, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tt_ptr_op(NULL, !=, cmd); + tt_int_op(0x1021, ==, cmd->cmd); + tt_int_op(6, ==, cmd->len); + test_mem_op("abcdef", ==, cmd->body, 6); + tt_int_op(0, ==, generic_buffer_len(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + /* Now try a length-10 command with 4 extra bytes. */ + generic_buffer_add(buf, "\xff\xff\x00\x0a" + "loremipsum\x10\x00\xff\xff", 18); + tt_int_op(1, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tt_ptr_op(NULL, !=, cmd); + tt_int_op(0xffff, ==, cmd->cmd); + tt_int_op(10, ==, cmd->len); + test_mem_op("loremipsum", ==, cmd->body, 10); + tt_int_op(4, ==, generic_buffer_len(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + /* Finally, let's try a maximum-length command. We already have the header + * waiting. */ + tt_int_op(0, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tmp = tor_malloc_zero(65535); + generic_buffer_add(buf, tmp, 65535); + tt_int_op(1, ==, generic_buffer_fetch_ext_or_cmd(buf, &cmd)); + tt_ptr_op(NULL, !=, cmd); + tt_int_op(0x1000, ==, cmd->cmd); + tt_int_op(0xffff, ==, cmd->len); + test_mem_op(tmp, ==, cmd->body, 65535); + tt_int_op(0, ==, generic_buffer_len(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + done: + ext_or_cmd_free(cmd); + generic_buffer_free(buf); + tor_free(tmp); +} + +struct testcase_t buffer_tests[] = { + { "basic", test_buffers_basic, 0, NULL, NULL }, + { "copy", test_buffer_copy, 0, NULL, NULL }, + { "ext_or_cmd", test_buffer_ext_or_cmd, 0, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_cell_queue.c b/src/test/test_cell_queue.c new file mode 100644 index 000000000..1eac07310 --- /dev/null +++ b/src/test/test_cell_queue.c @@ -0,0 +1,148 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CIRCUITLIST_PRIVATE +#define RELAY_PRIVATE +#include "or.h" +#include "circuitlist.h" +#include "relay.h" +#include "test.h" + +static void +test_cq_manip(void *arg) +{ + packed_cell_t *pc1=NULL, *pc2=NULL, *pc3=NULL, *pc4=NULL, *pc_tmp=NULL; + cell_queue_t cq; + cell_t cell; + (void) arg; + + init_cell_pool(); + cell_queue_init(&cq); + tt_int_op(cq.n, ==, 0); + + pc1 = packed_cell_new(); + pc2 = packed_cell_new(); + pc3 = packed_cell_new(); + pc4 = packed_cell_new(); + tt_assert(pc1 && pc2 && pc3 && pc4); + + tt_ptr_op(NULL, ==, cell_queue_pop(&cq)); + + /* Add and remove a singleton. */ + cell_queue_append(&cq, pc1); + tt_int_op(cq.n, ==, 1); + tt_ptr_op(pc1, ==, cell_queue_pop(&cq)); + tt_int_op(cq.n, ==, 0); + + /* Add and remove four items */ + cell_queue_append(&cq, pc4); + cell_queue_append(&cq, pc3); + cell_queue_append(&cq, pc2); + cell_queue_append(&cq, pc1); + tt_int_op(cq.n, ==, 4); + tt_ptr_op(pc4, ==, cell_queue_pop(&cq)); + tt_ptr_op(pc3, ==, cell_queue_pop(&cq)); + tt_ptr_op(pc2, ==, cell_queue_pop(&cq)); + tt_ptr_op(pc1, ==, cell_queue_pop(&cq)); + tt_int_op(cq.n, ==, 0); + tt_ptr_op(NULL, ==, cell_queue_pop(&cq)); + + /* Try a packed copy (wide, then narrow, which is a bit of a cheat, since a + * real cell queue has only one type.) */ + memset(&cell, 0, sizeof(cell)); + cell.circ_id = 0x12345678; + cell.command = 10; + strlcpy((char*)cell.payload, "Lorax ipsum gruvvulus thneed amet, snergelly " + "once-ler lerkim, sed do barbaloot tempor gluppitus ut labore et " + "truffula magna aliqua.", + sizeof(cell.payload)); + cell_queue_append_packed_copy(NULL /*circ*/, &cq, 0 /*exitward*/, &cell, + 1 /*wide*/, 0 /*stats*/); + cell.circ_id = 0x2013; + cell_queue_append_packed_copy(NULL /*circ*/, &cq, 0 /*exitward*/, &cell, + 0 /*wide*/, 0 /*stats*/); + tt_int_op(cq.n, ==, 2); + + pc_tmp = cell_queue_pop(&cq); + tt_int_op(cq.n, ==, 1); + tt_ptr_op(pc_tmp, !=, NULL); + test_mem_op(pc_tmp->body, ==, "\x12\x34\x56\x78\x0a", 5); + test_mem_op(pc_tmp->body+5, ==, cell.payload, sizeof(cell.payload)); + packed_cell_free(pc_tmp); + + pc_tmp = cell_queue_pop(&cq); + tt_int_op(cq.n, ==, 0); + tt_ptr_op(pc_tmp, !=, NULL); + test_mem_op(pc_tmp->body, ==, "\x20\x13\x0a", 3); + test_mem_op(pc_tmp->body+3, ==, cell.payload, sizeof(cell.payload)); + packed_cell_free(pc_tmp); + pc_tmp = NULL; + + tt_ptr_op(NULL, ==, cell_queue_pop(&cq)); + + /* Now make sure cell_queue_clear works. */ + cell_queue_append(&cq, pc2); + cell_queue_append(&cq, pc1); + tt_int_op(cq.n, ==, 2); + cell_queue_clear(&cq); + pc2 = pc1 = NULL; /* prevent double-free */ + tt_int_op(cq.n, ==, 0); + + done: + packed_cell_free(pc1); + packed_cell_free(pc2); + packed_cell_free(pc3); + packed_cell_free(pc4); + packed_cell_free(pc_tmp); + + cell_queue_clear(&cq); + free_cell_pool(); +} + +static void +test_circuit_n_cells(void *arg) +{ + packed_cell_t *pc1=NULL, *pc2=NULL, *pc3=NULL, *pc4=NULL, *pc5=NULL; + origin_circuit_t *origin_c=NULL; + or_circuit_t *or_c=NULL; + + (void)arg; + + init_cell_pool(); + + pc1 = packed_cell_new(); + pc2 = packed_cell_new(); + pc3 = packed_cell_new(); + pc4 = packed_cell_new(); + pc5 = packed_cell_new(); + tt_assert(pc1 && pc2 && pc3 && pc4 && pc5); + + or_c = or_circuit_new(0, NULL); + origin_c = origin_circuit_new(); + origin_c->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL; + + tt_int_op(n_cells_in_circ_queues(TO_CIRCUIT(or_c)), ==, 0); + cell_queue_append(&or_c->p_chan_cells, pc1); + tt_int_op(n_cells_in_circ_queues(TO_CIRCUIT(or_c)), ==, 1); + cell_queue_append(&or_c->base_.n_chan_cells, pc2); + cell_queue_append(&or_c->base_.n_chan_cells, pc3); + tt_int_op(n_cells_in_circ_queues(TO_CIRCUIT(or_c)), ==, 3); + + tt_int_op(n_cells_in_circ_queues(TO_CIRCUIT(origin_c)), ==, 0); + cell_queue_append(&origin_c->base_.n_chan_cells, pc4); + cell_queue_append(&origin_c->base_.n_chan_cells, pc5); + tt_int_op(n_cells_in_circ_queues(TO_CIRCUIT(origin_c)), ==, 2); + + done: + circuit_free(TO_CIRCUIT(or_c)); + circuit_free(TO_CIRCUIT(origin_c)); + + free_cell_pool(); +} + +struct testcase_t cell_queue_tests[] = { + { "basic", test_cq_manip, TT_FORK, NULL, NULL, }, + { "circ_n_cells", test_circuit_n_cells, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_circuitlist.c b/src/test/test_circuitlist.c new file mode 100644 index 000000000..720b40765 --- /dev/null +++ b/src/test/test_circuitlist.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define TOR_CHANNEL_INTERNAL_ +#define CIRCUITLIST_PRIVATE +#include "or.h" +#include "channel.h" +#include "circuitlist.h" +#include "test.h" + +static channel_t * +new_fake_channel(void) +{ + channel_t *chan = tor_malloc_zero(sizeof(channel_t)); + channel_init(chan); + return chan; +} + +static struct { + int ncalls; + void *cmux; + void *circ; + cell_direction_t dir; +} cam; + +static void +circuitmux_attach_mock(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t dir) +{ + ++cam.ncalls; + cam.cmux = cmux; + cam.circ = circ; + cam.dir = dir; +} + +static struct { + int ncalls; + void *cmux; + void *circ; +} cdm; + +static void +circuitmux_detach_mock(circuitmux_t *cmux, circuit_t *circ) +{ + ++cdm.ncalls; + cdm.cmux = cmux; + cdm.circ = circ; +} + +#define GOT_CMUX_ATTACH(mux_, circ_, dir_) do { \ + tt_int_op(cam.ncalls, ==, 1); \ + tt_ptr_op(cam.cmux, ==, (mux_)); \ + tt_ptr_op(cam.circ, ==, (circ_)); \ + tt_ptr_op(cam.dir, ==, (dir_)); \ + memset(&cam, 0, sizeof(cam)); \ + } while (0) + +#define GOT_CMUX_DETACH(mux_, circ_) do { \ + tt_int_op(cdm.ncalls, ==, 1); \ + tt_ptr_op(cdm.cmux, ==, (mux_)); \ + tt_ptr_op(cdm.circ, ==, (circ_)); \ + memset(&cdm, 0, sizeof(cdm)); \ + } while (0) + +static void +test_clist_maps(void *arg) +{ + channel_t *ch1 = new_fake_channel(); + channel_t *ch2 = new_fake_channel(); + channel_t *ch3 = new_fake_channel(); + or_circuit_t *or_c1=NULL, *or_c2=NULL; + + (void) arg; + + MOCK(circuitmux_attach_circuit, circuitmux_attach_mock); + MOCK(circuitmux_detach_circuit, circuitmux_detach_mock); + memset(&cam, 0, sizeof(cam)); + memset(&cdm, 0, sizeof(cdm)); + + ch1->cmux = (void*)0x1001; + ch2->cmux = (void*)0x1002; + ch3->cmux = (void*)0x1003; + + or_c1 = or_circuit_new(100, ch2); + tt_assert(or_c1); + GOT_CMUX_ATTACH(ch2->cmux, or_c1, CELL_DIRECTION_IN); + tt_int_op(or_c1->p_circ_id, ==, 100); + tt_ptr_op(or_c1->p_chan, ==, ch2); + + or_c2 = or_circuit_new(100, ch1); + tt_assert(or_c2); + GOT_CMUX_ATTACH(ch1->cmux, or_c2, CELL_DIRECTION_IN); + tt_int_op(or_c2->p_circ_id, ==, 100); + tt_ptr_op(or_c2->p_chan, ==, ch1); + + circuit_set_n_circid_chan(TO_CIRCUIT(or_c1), 200, ch1); + GOT_CMUX_ATTACH(ch1->cmux, or_c1, CELL_DIRECTION_OUT); + + circuit_set_n_circid_chan(TO_CIRCUIT(or_c2), 200, ch2); + GOT_CMUX_ATTACH(ch2->cmux, or_c2, CELL_DIRECTION_OUT); + + tt_ptr_op(circuit_get_by_circid_channel(200, ch1), ==, TO_CIRCUIT(or_c1)); + tt_ptr_op(circuit_get_by_circid_channel(200, ch2), ==, TO_CIRCUIT(or_c2)); + tt_ptr_op(circuit_get_by_circid_channel(100, ch2), ==, TO_CIRCUIT(or_c1)); + /* Try the same thing again, to test the "fast" path. */ + tt_ptr_op(circuit_get_by_circid_channel(100, ch2), ==, TO_CIRCUIT(or_c1)); + tt_assert(circuit_id_in_use_on_channel(100, ch2)); + tt_assert(! circuit_id_in_use_on_channel(101, ch2)); + + /* Try changing the circuitid and channel of that circuit. */ + circuit_set_p_circid_chan(or_c1, 500, ch3); + GOT_CMUX_DETACH(ch2->cmux, TO_CIRCUIT(or_c1)); + GOT_CMUX_ATTACH(ch3->cmux, TO_CIRCUIT(or_c1), CELL_DIRECTION_IN); + tt_ptr_op(circuit_get_by_circid_channel(100, ch2), ==, NULL); + tt_assert(! circuit_id_in_use_on_channel(100, ch2)); + tt_ptr_op(circuit_get_by_circid_channel(500, ch3), ==, TO_CIRCUIT(or_c1)); + + /* Now let's see about destroy handling. */ + tt_assert(! circuit_id_in_use_on_channel(205, ch2)); + tt_assert(circuit_id_in_use_on_channel(200, ch2)); + channel_note_destroy_pending(ch2, 200); + channel_note_destroy_pending(ch2, 205); + channel_note_destroy_pending(ch1, 100); + tt_assert(circuit_id_in_use_on_channel(205, ch2)) + tt_assert(circuit_id_in_use_on_channel(200, ch2)); + tt_assert(circuit_id_in_use_on_channel(100, ch1)); + + tt_assert(TO_CIRCUIT(or_c2)->n_delete_pending != 0); + tt_ptr_op(circuit_get_by_circid_channel(200, ch2), ==, TO_CIRCUIT(or_c2)); + tt_ptr_op(circuit_get_by_circid_channel(100, ch1), ==, TO_CIRCUIT(or_c2)); + + /* Okay, now free ch2 and make sure that the circuit ID is STILL not + * usable, because we haven't declared the destroy to be nonpending */ + tt_int_op(cdm.ncalls, ==, 0); + circuit_free(TO_CIRCUIT(or_c2)); + or_c2 = NULL; /* prevent free */ + tt_int_op(cdm.ncalls, ==, 2); + memset(&cdm, 0, sizeof(cdm)); + tt_assert(circuit_id_in_use_on_channel(200, ch2)); + tt_assert(circuit_id_in_use_on_channel(100, ch1)); + tt_ptr_op(circuit_get_by_circid_channel(200, ch2), ==, NULL); + tt_ptr_op(circuit_get_by_circid_channel(100, ch1), ==, NULL); + + /* Now say that the destroy is nonpending */ + channel_note_destroy_not_pending(ch2, 200); + tt_ptr_op(circuit_get_by_circid_channel(200, ch2), ==, NULL); + channel_note_destroy_not_pending(ch1, 100); + tt_ptr_op(circuit_get_by_circid_channel(100, ch1), ==, NULL); + tt_assert(! circuit_id_in_use_on_channel(200, ch2)); + tt_assert(! circuit_id_in_use_on_channel(100, ch1)); + + done: + tor_free(ch1); + tor_free(ch2); + tor_free(ch3); + if (or_c1) + circuit_free(TO_CIRCUIT(or_c1)); + if (or_c2) + circuit_free(TO_CIRCUIT(or_c2)); + UNMOCK(circuitmux_attach_circuit); + UNMOCK(circuitmux_detach_circuit); +} + +struct testcase_t circuitlist_tests[] = { + { "maps", test_clist_maps, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c new file mode 100644 index 000000000..0f592001c --- /dev/null +++ b/src/test/test_circuitmux.c @@ -0,0 +1,84 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define TOR_CHANNEL_INTERNAL_ +#define CIRCUITMUX_PRIVATE +#define RELAY_PRIVATE +#include "or.h" +#include "channel.h" +#include "circuitmux.h" +#include "relay.h" +#include "test.h" + +/* XXXX duplicated function from test_circuitlist.c */ +static channel_t * +new_fake_channel(void) +{ + channel_t *chan = tor_malloc_zero(sizeof(channel_t)); + channel_init(chan); + return chan; +} + +static int +has_queued_writes(channel_t *c) +{ + (void) c; + return 1; +} + +/** Test destroy cell queue with no interference from other queues. */ +static void +test_cmux_destroy_cell_queue(void *arg) +{ + circuitmux_t *cmux = NULL; + channel_t *ch = NULL; + circuit_t *circ = NULL; + cell_queue_t *cq = NULL; + packed_cell_t *pc = NULL; + + init_cell_pool(); + (void) arg; + + cmux = circuitmux_alloc(); + tt_assert(cmux); + ch = new_fake_channel(); + ch->has_queued_writes = has_queued_writes; + ch->wide_circ_ids = 1; + + circ = circuitmux_get_first_active_circuit(cmux, &cq); + tt_assert(!circ); + tt_assert(!cq); + + circuitmux_append_destroy_cell(ch, cmux, 100, 10); + circuitmux_append_destroy_cell(ch, cmux, 190, 6); + circuitmux_append_destroy_cell(ch, cmux, 30, 1); + + tt_int_op(circuitmux_num_cells(cmux), ==, 3); + + circ = circuitmux_get_first_active_circuit(cmux, &cq); + tt_assert(!circ); + tt_assert(cq); + + tt_int_op(cq->n, ==, 3); + + pc = cell_queue_pop(cq); + tt_assert(pc); + test_mem_op(pc->body, ==, "\x00\x00\x00\x64\x04\x0a\x00\x00\x00", 9); + packed_cell_free(pc); + pc = NULL; + + tt_int_op(circuitmux_num_cells(cmux), ==, 2); + + done: + circuitmux_free(cmux); + channel_free(ch); + packed_cell_free(pc); + + free_cell_pool(); +} + +struct testcase_t circuitmux_tests[] = { + { "destroy_cell_queue", test_cmux_destroy_cell_queue, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_cmdline_args.py b/src/test/test_cmdline_args.py new file mode 100755 index 000000000..6d9cf44db --- /dev/null +++ b/src/test/test_cmdline_args.py @@ -0,0 +1,273 @@ +#!/usr/bin/python + +import binascii +import hashlib +import os +import re +import shutil +import subprocess +import sys +import tempfile +import unittest + +TOR = "./src/or/tor" +TOP_SRCDIR = "." + +if len(sys.argv) > 1: + TOR = sys.argv[1] + del sys.argv[1] + +if len(sys.argv) > 1: + TOP_SRCDIR = sys.argv[1] + del sys.argv[1] + +class UnexpectedSuccess(Exception): + pass + +class UnexpectedFailure(Exception): + pass + +def contents(fn): + f = open(fn) + try: + return f.read() + finally: + f.close() + +def run_tor(args, failure=False): + p = subprocess.Popen([TOR] + args, stdout=subprocess.PIPE) + output, _ = p.communicate() + result = p.poll() + if result and not failure: + raise UnexpectedFailure() + elif not result and failure: + raise UnexpectedSuccess() + return output + +def spaceify_fp(fp): + for i in xrange(0, len(fp), 4): + yield fp[i:i+4] + +def lines(s): + out = s.split("\n") + if out and out[-1] == '': + del out[-1] + return out + +def strip_log_junk(line): + m = re.match(r'([^\[]+\[[a-z]*\] *)(.*)', line) + if not m: + return ""+line + return m.group(2).strip() + +def randstring(entropy_bytes): + s = os.urandom(entropy_bytes) + return binascii.b2a_hex(s) + +def findLineContaining(lines, s): + for ln in lines: + if s in ln: + return True + return False + +class CmdlineTests(unittest.TestCase): + + def test_version(self): + out = run_tor(["--version"]) + self.failUnless(out.startswith("Tor version ")) + self.assertEquals(len(lines(out)), 1) + + def test_quiet(self): + out = run_tor(["--quiet", "--quumblebluffin", "1"], failure=True) + self.assertEquals(out, "") + + def test_help(self): + out = run_tor(["--help"], failure=False) + out2 = run_tor(["-h"], failure=False) + self.assert_(out.startswith("Copyright (c) 2001")) + self.assert_(out.endswith( + "tor -f <torrc> [args]\n" + "See man page for options, or https://www.torproject.org/ for documentation.\n")) + self.assert_(out == out2) + + def test_hush(self): + torrc = tempfile.NamedTemporaryFile(delete=False) + torrc.close() + try: + out = run_tor(["--hush", "-f", torrc.name, + "--quumblebluffin", "1"], failure=True) + finally: + os.unlink(torrc.name) + self.assertEquals(len(lines(out)), 2) + ln = [ strip_log_junk(l) for l in lines(out) ] + self.assertEquals(ln[0], "Failed to parse/validate config: Unknown option 'quumblebluffin'. Failing.") + self.assertEquals(ln[1], "Reading config failed--see warnings above.") + + def test_missing_argument(self): + out = run_tor(["--hush", "--hash-password"], failure=True) + self.assertEquals(len(lines(out)), 2) + ln = [ strip_log_junk(l) for l in lines(out) ] + self.assertEquals(ln[0], "Command-line option '--hash-password' with no value. Failing.") + + def test_hash_password(self): + out = run_tor(["--hash-password", "woodwose"]) + result = lines(out)[-1] + self.assertEquals(result[:3], "16:") + self.assertEquals(len(result), 61) + r = binascii.a2b_hex(result[3:]) + self.assertEquals(len(r), 29) + + salt, how, hashed = r[:8], r[8], r[9:] + self.assertEquals(len(hashed), 20) + + count = (16 + (ord(how) & 15)) << ((ord(how) >> 4) + 6) + stuff = salt + "woodwose" + repetitions = count // len(stuff) + 1 + inp = stuff * repetitions + inp = inp[:count] + + self.assertEquals(hashlib.sha1(inp).digest(), hashed) + + def test_digests(self): + main_c = os.path.join(TOP_SRCDIR, "src", "or", "main.c") + + if os.stat(TOR).st_mtime < os.stat(main_c).st_mtime: + self.skipTest(TOR+" not up to date") + out = run_tor(["--digests"]) + main_line = [ l for l in lines(out) if l.endswith("/main.c") ] + digest, name = main_line[0].split() + actual = hashlib.sha1(open(main_c).read()).hexdigest() + self.assertEquals(digest, actual) + + def test_dump_options(self): + default_torrc = tempfile.NamedTemporaryFile(delete=False) + torrc = tempfile.NamedTemporaryFile(delete=False) + torrc.write("SocksPort 9999") + torrc.close() + default_torrc.write("SafeLogging 0") + default_torrc.close() + out_sh = out_nb = out_fl = None + opts = [ "-f", torrc.name, + "--defaults-torrc", default_torrc.name ] + try: + out_sh = run_tor(["--dump-config", "short"]+opts) + out_nb = run_tor(["--dump-config", "non-builtin"]+opts) + out_fl = run_tor(["--dump-config", "full"]+opts) + out_nr = run_tor(["--dump-config", "bliznert"]+opts, + failure=True) + + out_verif = run_tor(["--verify-config"]+opts) + finally: + os.unlink(torrc.name) + os.unlink(default_torrc.name) + + self.assertEquals(len(lines(out_sh)), 2) + self.assert_(lines(out_sh)[0].startswith("DataDirectory ")) + self.assertEquals(lines(out_sh)[1:], + [ "SocksPort 9999" ]) + + self.assertEquals(len(lines(out_nb)), 2) + self.assertEquals(lines(out_nb), + [ "SafeLogging 0", + "SocksPort 9999" ]) + + out_fl = lines(out_fl) + self.assert_(len(out_fl) > 100) + self.assert_("SocksPort 9999" in out_fl) + self.assert_("SafeLogging 0" in out_fl) + self.assert_("ClientOnly 0" in out_fl) + + self.assert_(out_verif.endswith("Configuration was valid\n")) + + def test_list_fingerprint(self): + tmpdir = tempfile.mkdtemp(prefix='ttca_') + torrc = tempfile.NamedTemporaryFile(delete=False) + torrc.write("ORPort 9999\n") + torrc.write("DataDirectory %s\n"%tmpdir) + torrc.write("Nickname tippi") + torrc.close() + opts = ["-f", torrc.name] + try: + out = run_tor(["--list-fingerprint"]+opts) + fp = contents(os.path.join(tmpdir, "fingerprint")) + finally: + os.unlink(torrc.name) + shutil.rmtree(tmpdir) + + out = lines(out) + lastlog = strip_log_junk(out[-2]) + lastline = out[-1] + fp = fp.strip() + nn_fp = fp.split()[0] + space_fp = " ".join(spaceify_fp(fp.split()[1])) + self.assertEquals(lastlog, + "Your Tor server's identity key fingerprint is '%s'"%fp) + self.assertEquals(lastline, "tippi %s"%space_fp) + self.assertEquals(nn_fp, "tippi") + + def test_list_options(self): + out = lines(run_tor(["--list-torrc-options"])) + self.assert_(len(out)>100) + self.assert_(out[0] <= 'AccountingMax') + self.assert_("UseBridges" in out) + self.assert_("SocksPort" in out) + + def test_cmdline_args(self): + default_torrc = tempfile.NamedTemporaryFile(delete=False) + torrc = tempfile.NamedTemporaryFile(delete=False) + torrc.write("SocksPort 9999\n") + torrc.write("SocksPort 9998\n") + torrc.write("ORPort 9000\n") + torrc.write("ORPort 9001\n") + torrc.write("Nickname eleventeen\n") + torrc.write("ControlPort 9500\n") + torrc.close() + default_torrc.write("") + default_torrc.close() + out_sh = out_nb = out_fl = None + opts = [ "-f", torrc.name, + "--defaults-torrc", default_torrc.name, + "--dump-config", "short" ] + try: + out_1 = run_tor(opts) + out_2 = run_tor(opts+["+ORPort", "9003", + "SocksPort", "9090", + "/ControlPort", + "/TransPort", + "+ExtORPort", "9005"]) + finally: + os.unlink(torrc.name) + os.unlink(default_torrc.name) + + out_1 = [ l for l in lines(out_1) if not l.startswith("DataDir") ] + out_2 = [ l for l in lines(out_2) if not l.startswith("DataDir") ] + + self.assertEquals(out_1, + ["ControlPort 9500", + "Nickname eleventeen", + "ORPort 9000", + "ORPort 9001", + "SocksPort 9999", + "SocksPort 9998"]) + self.assertEquals(out_2, + ["ExtORPort 9005", + "Nickname eleventeen", + "ORPort 9000", + "ORPort 9001", + "ORPort 9003", + "SocksPort 9090"]) + + def test_missing_torrc(self): + fname = "nonexistent_file_"+randstring(8) + out = run_tor(["-f", fname, "--verify-config"], failure=True) + ln = [ strip_log_junk(l) for l in lines(out) ] + self.assert_("Unable to open configuration file" in ln[-2]) + self.assert_("Reading config failed" in ln[-1]) + + out = run_tor(["-f", fname, "--verify-config", "--ignore-missing-torrc"]) + ln = [ strip_log_junk(l) for l in lines(out) ] + self.assert_(findLineContaining(ln, ", using reasonable defaults")) + self.assert_("Configuration was valid" in ln[-1]) + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/test_config.c b/src/test/test_config.c index e20fe7329..6a285db6e 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -4,12 +4,16 @@ /* See LICENSE for licensing information */ #include "orconfig.h" + +#define CONFIG_PRIVATE #include "or.h" #include "addressmap.h" #include "config.h" #include "confparse.h" #include "connection_edge.h" #include "test.h" +#include "util.h" +#include "address.h" static void test_config_addressmap(void *arg) @@ -169,11 +173,391 @@ test_config_addressmap(void *arg) ; } +static int +is_private_dir(const char* path) +{ + struct stat st; + int r = stat(path, &st); + if (r) { + return 0; + } +#if !defined (_WIN32) || defined (WINCE) + if ((st.st_mode & (S_IFDIR | 0777)) != (S_IFDIR | 0700)) { + return 0; + } +#endif + return 1; +} + +static void +test_config_check_or_create_data_subdir(void *arg) +{ + or_options_t *options = get_options_mutable(); + char *datadir = options->DataDirectory = tor_strdup(get_fname("datadir-0")); + const char *subdir = "test_stats"; + char *subpath = get_datadir_fname(subdir); + struct stat st; + int r; +#if !defined (_WIN32) || defined (WINCE) + unsigned group_permission; +#endif + (void)arg; + +#if defined (_WIN32) && !defined (WINCE) + tt_int_op(mkdir(options->DataDirectory), ==, 0); +#else + tt_int_op(mkdir(options->DataDirectory, 0700), ==, 0); +#endif + + r = stat(subpath, &st); + + // The subdirectory shouldn't exist yet, + // but should be created by the call to check_or_create_data_subdir. + test_assert(r && (errno == ENOENT)); + test_assert(!check_or_create_data_subdir(subdir)); + test_assert(is_private_dir(subpath)); + + // The check should return 0, if the directory already exists + // and is private to the user. + test_assert(!check_or_create_data_subdir(subdir)); + +#if !defined (_WIN32) || defined (WINCE) + group_permission = st.st_mode | 0070; + r = chmod(subpath, group_permission); + + if (r) { + test_fail_msg("Changing permissions for the subdirectory failed."); + } + + // If the directory exists, but its mode is too permissive + // a call to check_or_create_data_subdir should reset the mode. + test_assert(!is_private_dir(subpath)); + test_assert(!check_or_create_data_subdir(subdir)); + test_assert(is_private_dir(subpath)); +#endif + + done: + rmdir(subpath); + tor_free(datadir); + tor_free(subpath); +} + +static void +test_config_write_to_data_subdir(void *arg) +{ + or_options_t* options = get_options_mutable(); + char *datadir = options->DataDirectory = tor_strdup(get_fname("datadir-1")); + const char* subdir = "test_stats"; + const char* fname = "test_file"; + const char* str = + "Lorem ipsum dolor sit amet, consetetur sadipscing\n" + "elitr, sed diam nonumy eirmod\n" + "tempor invidunt ut labore et dolore magna aliquyam\n" + "erat, sed diam voluptua.\n" + "At vero eos et accusam et justo duo dolores et ea\n" + "rebum. Stet clita kasd gubergren,\n" + "no sea takimata sanctus est Lorem ipsum dolor sit amet.\n" + "Lorem ipsum dolor sit amet,\n" + "consetetur sadipscing elitr, sed diam nonumy eirmod\n" + "tempor invidunt ut labore et dolore\n" + "magna aliquyam erat, sed diam voluptua. At vero eos et\n" + "accusam et justo duo dolores et\n" + "ea rebum. Stet clita kasd gubergren, no sea takimata\n" + "sanctus est Lorem ipsum dolor sit amet."; + char* filepath = get_datadir_fname2(subdir, fname); + (void)arg; + +#if defined (_WIN32) && !defined (WINCE) + tt_int_op(mkdir(options->DataDirectory), ==, 0); +#else + tt_int_op(mkdir(options->DataDirectory, 0700), ==, 0); +#endif + + // Write attempt shoudl fail, if subdirectory doesn't exist. + test_assert(write_to_data_subdir(subdir, fname, str, NULL)); + test_assert(! check_or_create_data_subdir(subdir)); + + // Content of file after write attempt should be + // equal to the original string. + test_assert(!write_to_data_subdir(subdir, fname, str, NULL)); + test_streq(read_file_to_str(filepath, 0, NULL), str); + + // A second write operation should overwrite the old content. + test_assert(!write_to_data_subdir(subdir, fname, str, NULL)); + test_streq(read_file_to_str(filepath, 0, NULL), str); + + done: + (void) unlink(filepath); + rmdir(options->DataDirectory); + tor_free(datadir); + tor_free(filepath); +} + +/* Test helper function: Make sure that a bridge line gets parsed + * properly. Also make sure that the resulting bridge_line_t structure + * has its fields set correctly. */ +static void +good_bridge_line_test(const char *string, const char *test_addrport, + const char *test_digest, const char *test_transport, + const smartlist_t *test_socks_args) +{ + char *tmp = NULL; + bridge_line_t *bridge_line = parse_bridge_line(string); + test_assert(bridge_line); + + /* test addrport */ + tmp = tor_strdup(fmt_addrport(&bridge_line->addr, bridge_line->port)); + test_streq(test_addrport, tmp); + tor_free(tmp); + + /* If we were asked to validate a digest, but we did not get a + digest after parsing, we failed. */ + if (test_digest && tor_digest_is_zero(bridge_line->digest)) + test_assert(0); + + /* If we were not asked to validate a digest, and we got a digest + after parsing, we failed again. */ + if (!test_digest && !tor_digest_is_zero(bridge_line->digest)) + test_assert(0); + + /* If we were asked to validate a digest, and we got a digest after + parsing, make sure it's correct. */ + if (test_digest) { + tmp = tor_strdup(hex_str(bridge_line->digest, DIGEST_LEN)); + tor_strlower(tmp); + test_streq(test_digest, tmp); + tor_free(tmp); + } + + /* If we were asked to validate a transport name, make sure tha it + matches with the transport name that was parsed. */ + if (test_transport && !bridge_line->transport_name) + test_assert(0); + if (!test_transport && bridge_line->transport_name) + test_assert(0); + if (test_transport) + test_streq(test_transport, bridge_line->transport_name); + + /* Validate the SOCKS argument smartlist. */ + if (test_socks_args && !bridge_line->socks_args) + test_assert(0); + if (!test_socks_args && bridge_line->socks_args) + test_assert(0); + if (test_socks_args) + test_assert(smartlist_strings_eq(test_socks_args, + bridge_line->socks_args)); + + done: + tor_free(tmp); + bridge_line_free(bridge_line); +} + +/* Test helper function: Make sure that a bridge line is + * unparseable. */ +static void +bad_bridge_line_test(const char *string) +{ + bridge_line_t *bridge_line = parse_bridge_line(string); + test_assert(!bridge_line); + + done: + bridge_line_free(bridge_line); +} + +static void +test_config_parse_bridge_line(void *arg) +{ + (void) arg; + good_bridge_line_test("192.0.2.1:4123", + "192.0.2.1:4123", NULL, NULL, NULL); + + good_bridge_line_test("192.0.2.1", + "192.0.2.1:443", NULL, NULL, NULL); + + good_bridge_line_test("transport [::1]", + "[::1]:443", NULL, "transport", NULL); + + good_bridge_line_test("transport 192.0.2.1:12 " + "4352e58420e68f5e40bf7c74faddccd9d1349413", + "192.0.2.1:12", + "4352e58420e68f5e40bf7c74faddccd9d1349413", + "transport", NULL); + + { + smartlist_t *sl_tmp = smartlist_new(); + smartlist_add_asprintf(sl_tmp, "twoandtwo=five"); + + good_bridge_line_test("transport 192.0.2.1:12 " + "4352e58420e68f5e40bf7c74faddccd9d1349413 twoandtwo=five", + "192.0.2.1:12", "4352e58420e68f5e40bf7c74faddccd9d1349413", + "transport", sl_tmp); + + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + } + + { + smartlist_t *sl_tmp = smartlist_new(); + smartlist_add_asprintf(sl_tmp, "twoandtwo=five"); + smartlist_add_asprintf(sl_tmp, "z=z"); + + good_bridge_line_test("transport 192.0.2.1:12 twoandtwo=five z=z", + "192.0.2.1:12", NULL, "transport", sl_tmp); + + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + } + + good_bridge_line_test("192.0.2.1:1231 " + "4352e58420e68f5e40bf7c74faddccd9d1349413", + "192.0.2.1:1231", + "4352e58420e68f5e40bf7c74faddccd9d1349413", + NULL, NULL); + + /* Empty line */ + bad_bridge_line_test(""); + /* bad transport name */ + bad_bridge_line_test("tr$n_sp0r7 190.20.2.2"); + /* weird ip address */ + bad_bridge_line_test("a.b.c.d"); + /* invalid fpr */ + bad_bridge_line_test("2.2.2.2:1231 4352e58420e68f5e40bf7c74faddccd9d1349"); + /* no k=v in the end */ + bad_bridge_line_test("obfs2 2.2.2.2:1231 " + "4352e58420e68f5e40bf7c74faddccd9d1349413 what"); + /* no addrport */ + bad_bridge_line_test("asdw"); + /* huge k=v value that can't fit in SOCKS fields */ + bad_bridge_line_test( + "obfs2 2.2.2.2:1231 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aa=b"); +} + +static void +test_config_parse_transport_options_line(void *arg) +{ + smartlist_t *options_sl = NULL, *sl_tmp = NULL; + + (void) arg; + + { /* too small line */ + options_sl = get_options_from_transport_options_line("valley", NULL); + test_assert(!options_sl); + } + + { /* no k=v values */ + options_sl = get_options_from_transport_options_line("hit it!", NULL); + test_assert(!options_sl); + } + + { /* correct line, but wrong transport specified */ + options_sl = + get_options_from_transport_options_line("trebuchet k=v", "rook"); + test_assert(!options_sl); + } + + { /* correct -- no transport specified */ + sl_tmp = smartlist_new(); + smartlist_add_asprintf(sl_tmp, "ladi=dadi"); + smartlist_add_asprintf(sl_tmp, "weliketo=party"); + + options_sl = + get_options_from_transport_options_line("rook ladi=dadi weliketo=party", + NULL); + test_assert(options_sl); + test_assert(smartlist_strings_eq(options_sl, sl_tmp)); + + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + sl_tmp = NULL; + SMARTLIST_FOREACH(options_sl, char *, s, tor_free(s)); + smartlist_free(options_sl); + options_sl = NULL; + } + + { /* correct -- correct transport specified */ + sl_tmp = smartlist_new(); + smartlist_add_asprintf(sl_tmp, "ladi=dadi"); + smartlist_add_asprintf(sl_tmp, "weliketo=party"); + + options_sl = + get_options_from_transport_options_line("rook ladi=dadi weliketo=party", + "rook"); + test_assert(options_sl); + test_assert(smartlist_strings_eq(options_sl, sl_tmp)); + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + sl_tmp = NULL; + SMARTLIST_FOREACH(options_sl, char *, s, tor_free(s)); + smartlist_free(options_sl); + options_sl = NULL; + } + + done: + if (options_sl) { + SMARTLIST_FOREACH(options_sl, char *, s, tor_free(s)); + smartlist_free(options_sl); + } + if (sl_tmp) { + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + } +} + +// Tests if an options with MyFamily fingerprints missing '$' normalises +// them correctly and also ensure it also works with multiple fingerprints +static void +test_config_fix_my_family(void *arg) +{ + char *err = NULL; + const char *family = "$1111111111111111111111111111111111111111, " + "1111111111111111111111111111111111111112, " + "$1111111111111111111111111111111111111113"; + + or_options_t* options = options_new(); + or_options_t* defaults = options_new(); + (void) arg; + + options_init(options); + options_init(defaults); + options->MyFamily = tor_strdup(family); + + options_validate(NULL, options, defaults, 0, &err) ; + + if (err != NULL) { + TT_FAIL(("options_validate failed: %s", err)); + } + + test_streq(options->MyFamily, "$1111111111111111111111111111111111111111, " + "$1111111111111111111111111111111111111112, " + "$1111111111111111111111111111111111111113"); + + done: + if (err != NULL) { + tor_free(err); + } + + or_options_free(options); + or_options_free(defaults); +} + #define CONFIG_TEST(name, flags) \ { #name, test_config_ ## name, flags, NULL, NULL } struct testcase_t config_tests[] = { CONFIG_TEST(addressmap, 0), + CONFIG_TEST(parse_bridge_line, 0), + CONFIG_TEST(parse_transport_options_line, 0), + CONFIG_TEST(check_or_create_data_subdir, TT_FORK), + CONFIG_TEST(write_to_data_subdir, TT_FORK), + CONFIG_TEST(fix_my_family, 0), END_OF_TESTCASES }; diff --git a/src/test/test_containers.c b/src/test/test_containers.c index 005e102e2..067c4c190 100644 --- a/src/test/test_containers.c +++ b/src/test/test_containers.c @@ -469,6 +469,51 @@ test_container_smartlist_join(void) tor_free(joined); } +static void +test_container_smartlist_ints_eq(void *arg) +{ + smartlist_t *sl1 = NULL, *sl2 = NULL; + int x; + (void)arg; + + tt_assert(smartlist_ints_eq(NULL, NULL)); + + sl1 = smartlist_new(); + tt_assert(!smartlist_ints_eq(sl1, NULL)); + tt_assert(!smartlist_ints_eq(NULL, sl1)); + + sl2 = smartlist_new(); + tt_assert(smartlist_ints_eq(sl1, sl2)); + + x = 5; + smartlist_add(sl1, tor_memdup(&x, sizeof(int))); + smartlist_add(sl2, tor_memdup(&x, sizeof(int))); + x = 90; + smartlist_add(sl1, tor_memdup(&x, sizeof(int))); + smartlist_add(sl2, tor_memdup(&x, sizeof(int))); + tt_assert(smartlist_ints_eq(sl1, sl2)); + + x = -50; + smartlist_add(sl1, tor_memdup(&x, sizeof(int))); + tt_assert(! smartlist_ints_eq(sl1, sl2)); + tt_assert(! smartlist_ints_eq(sl2, sl1)); + smartlist_add(sl2, tor_memdup(&x, sizeof(int))); + tt_assert(smartlist_ints_eq(sl1, sl2)); + + *(int*)smartlist_get(sl1, 1) = 101010; + tt_assert(! smartlist_ints_eq(sl2, sl1)); + *(int*)smartlist_get(sl2, 1) = 101010; + tt_assert(smartlist_ints_eq(sl1, sl2)); + + done: + if (sl1) + SMARTLIST_FOREACH(sl1, int *, ip, tor_free(ip)); + if (sl2) + SMARTLIST_FOREACH(sl2, int *, ip, tor_free(ip)); + smartlist_free(sl1); + smartlist_free(sl2); +} + /** Run unit tests for bitarray code */ static void test_container_bitarray(void) @@ -784,7 +829,7 @@ test_container_order_functions(void) } static void -test_di_map(void *arg) +test_container_di_map(void *arg) { di_digest256_map_t *map = NULL; const uint8_t key1[] = "In view of the fact that it was "; @@ -856,12 +901,12 @@ test_container_fp_pair_map(void) memset(fp6.second, 0x62, DIGEST_LEN); v = fp_pair_map_set(map, &fp1, (void*)99); - test_eq(v, NULL); + tt_ptr_op(v, ==, NULL); test_assert(!fp_pair_map_isempty(map)); v = fp_pair_map_set(map, &fp2, (void*)101); - test_eq(v, NULL); + tt_ptr_op(v, ==, NULL); v = fp_pair_map_set(map, &fp1, (void*)100); - test_eq(v, (void*)99); + tt_ptr_op(v, ==, (void*)99); test_eq_ptr(fp_pair_map_get(map, &fp1), (void*)100); test_eq_ptr(fp_pair_map_get(map, &fp2), (void*)101); test_eq_ptr(fp_pair_map_get(map, &fp3), NULL); @@ -912,18 +957,22 @@ test_container_fp_pair_map(void) #define CONTAINER_LEGACY(name) \ { #name, legacy_test_helper, 0, &legacy_setup, test_container_ ## name } +#define CONTAINER(name, flags) \ + { #name, test_container_ ## name, (flags), NULL, NULL } + struct testcase_t container_tests[] = { CONTAINER_LEGACY(smartlist_basic), CONTAINER_LEGACY(smartlist_strings), CONTAINER_LEGACY(smartlist_overlap), CONTAINER_LEGACY(smartlist_digests), CONTAINER_LEGACY(smartlist_join), + CONTAINER(smartlist_ints_eq, 0), CONTAINER_LEGACY(bitarray), CONTAINER_LEGACY(digestset), CONTAINER_LEGACY(strmap), CONTAINER_LEGACY(pqueue), CONTAINER_LEGACY(order_functions), - { "di_map", test_di_map, 0, NULL, NULL }, + CONTAINER(di_map, 0), CONTAINER_LEGACY(fp_pair_map), END_OF_TESTCASES }; diff --git a/src/test/test_controller_events.c b/src/test/test_controller_events.c new file mode 100644 index 000000000..3a9aeca2f --- /dev/null +++ b/src/test/test_controller_events.c @@ -0,0 +1,298 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CONNECTION_PRIVATE +#define TOR_CHANNEL_INTERNAL_ +#define CONTROL_PRIVATE +#include "or.h" +#include "channel.h" +#include "channeltls.h" +#include "connection.h" +#include "control.h" +#include "test.h" + +static void +help_test_bucket_note_empty(uint32_t expected_msec_since_midnight, + int tokens_before, size_t tokens_removed, + uint32_t msec_since_epoch) +{ + uint32_t timestamp_var = 0; + struct timeval tvnow; + tvnow.tv_sec = msec_since_epoch / 1000; + tvnow.tv_usec = (msec_since_epoch % 1000) * 1000; + connection_buckets_note_empty_ts(×tamp_var, tokens_before, + tokens_removed, &tvnow); + tt_int_op(expected_msec_since_midnight, ==, timestamp_var); + + done: + ; +} + +static void +test_cntev_bucket_note_empty(void *arg) +{ + (void)arg; + + /* Two cases with nothing to note, because bucket was empty before; + * 86442200 == 1970-01-02 00:00:42.200000 */ + help_test_bucket_note_empty(0, 0, 0, 86442200); + help_test_bucket_note_empty(0, -100, 100, 86442200); + + /* Nothing to note, because bucket has not been emptied. */ + help_test_bucket_note_empty(0, 101, 100, 86442200); + + /* Bucket was emptied, note 42200 msec since midnight. */ + help_test_bucket_note_empty(42200, 101, 101, 86442200); + help_test_bucket_note_empty(42200, 101, 102, 86442200); +} + +static void +test_cntev_bucket_millis_empty(void *arg) +{ + struct timeval tvnow; + (void)arg; + + /* 1970-01-02 00:00:42.200000 */ + tvnow.tv_sec = 86400 + 42; + tvnow.tv_usec = 200000; + + /* Bucket has not been refilled. */ + tt_int_op(0, ==, bucket_millis_empty(0, 42120, 0, 100, &tvnow)); + tt_int_op(0, ==, bucket_millis_empty(-10, 42120, -10, 100, &tvnow)); + + /* Bucket was not empty. */ + tt_int_op(0, ==, bucket_millis_empty(10, 42120, 20, 100, &tvnow)); + + /* Bucket has been emptied 80 msec ago and has just been refilled. */ + tt_int_op(80, ==, bucket_millis_empty(-20, 42120, -10, 100, &tvnow)); + tt_int_op(80, ==, bucket_millis_empty(-10, 42120, 0, 100, &tvnow)); + tt_int_op(80, ==, bucket_millis_empty(0, 42120, 10, 100, &tvnow)); + + /* Bucket has been emptied 180 msec ago, last refill was 100 msec ago + * which was insufficient to make it positive, so cap msec at 100. */ + tt_int_op(100, ==, bucket_millis_empty(0, 42020, 1, 100, &tvnow)); + + /* 1970-01-02 00:00:00:050000 */ + tvnow.tv_sec = 86400; + tvnow.tv_usec = 50000; + + /* Last emptied 30 msec before midnight, tvnow is 50 msec after + * midnight, that's 80 msec in total. */ + tt_int_op(80, ==, bucket_millis_empty(0, 86400000 - 30, 1, 100, &tvnow)); + + done: + ; +} + +static void +add_testing_cell_stats_entry(circuit_t *circ, uint8_t command, + unsigned int waiting_time, + unsigned int removed, unsigned int exitward) +{ + testing_cell_stats_entry_t *ent = tor_malloc_zero( + sizeof(testing_cell_stats_entry_t)); + ent->command = command; + ent->waiting_time = waiting_time; + ent->removed = removed; + ent->exitward = exitward; + if (!circ->testing_cell_stats) + circ->testing_cell_stats = smartlist_new(); + smartlist_add(circ->testing_cell_stats, ent); +} + +static void +test_cntev_sum_up_cell_stats(void *arg) +{ + or_circuit_t *or_circ; + circuit_t *circ; + cell_stats_t *cell_stats = NULL; + (void)arg; + + /* This circuit is fake. */ + or_circ = tor_malloc_zero(sizeof(or_circuit_t)); + or_circ->base_.magic = OR_CIRCUIT_MAGIC; + or_circ->base_.purpose = CIRCUIT_PURPOSE_OR; + circ = TO_CIRCUIT(or_circ); + + /* A single RELAY cell was added to the appward queue. */ + cell_stats = tor_malloc_zero(sizeof(cell_stats_t)); + add_testing_cell_stats_entry(circ, CELL_RELAY, 0, 0, 0); + sum_up_cell_stats_by_command(circ, cell_stats); + tt_int_op(1, ==, cell_stats->added_cells_appward[CELL_RELAY]); + + /* A single RELAY cell was added to the exitward queue. */ + add_testing_cell_stats_entry(circ, CELL_RELAY, 0, 0, 1); + sum_up_cell_stats_by_command(circ, cell_stats); + tt_int_op(1, ==, cell_stats->added_cells_exitward[CELL_RELAY]); + + /* A single RELAY cell was removed from the appward queue where it spent + * 20 msec. */ + add_testing_cell_stats_entry(circ, CELL_RELAY, 2, 1, 0); + sum_up_cell_stats_by_command(circ, cell_stats); + tt_int_op(20, ==, cell_stats->total_time_appward[CELL_RELAY]); + tt_int_op(1, ==, cell_stats->removed_cells_appward[CELL_RELAY]); + + /* A single RELAY cell was removed from the exitward queue where it + * spent 30 msec. */ + add_testing_cell_stats_entry(circ, CELL_RELAY, 3, 1, 1); + sum_up_cell_stats_by_command(circ, cell_stats); + tt_int_op(30, ==, cell_stats->total_time_exitward[CELL_RELAY]); + tt_int_op(1, ==, cell_stats->removed_cells_exitward[CELL_RELAY]); + + done: + tor_free(cell_stats); + tor_free(or_circ); +} + +static void +test_cntev_append_cell_stats(void *arg) +{ + smartlist_t *event_parts; + const char *key = "Z"; + uint64_t include_if_non_zero[CELL_COMMAND_MAX_ + 1], + number_to_include[CELL_COMMAND_MAX_ + 1]; + (void)arg; + + event_parts = smartlist_new(); + memset(include_if_non_zero, 0, + (CELL_COMMAND_MAX_ + 1) * sizeof(uint64_t)); + memset(number_to_include, 0, + (CELL_COMMAND_MAX_ + 1) * sizeof(uint64_t)); + + /* All array entries empty. */ + append_cell_stats_by_command(event_parts, key, + include_if_non_zero, + number_to_include); + tt_int_op(0, ==, smartlist_len(event_parts)); + + /* There's a RELAY cell to include, but the corresponding field in + * include_if_non_zero is still zero. */ + number_to_include[CELL_RELAY] = 1; + append_cell_stats_by_command(event_parts, key, + include_if_non_zero, + number_to_include); + tt_int_op(0, ==, smartlist_len(event_parts)); + + /* Now include single RELAY cell. */ + include_if_non_zero[CELL_RELAY] = 2; + append_cell_stats_by_command(event_parts, key, + include_if_non_zero, + number_to_include); + tt_str_op("Z=relay:1", ==, smartlist_pop_last(event_parts)); + + /* Add four CREATE cells. */ + include_if_non_zero[CELL_CREATE] = 3; + number_to_include[CELL_CREATE] = 4; + append_cell_stats_by_command(event_parts, key, + include_if_non_zero, + number_to_include); + tt_str_op("Z=create:4,relay:1", ==, smartlist_pop_last(event_parts)); + + done: + ; +} + +static void +test_cntev_format_cell_stats(void *arg) +{ + char *event_string = NULL; + origin_circuit_t *ocirc; + or_circuit_t *or_circ; + cell_stats_t *cell_stats = NULL; + channel_tls_t *n_chan, *p_chan; + (void)arg; + + n_chan = tor_malloc_zero(sizeof(channel_tls_t)); + n_chan->base_.global_identifier = 1; + + ocirc = tor_malloc_zero(sizeof(origin_circuit_t)); + ocirc->base_.magic = ORIGIN_CIRCUIT_MAGIC; + ocirc->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL; + ocirc->global_identifier = 2; + ocirc->base_.n_circ_id = 3; + ocirc->base_.n_chan = &(n_chan->base_); + + /* Origin circuit was completely idle. */ + cell_stats = tor_malloc_zero(sizeof(cell_stats_t)); + format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats); + tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1", ==, event_string); + tor_free(event_string); + + /* Origin circuit had 4 RELAY cells added to its exitward queue. */ + cell_stats->added_cells_exitward[CELL_RELAY] = 4; + format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats); + tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1 OutboundAdded=relay:4", + ==, event_string); + tor_free(event_string); + + /* Origin circuit also had 5 CREATE2 cells added to its exitward + * queue. */ + cell_stats->added_cells_exitward[CELL_CREATE2] = 5; + format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats); + tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1 OutboundAdded=relay:4," + "create2:5", ==, event_string); + tor_free(event_string); + + /* Origin circuit also had 7 RELAY cells removed from its exitward queue + * which together spent 6 msec in the queue. */ + cell_stats->total_time_exitward[CELL_RELAY] = 6; + cell_stats->removed_cells_exitward[CELL_RELAY] = 7; + format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats); + tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1 OutboundAdded=relay:4," + "create2:5 OutboundRemoved=relay:7 OutboundTime=relay:6", + ==, event_string); + tor_free(event_string); + + p_chan = tor_malloc_zero(sizeof(channel_tls_t)); + p_chan->base_.global_identifier = 2; + + or_circ = tor_malloc_zero(sizeof(or_circuit_t)); + or_circ->base_.magic = OR_CIRCUIT_MAGIC; + or_circ->base_.purpose = CIRCUIT_PURPOSE_OR; + or_circ->p_circ_id = 8; + or_circ->p_chan = &(p_chan->base_); + or_circ->base_.n_circ_id = 9; + or_circ->base_.n_chan = &(n_chan->base_); + + tor_free(cell_stats); + + /* OR circuit was idle. */ + cell_stats = tor_malloc_zero(sizeof(cell_stats_t)); + format_cell_stats(&event_string, TO_CIRCUIT(or_circ), cell_stats); + tt_str_op("InboundQueue=8 InboundConn=2 OutboundQueue=9 OutboundConn=1", + ==, event_string); + tor_free(event_string); + + /* OR circuit had 3 RELAY cells added to its appward queue. */ + cell_stats->added_cells_appward[CELL_RELAY] = 3; + format_cell_stats(&event_string, TO_CIRCUIT(or_circ), cell_stats); + tt_str_op("InboundQueue=8 InboundConn=2 InboundAdded=relay:3 " + "OutboundQueue=9 OutboundConn=1", ==, event_string); + tor_free(event_string); + + /* OR circuit had 7 RELAY cells removed from its appward queue which + * together spent 6 msec in the queue. */ + cell_stats->total_time_appward[CELL_RELAY] = 6; + cell_stats->removed_cells_appward[CELL_RELAY] = 7; + format_cell_stats(&event_string, TO_CIRCUIT(or_circ), cell_stats); + tt_str_op("InboundQueue=8 InboundConn=2 InboundAdded=relay:3 " + "InboundRemoved=relay:7 InboundTime=relay:6 " + "OutboundQueue=9 OutboundConn=1", ==, event_string); + + done: + tor_free(cell_stats); + tor_free(event_string); +} + +#define TEST(name, flags) \ + { #name, test_cntev_ ## name, flags, 0, NULL } + +struct testcase_t controller_event_tests[] = { + TEST(bucket_note_empty, 0), + TEST(bucket_millis_empty, 0), + TEST(sum_up_cell_stats, 0), + TEST(append_cell_stats, 0), + TEST(format_cell_stats, 0), + END_OF_TESTCASES +}; + diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index f92bfd673..22adc6cc3 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -4,7 +4,6 @@ /* See LICENSE for licensing information */ #include "orconfig.h" -#define CRYPTO_PRIVATE #define CRYPTO_CURVE25519_PRIVATE #include "or.h" #include "test.h" @@ -14,6 +13,10 @@ #include "crypto_curve25519.h" #endif +extern const char AUTHORITY_SIGNKEY_3[]; +extern const char AUTHORITY_SIGNKEY_A_DIGEST[]; +extern const char AUTHORITY_SIGNKEY_A_DIGEST256[]; + /** Run unit tests for Diffie-Hellman functionality. */ static void test_crypto_dh(void) @@ -269,34 +272,6 @@ test_crypto_sha(void) "96177A9CB410FF61F20015AD"); tt_int_op(i, ==, 0); - /* Test HMAC-SHA-1 with test cases from RFC2202. */ - - /* Case 1. */ - memset(key, 0x0b, 20); - crypto_hmac_sha1(digest, key, 20, "Hi There", 8); - test_streq(hex_str(digest, 20), - "B617318655057264E28BC0B6FB378C8EF146BE00"); - /* Case 2. */ - crypto_hmac_sha1(digest, "Jefe", 4, "what do ya want for nothing?", 28); - test_streq(hex_str(digest, 20), - "EFFCDF6AE5EB2FA2D27416D5F184DF9C259A7C79"); - - /* Case 4. */ - base16_decode(key, 25, - "0102030405060708090a0b0c0d0e0f10111213141516171819", 50); - memset(data, 0xcd, 50); - crypto_hmac_sha1(digest, key, 25, data, 50); - test_streq(hex_str(digest, 20), - "4C9007F4026250C6BC8414F9BF50C86C2D7235DA"); - - /* Case 5. */ - memset(key, 0xaa, 80); - crypto_hmac_sha1(digest, key, 80, - "Test Using Larger Than Block-Size Key - Hash Key First", - 54); - test_streq(hex_str(digest, 20), - "AA4AE5E15272D00E95705637CE8A3B55ED402112"); - /* Test HMAC-SHA256 with test cases from wikipedia and RFC 4231 */ /* Case empty (wikipedia) */ @@ -422,7 +397,7 @@ test_crypto_pk(void) char *encoded = NULL; char data1[1024], data2[1024], data3[1024]; size_t size; - int i, j, p, len; + int i, len; /* Public-key ciphers */ pk1 = pk_generate(0); @@ -506,19 +481,16 @@ test_crypto_pk(void) /* Try with hybrid encryption wrappers. */ crypto_rand(data1, 1024); - for (i = 0; i < 2; ++i) { - for (j = 85; j < 140; ++j) { - memset(data2,0,1024); - memset(data3,0,1024); - p = (i==0)?PK_PKCS1_PADDING:PK_PKCS1_OAEP_PADDING; - len = crypto_pk_public_hybrid_encrypt(pk1,data2,sizeof(data2), - data1,j,p,0); - test_assert(len>=0); - len = crypto_pk_private_hybrid_decrypt(pk1,data3,sizeof(data3), - data2,len,p,1); - test_eq(len,j); - test_memeq(data1,data3,j); - } + for (i = 85; i < 140; ++i) { + memset(data2,0,1024); + memset(data3,0,1024); + len = crypto_pk_public_hybrid_encrypt(pk1,data2,sizeof(data2), + data1,i,PK_PKCS1_OAEP_PADDING,0); + test_assert(len>=0); + len = crypto_pk_private_hybrid_decrypt(pk1,data3,sizeof(data3), + data2,len,PK_PKCS1_OAEP_PADDING,1); + test_eq(len,i); + test_memeq(data1,data3,i); } /* Try copy_full */ @@ -536,6 +508,35 @@ test_crypto_pk(void) tor_free(encoded); } +/** Sanity check for crypto pk digests */ +static void +test_crypto_digests(void) +{ + crypto_pk_t *k = NULL; + ssize_t r; + digests_t pkey_digests; + char digest[DIGEST_LEN]; + + k = crypto_pk_new(); + test_assert(k); + r = crypto_pk_read_private_key_from_string(k, AUTHORITY_SIGNKEY_3, -1); + test_assert(!r); + + r = crypto_pk_get_digest(k, digest); + test_assert(r == 0); + test_memeq(hex_str(digest, DIGEST_LEN), + AUTHORITY_SIGNKEY_A_DIGEST, HEX_DIGEST_LEN); + + r = crypto_pk_get_all_digests(k, &pkey_digests); + + test_memeq(hex_str(pkey_digests.d[DIGEST_SHA1], DIGEST_LEN), + AUTHORITY_SIGNKEY_A_DIGEST, HEX_DIGEST_LEN); + test_memeq(hex_str(pkey_digests.d[DIGEST_SHA256], DIGEST256_LEN), + AUTHORITY_SIGNKEY_A_DIGEST256, HEX_DIGEST256_LEN); + done: + crypto_pk_free(k); +} + /** Run unit tests for misc crypto formatting functionality (base64, base32, * fingerprints, etc) */ static void @@ -630,7 +631,7 @@ test_crypto_formats(void) data1 = tor_strdup("ABCD1234ABCD56780000ABCD1234ABCD56780000"); test_eq(strlen(data1), 40); data2 = tor_malloc(FINGERPRINT_LEN+1); - add_spaces_to_fp(data2, FINGERPRINT_LEN+1, data1); + crypto_add_spaces_to_fp(data2, FINGERPRINT_LEN+1, data1); test_streq(data2, "ABCD 1234 ABCD 5678 0000 ABCD 1234 ABCD 5678 0000"); tor_free(data1); tor_free(data2); @@ -730,11 +731,13 @@ test_crypto_aes_iv(void *arg) /* Decrypt with the wrong key. */ decrypted_size = crypto_cipher_decrypt_with_iv(key2, decrypted2, 4095, encrypted1, encrypted_size); + test_eq(decrypted_size, 4095); test_memneq(plain, decrypted2, decrypted_size); /* Alter the initialization vector. */ encrypted1[0] += 42; decrypted_size = crypto_cipher_decrypt_with_iv(key1, decrypted1, 4095, encrypted1, encrypted_size); + test_eq(decrypted_size, 4095); test_memneq(plain, decrypted2, 4095); /* Special length case: 1. */ encrypted_size = crypto_cipher_encrypt_with_iv(key1, encrypted1, 16 + 1, @@ -1134,6 +1137,7 @@ struct testcase_t crypto_tests[] = { { "aes_EVP", test_crypto_aes, TT_FORK, &pass_data, (void*)"evp" }, CRYPTO_LEGACY(sha), CRYPTO_LEGACY(pk), + CRYPTO_LEGACY(digests), CRYPTO_LEGACY(dh), CRYPTO_LEGACY(s2k), { "aes_iv_AES", test_crypto_aes_iv, TT_FORK, &pass_data, (void*)"aes" }, diff --git a/src/test/test_data.c b/src/test/test_data.c index 5f0f7cba0..0c51c98f1 100644 --- a/src/test/test_data.c +++ b/src/test/test_data.c @@ -3,8 +3,18 @@ * Copyright (c) 2007-2013, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/* Our unit test expect that the AUTHORITY_CERT_* public keys will sort + * in this order. */ +#define AUTHORITY_CERT_A AUTHORITY_CERT_3 +#define AUTHORITY_CERT_B AUTHORITY_CERT_1 +#define AUTHORITY_CERT_C AUTHORITY_CERT_2 + +#define AUTHORITY_SIGNKEY_A AUTHORITY_SIGNKEY_3 +#define AUTHORITY_SIGNKEY_B AUTHORITY_SIGNKEY_1 +#define AUTHORITY_SIGNKEY_C AUTHORITY_SIGNKEY_2 + /** First of 3 example authority certificates for unit testing. */ -const char AUTHORITY_CERT_1[] = +const char AUTHORITY_CERT_A[] = "dir-key-certificate-version 3\n" "fingerprint D867ACF56A9D229B35C25F0090BC9867E906BE69\n" "dir-key-published 2008-12-12 18:07:24\n" @@ -46,7 +56,7 @@ const char AUTHORITY_CERT_1[] = "-----END SIGNATURE-----\n"; /** The private signing key for AUTHORITY_CERT_1 */ -const char AUTHORITY_SIGNKEY_1[] = +const char AUTHORITY_SIGNKEY_A[] = "-----BEGIN RSA PRIVATE KEY-----\n" "MIICWwIBAAKBgQCz0lCJ8rhLujVdzY6M6ZWp4iBAc0FxI79cff/pqp8GQAaWFZrs\n" "vQPJ8XqMmN7GRbJ2MDVvyGYwIBtt6RJnr7txfi+JsjI42mujkZdzIEWEOIJrhaqX\n" @@ -63,111 +73,128 @@ const char AUTHORITY_SIGNKEY_1[] = "Yx4lqK0ca5IkTp3HevwnlWaJgbaOTUspCVshzJBhDA==\n" "-----END RSA PRIVATE KEY-----\n"; +const char AUTHORITY_SIGNKEY_A_DIGEST[] = + "CBF56A83368A5150F1A9AAADAFB4D77F8C4170E2"; +const char AUTHORITY_SIGNKEY_A_DIGEST256[] = + "AF7C5468DBE3BA54A052726038D7F15F3C4CA511B1952645B3D96D83A8DFB51C"; + /** Second of 3 example authority certificates for unit testing. */ -const char AUTHORITY_CERT_2[] = +const char AUTHORITY_CERT_B[] = "dir-key-certificate-version 3\n" -"fingerprint 4D44AE0470B9E88FD4558EFEC82698FB33715400\n" -"dir-key-published 2007-06-13 16:52:32\n" -"dir-key-expires 2008-06-13 16:52:32\n" +"fingerprint AD011E25302925A9D39A80E0E32576442E956467\n" +"dir-key-published 2013-11-14 14:12:05\n" +"dir-key-expires 2014-11-14 14:12:05\n" "dir-identity-key\n" "-----BEGIN RSA PUBLIC KEY-----\n" -"MIIBigKCAYEAqukDwQRm1Oy1pPY+7GNRnRNFJzEVPUBfJwC4tBH19tkvdRQPuIGI\n" -"2jiTy/rmZ6CLcl1G0oulSgxfKEX75QdptOasZu+rKUrRRSxx0QrXhs9a7up0rpXh\n" -"13fw3mh1Vl/As3rJYF30Hjk01BTOJMxi/HY2y0ALQytFWjiMGY74A9Y6+uDcHkB2\n" -"KflBjxIl8zpCsXsTTnUhN5kXqaOOnK46XaUShSpXsyOxTMJXuJEtgLz9XCyA8XjW\n" -"d75QLHucEnlTqxUAdI5YSN2KIlIJiySCVnAorDpJey2mE9VncpHQWMCv/FPFdnSU\n" -"EMMPUc4bBShcoNFf0mMJeV2sv+dBkgKAL0GLM19PuJIThJhfN/B6+YQTxw4HEpPV\n" -"plfUqYRN0fYC+5hCTS6rroO/uCfDR7NBtoeDNm9dQrvjfk3b/Mywah1rdWNjnVqs\n" -"tPJaz3fc/CVBOUUexhmyktgLuwSNEYIQilQ+BydkWN/4RObhV+YSV5BgekEDVaoS\n" -"RHw4IbYBDHVxAgMBAAE=\n" +"MIIBigKCAYEAyXYEMlGNRAixXdg65xf2WPkskYj2Wo8ysKMTls1JCXdIOAPvC2k2\n" +"+AC6i3x9JHzUgCjWr4Jd5PSi7ODGyFC543igYl4wzkxNTU2L+SQ+hMe9qbEuUNhH\n" +"sRR0xofdoH//3UuKj+HXEiMhhHbRWQGtWFuJqtGBruJqjZqIGOrp5nFjdlP0R98n\n" +"Rx5wWlPgdJzifkXjKouu4mV+KzLl7f0gAtngA9DkSjt1wzga5IlL/lxDciD0SyJU\n" +"tKMmls056omrZNbTnBxnY2pOlq9nx/zFrt/KQm1fTAQMjMBCf9KnDIV7NhaaHx7F\n" +"7Nk8L7Hha353SvR+bsOFpiu05/EMZFTTIhO3MhUxZiCVZ0hKXvW1xe0HoGC5wbB+\n" +"NyXu8oa4fIKLJ+WJ8Z60BNc0DcxJiQOf1eolGM/qrBul1lFZznds5/7182d+nF2W\n" +"+bEjSm0fgXIxPfSD/7hB0FvgtmB3TXybHGBfPZgX0sTzFB6LNtP0BHicRoMXKdLF\n" +"hM3tgIjEAsoZAgMBAAE=\n" "-----END RSA PUBLIC KEY-----\n" "dir-signing-key\n" "-----BEGIN RSA PUBLIC KEY-----\n" -"MIGJAoGBAOu3dgrQth3iqvi/UzfywaANw0bBUuMOBhnMBeiLEcRLneJHUJkVvrpR\n" -"/EDQkdMov1e7CX6aqBKygVnbDNYjJ+bcQej8MKpuuW+zIknnz5lfnAVZO5uAmo3Y\n" -"DpG574oQ2FFMdkWHSBloIRxSj/E4Jn1M2qJjElBXP0E33Ka/Noo7AgMBAAE=\n" +"MIGJAoGBAJ567PZIGG/mYWEY4szYi/C5XXvf0BkquzKTHKrqVjysZEys9giz56Gv\n" +"B08kIRxsxYKEWkq60rv0xtTc1WyEMcDpV1WLU0KSTQSVXzLu7BT8jbTsWzGsxdTV\n" +"TdeyOirwHh8Cyyon5lppuMH5twUHrL5O7pWWbxjjrQjAHCn3gd+NAgMBAAE=\n" "-----END RSA PUBLIC KEY-----\n" +"dir-key-crosscert\n" +"-----BEGIN ID SIGNATURE-----\n" +"OC+gaukd4K7xJOsgTPbRhacf5mDUGxsu3ho/J1oJdtni4CK9WscVs6/Goj1o5Lot\n" +"H1nCAMaR96Jnqq5c63Aaj1sEXdeYHlu5cI7YHgtGI5MmtjiUNXUCWMjCwSQYwGKe\n" +"2YDYGAKAGt97n7XMKhJWGjAmv1TgmK3DvL1jt/aazL8=\n" +"-----END ID SIGNATURE-----\n" "dir-key-certification\n" "-----BEGIN SIGNATURE-----\n" -"Fv0Li68QUdAiChY3OklZOakHzwXAUfCzDNxkqe+HLC0n6ZECE9ZCvLVo69XmgVhH\n" -"L5qYr2rxT6QpF+9yuOHbN9gWn8EsDcli06MlhX9TUt/IYVxHa/9tJwNoTfEw2w2D\n" -"tyHhWm94IfOK7/Sea6jHnjckl80X+kk0ZNtAGs3/6fP4iltKNGXnvBwfgLpEgW7X\n" -"NpDl0OLeDuA79zem2GogwQZQdoDbePByU0TJVx9jYi2Bzx2Nb2H0hRTPP6+dY0HQ\n" -"MHb7yyyTQRad5iAUnExKhhyt22p7X3a6lgkAhq4YrNn/zVPkpnT2dzjsOydTHOW8\n" -"2BQs33QlGNe095i47pJBDYsUgmJaXfqB/RG6dFg7jwIsc3/7dZcvcqfxY7wKcD/T\n" -"wtogCIKxDvWbZn7f0hqYkT6uQC8Zom8bcnedmyzufOZCyA2SqQ2wvio6lznR4RIB\n" -"a8qDHR0tPS9/VkqTPcvUWCZeY3UiDeWPjoK1nea1pz6DHDWglKPx86a0amjjayZQ\n" -"-----END SIGNATURE-----\n"; +"BddmCKsvS6VoFXIf9Aj9OZnfyVCx527517QtsQHN+NaVm20LzUkJ5MWGXYx4wgh3\n" +"ExsHvVQguiVfnonkQpEHHKg+TbldlkuDhIdlb9f7dL7V3HLCsEdmS1c3A+TEyrPH\n" +"i44p6QB5IMFAdgUMV/9ueKMh7pMoam6VNakMOd+Axx9BSJTrCRzcepjtM4Z0cPsj\n" +"nmDgZi0df1+ca1t+HnuWyt3trxlqoUxRcPZKz28kEFDJsgnRNvoHrIvNTuy9qY4x\n" +"rONnPuLr5kTO7VQVVZxgxt6WX3p6d8tj+WYHubydr2pG0dwu2vGDTy4qXvDIm/I4\n" +"Gyo6OAoPbYV8fl0584EgiEbAWcX/Pze8mXr9lmXbf73xbSBHqveAs0UfB+4sBI98\n" +"v4ax4NZkGs8cCIfugtAOLgZE0WCh/TQYnQ3PFcrUtj0RW+tM1z7S8P3UfEVBHVkJ\n" +"8SqSB+pbsY6PwMuy6TC3WujW7gmjVanbwkbW19El9l9jRzteFerz7grG/WQkshqF\n" + "-----END SIGNATURE-----\n"; /** The private signing key for AUTHORITY_CERT_2 */ -const char AUTHORITY_SIGNKEY_2[] = +const char AUTHORITY_SIGNKEY_B[] = "-----BEGIN RSA PRIVATE KEY-----\n" -"MIICXgIBAAKBgQDrt3YK0LYd4qr4v1M38sGgDcNGwVLjDgYZzAXoixHES53iR1CZ\n" -"Fb66UfxA0JHTKL9Xuwl+mqgSsoFZ2wzWIyfm3EHo/DCqbrlvsyJJ58+ZX5wFWTub\n" -"gJqN2A6Rue+KENhRTHZFh0gZaCEcUo/xOCZ9TNqiYxJQVz9BN9ymvzaKOwIDAQAB\n" -"AoGAJ+I9/ex8tCfTSA2PdisEKiHKBeHWNYb870Z/RW6qje1BhLUOZSixwfL3XLwt\n" -"wG3nml+SZrKid69uhZaz4FPIf0tqCgURf6dDrF5vuzzr7VLVqkZHYSBp0vE6bu0R\n" -"Sgc5QNxI2talgc4bsp0O0C+Zd4n3Yto0pXl/I6NHVAxlFBECQQD2mahkY+QEHWPV\n" -"yRY3w3HhRmWBcrkY2zVyvPpqfn/sdHRPYW/yj4Xr/d1CO9VyFmEs4k324lIvu6LT\n" -"WDdpPlcJAkEA9LOZv5aNeAm8ckvvXH7iv8KiONiSz0n9wlisxMhNYTEkOCo1g7jG\n" -"AX5ZknRC9s4sWCPOBpMhloUvemdQ5FCEIwJBAMqCFwoSCf7jD8hRcUBr7QodoF/0\n" -"kVJ7OeI2lMJ9jZnlbFp/3snn2Qeam2e38SnWfQi582KKKwnt4eIDMMXpntkCQQDI\n" -"v1Lh11wl3y7nQZ6T7lCNatp08k+2mQgCWYcbRQweMRd6sD4I2xwt+372ZETPfyLo\n" -"CC+sOyYx+v+RVpMJS3irAkEA6l98nMteZKmhOgyKSjdolP+ahpZunb+WnCdAtP97\n" -"rjZyXmEZS3oe7TRCDD28GAGMmxSDvNfOOpyn14ishEs5AQ==\n" +"MIICWwIBAAKBgQCeeuz2SBhv5mFhGOLM2IvwuV1739AZKrsykxyq6lY8rGRMrPYI\n" +"s+ehrwdPJCEcbMWChFpKutK79MbU3NVshDHA6VdVi1NCkk0ElV8y7uwU/I207Fsx\n" +"rMXU1U3Xsjoq8B4fAssqJ+ZaabjB+bcFB6y+Tu6Vlm8Y460IwBwp94HfjQIDAQAB\n" +"AoGAfHQ4ZmfTmPyoeGHcqdVcgBxxh3gJqdnezCavGqGQO3F+CqDBTbBKNLSI3uOW\n" +"hQX+TTK23Xy9RRFCm6MYj3F4x7OOrSHSFyhMmzRnAZi3zGbtQZn30XoqTwCmVevY\n" +"p5JbVvhP2BJcvdsyQhiIG23FRQ7MMHWtksAxmovTto1h/hkCQQDNCfMqSztgJZDn\n" +"JSf5ASHBOw8QzfZBeYi3hqfiDtAN1RxT1uQnEiFQFJqwCz5lCbcwVrfQbrrk5M+h\n" +"ooYrX7tTAkEAxd6Tl0N0WM3zCKz+3/Hoiyty6olnnpzNoPCg7LLBJcetABQi0KUv\n" +"swYWlKP3eOFZkiBzTqa9nBK7eYLKV3d9nwJAKNM3WI98Nguky3FJgTnpd6kDuevY\n" +"gXbqcuhb2xXp9Sceqc7axLDGc0R2/GBwvvttPzG1DcpOai7o7J0Iq/A2wwJAYuKI\n" +"/99GFdtWyc8q0OAkRui/1VY14p6aZQPcaG4s+KSBYLivbXYgEGfKgR4wXsi/6rcs\n" +"6PGLcKQr7N3gITYmIQJAaQn6djUWygCn1noKyWU+Sa7G5qqU2GWkLq9dMaRLm1/I\n" +"nqi+2K1mN15rra0QtFVqSH4JXr8h3KAGyU45voGM7A==\n" "-----END RSA PRIVATE KEY-----\n"; /** Third of 3 example authority certificates for unit testing. */ -const char AUTHORITY_CERT_3[] = +const char AUTHORITY_CERT_C[] = "dir-key-certificate-version 3\n" -"fingerprint ED3719BF554DE9D7D59F5CA5A4F5AD121D020ED9\n" -"dir-key-published 2007-06-13 16:52:40\n" -"dir-key-expires 2008-06-13 16:52:40\n" +"fingerprint 628C2086EC29C9D26E638C5A8B2065BFBD35829B\n" +"dir-key-published 2013-11-14 14:12:18\n" +"dir-key-expires 2014-11-14 14:12:18\n" "dir-identity-key\n" "-----BEGIN RSA PUBLIC KEY-----\n" -"MIIBigKCAYEAtB+yw4BNxtZAG4cPaedkhWNmeij7IuNWmXjh58ZYEGurvGyHs1w4\n" -"QlwNYI2UftSIeIGdWZ5fJ17h9P3xvO6eeJuOt4KPrNOxUbSGrELEx1Lje1fDAJ1X\n" -"SvN+dvptusxtyFUr8afgTPrFIvYuazQ6q/Rw+NDagjmDx3h/A/enihpBnjwzeH8j\n" -"Xzu7b+HKnzFnNfveTDdvSy0NSC6tCOnrfXo31XbXRXtlesnMIpbJClUcAv55eyai\n" -"/PrVPCCUz8mk0sQnn2Xhv1YJmwOlQTGMfg0a0kWLmh+UWcHsGQ4VWxBZJcuzgFHG\n" -"hu2/Fz6DXSpX5Q6B9HKoGmnH1oBh24l0kUW1jL8BxPY4YDU1Lt5t3qgcDn9dXYcI\n" -"o8VvyI0ecSc26Q2PYFWX1hpN4VIBZ8uGaW3IpyTdNiRq0g3iMGRFEXcDlWuyMB9E\n" -"EbSM7m/79V/z7SjDd75EP8Z0qDPESEVB8a8LbuSJtzFVE0KHd7RzkIEN5sorXspZ\n" -"/THukftSmkIvAgMBAAE=\n" +"MIIBigKCAYEAuzPA82lRVUAc1uZgfDehhK0rBU5xt+qhJXUSH0DxsuocYCLW//q+\n" +"7+L7q9SochqZK3R5+SxJaZRlVK4rAeIHsxXFxsnGvuqasGM3he80EV1RpVRkvLaO\n" +"2dDmHcfEjYBadft2DEq811yvqSRqbFXmK0hLucA6LI6NnEw9VNWlguaV6ACVLyKQ\n" +"iYVFz2JOJIAi0Zz57WZg7eHypUAGoyXjtYTJPsh6pUe/0NLFJVd3JHcJX+bNqU2a\n" +"QU37r+CQ9f3T+8fZGJQ/CXNnYUNHa0j+toOFuPEiZBBh8C4PE7FJWjidvhe9uI7T\n" +"Py41RZhy8e05MAQmUBNRKBHWPKHoy2zWZZxTkcfWFdJJz/dzsNrIjrqf2fYId9To\n" +"fDpHzYd/UjzZaaVYRVS/Oyf3pN8DKw8LMhEArS0X9pblPVkWWjmYMU6f0VR7pelc\n" +"gGYuML3gOiKdNbeMWgAv3HNRsVsuW0HZLrhXUGYzTRPJ/GxVCwA/NmYgMTNVWRwF\n" +"7M78YHpayyEPAgMBAAE=\n" "-----END RSA PUBLIC KEY-----\n" "dir-signing-key\n" "-----BEGIN RSA PUBLIC KEY-----\n" -"MIGJAoGBANrSZlUq38Boz3iuUOydYTJV57rTbq1bz805FP2QG2Z+2bwpgKIOZag/\n" -"gN2A1ySJaIYLgZIg9irxrLkqlY/UAjC23y6V9fJXP1S3TXoqLmHleW8PsaDLuwTo\n" -"hCWaR61Mx9WG7IXcodn2Z7RiCfZpSW4Rztbk5WtjQa5jPXSFOuBJAgMBAAE=\n" +"MIGJAoGBANESf/hRRWCK3TLQyNb9Y42tYedCORUc8Rl+Q4wrvdz3R0TNr6rztE9N\n" +"u8v3Wbvjtiqm1xL1I5PaOObFQQj61QZxKiCm1yU4eFH15dNmcvBEy5BjEXVYiDgy\n" +"zKRyePzjHYQIZF3ZaQTABUplkXVpY0YvAurluhEy+dKEvZMwWFZTAgMBAAE=\n" "-----END RSA PUBLIC KEY-----\n" +"dir-key-crosscert\n" +"-----BEGIN ID SIGNATURE-----\n" +"NHNBya6Dt7Ww3qSGA0DBEl6pZFBzmYXM+QdqF+ESpdyYCQ54EYimaxl4VcXoGaxy\n" +"xk8/VOXPC6h7hVnTWDTsC86G6eXug1yzpd/uhQbcDJMH5q8/Yg5WXGOnGhMWNCBh\n" +"u2UmbtAjdjLrObQaB50FfOpuOV9kdG4SEzaPUBR2ayU=\n" +"-----END ID SIGNATURE-----\n" "dir-key-certification\n" "-----BEGIN SIGNATURE-----\n" -"UNXZy+4OQ8iat+gw+vg2ynvKj2BYbqZt+EAZAV3rmw6gux44U9TLRECRd6LsA08N\n" -"4+Vz01TU81xqMgfrUy94ei2YvcfpO8art9/muWHTP9SmOX8S1uqDqLWA+n723C9A\n" -"HyVXn4aINncO2081gJcIW5+Ul8WTCeZe/n3LVPTCKbTdqxvmrPUdCWlJTQUmb19M\n" -"T+kcCjaEfgQGLC+Y2MHqYe/nxz+aBKqpjiWUDdjc35va6r/2e3c0jGi1B1xRZxN1\n" -"xThPZ+CifjDoWBxJdDGlIfZRK1lMnOCJY9w9ibTXQ1UnvE4whFvmB55/t9/XLq4q\n" -"3pnZz0H7funey3+ilmTxDohoAYT1GX+4a+3xYH07UmAFqlTzqKClj84XEHn+Cer7\n" -"Nun9kJlJFuBgUpQjwCkzedFZKKLOHgB2h7trJfnqcBpAM8Rup1Bb5u/RcBx9gy1q\n" -"pMc65FviIrc/Q5TUku6NNbCbnGll1599PvWuUzkG42lJ17V6psKHIsqGtVdHlCUc\n" +"NocTkLl9iKglVo+yrpY0slsqgPviuScMyEfOJ3i65KeJb4Dr1huIs0Fip40zFD8D\n" +"cz/SYu09FbANuRwBJIRdVWZLLwVFLBj5F8U65iJRAPBw/O/xgSVBvWoOhBUZqmJA\n" +"Jp1IUutQHYFfnAOO9za4r8Ox6yPaOWF9Ks5gL0kU/fI8Bdi5E9p3e9fMtoM7hROg\n" +"oX1AoV/za3LcM0oMsGsdXQ7B8vRqY0eUX523kpRpF1fUDyvBUvvMsXdZDN6anCV6\n" +"NtSq2UaM/msTX1oQ8gzyD1gMXH0Ek26YMhd+6WZE6KUeb1x5HJgXtKtYzMLB6nQM\n" +"4Q/OA4NND/Veflofy6xx8uzXe8H+MoUHK9WiORtwqvBl0E9qk6SVCuo4ipR4Ybgk\n" +"PAFOXA58j80dlNYYEVgV8MXF1Y/g/thuXlf2dWiLAExdHTtE0AzC4quWshegaImC\n" +"4aziHeA43TRDszAXcJorREAM0AhSxp3aWDde4Jt46ODOJR8t+gHreks29eDttEIn\n" "-----END SIGNATURE-----\n"; /** The private signing key for AUTHORITY_CERT_3 */ -const char AUTHORITY_SIGNKEY_3[] = +const char AUTHORITY_SIGNKEY_C[] = "-----BEGIN RSA PRIVATE KEY-----\n" -"MIICXgIBAAKBgQDa0mZVKt/AaM94rlDsnWEyVee6026tW8/NORT9kBtmftm8KYCi\n" -"DmWoP4DdgNckiWiGC4GSIPYq8ay5KpWP1AIwtt8ulfXyVz9Ut016Ki5h5XlvD7Gg\n" -"y7sE6IQlmketTMfVhuyF3KHZ9me0Ygn2aUluEc7W5OVrY0GuYz10hTrgSQIDAQAB\n" -"AoGBAIyoeG1AnQmildKeQpiGZackf0uhg2BeRwpFKg//5Q0Sd0Wza+M/2+q1v1Ei\n" -"86ihxxV7KfPTykk6hmuUSwVkI28Z+5J9NYTr35EzPiUlqpo0iclTkFqrlbqSPULx\n" -"9fQhvcOGv1c0m5CnYrHsM8eu3tagLg+6OE4abLOYX4Az5pkxAkEA/NwHhVaVJrXH\n" -"lGDrRAfGtaD5Tzeeg1H9DNZi5lmFiSNR0O11sgDLkiZNP5oM8knyqo8Gq08hwxEb\n" -"yqMXM3XtJQJBAN2KJbFhOjDIkvJyYvbmcP6P7vV2c9j+oUTKkFMF7vvfWunxMi9j\n" -"ghbdUKgl7tU0VFpw7ufDDD0pkN6sua3gp1UCQQCvNzTK861U7p/GtMYyFQVf9JTt\n" -"jMf9jYHBNInBvwTme6AFG5bz6tMlif77dJ9GAXHzODrR2Hq3thJA/3RjR3M1AkBg\n" -"+6M4ncmtpYC+5lhwob0Bk90WU/6vFflfdhXsYoKWfNb95vsDR9qhS82Nbt25NClh\n" -"VmMfzoFDHTkwYgj/F4PpAkEA+RaaSRP7BmbvFNqvlm8J/m0RVdAH4+p/Q5Z5u6Yo\n" -"N7xC/gFi0qFPGKsDvD2CncAYmt+KNsd8S0JGDN4eieKn+Q==\n" +"MIICXAIBAAKBgQDREn/4UUVgit0y0MjW/WONrWHnQjkVHPEZfkOMK73c90dEza+q\n" +"87RPTbvL91m747YqptcS9SOT2jjmxUEI+tUGcSogptclOHhR9eXTZnLwRMuQYxF1\n" +"WIg4Msykcnj84x2ECGRd2WkEwAVKZZF1aWNGLwLq5boRMvnShL2TMFhWUwIDAQAB\n" +"AoGAU68L+eDN3C65CzX2rdcOmg7kOSSQpJrJBmM7tkdr3546sJeD0PFrIrMCkEmZ\n" +"aVNj/v545+WnL+8RB4280lNUIF4AMNaMZUL+4FAtwekqWua3QvvqgRMjCdG3/h/d\n" +"bOAUiiKKEimflTaIVHNVSCvOIntftOu3PhebctuabnZzg0ECQQD9i+FX7M9UXT1A\n" +"bVm+bRIJuQtG+u9jD3VxrvHsmh0QnOAL3oa/ofTCwoTJLZs8Qy0GeAoJNf28rY1q\n" +"AgNMEeEXAkEA0xhxNX2fDQ2yvKwPkPMrRycJVWry+KHvSZG2+XYh+V5sVGQ5H7Gu\n" +"krc6IzRZlIKQhEGktkw8ih0DEHQbAihiJQJBAKi/SnFcePjrPXL91Hb63MB/2dOZ\n" +"+21wwnexOe6A+8ssvajop8IvJlnhYMMMiX7oLrVZe0R6HLBQyge94zfjxm0CQGye\n" +"dRIrE34qAEBo4JGbLjesdHcJUwBwgqn+WoI+MPkZhvBdqa8PRF6l/TpEI5vxGt+S\n" +"z2gmDjia+QqsU4FmuikCQDDOs85uwNSKJFax9XMzd1qd1QwX20F8lvnOsWErXiDw\n" +"Fy2+rmIRHoSxn4D+rE5ivqkO99E9jAlz+uuQz/6WqwE=\n" "-----END RSA PRIVATE KEY-----\n"; diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 56ac3b34c..9b83e768f 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -214,8 +214,10 @@ test_dir_formats(void) strlcat(buf2, "signing-key\n", sizeof(buf2)); strlcat(buf2, pk1_str, sizeof(buf2)); strlcat(buf2, "hidden-service-dir\n", sizeof(buf2)); +#ifdef CURVE25519_ENABLED strlcat(buf2, "ntor-onion-key " "skyinAnvardNostarsNomoonNowindormistsorsnow=\n", sizeof(buf2)); +#endif strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2)); strlcat(buf2, "router-signature\n", sizeof(buf2)); @@ -235,9 +237,11 @@ test_dir_formats(void) test_eq(rp2->bandwidthrate, r2->bandwidthrate); test_eq(rp2->bandwidthburst, r2->bandwidthburst); test_eq(rp2->bandwidthcapacity, r2->bandwidthcapacity); +#ifdef CURVE25519_ENABLED test_memeq(rp2->onion_curve25519_pkey->public_key, r2->onion_curve25519_pkey->public_key, CURVE25519_PUBKEY_LEN); +#endif test_assert(crypto_pk_cmp_keys(rp2->onion_pkey, pk2) == 0); test_assert(crypto_pk_cmp_keys(rp2->identity_pkey, pk1) == 0); @@ -1244,7 +1248,6 @@ test_a_networkstatus( /* Parse certificates and keys. */ cert1 = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); test_assert(cert1); - test_assert(cert1->is_cross_certified); cert2 = authority_cert_parse_from_string(AUTHORITY_CERT_2, NULL); test_assert(cert2); cert3 = authority_cert_parse_from_string(AUTHORITY_CERT_3, NULL); @@ -2317,9 +2320,9 @@ test_dir_v2_dir(void *arg) /* Make a directory so there's somewhere to store the thing */ #ifdef _WIN32 - mkdir(get_fname("cached-status")); + tt_int_op(mkdir(get_fname("cached-status")), ==, 0); #else - mkdir(get_fname("cached-status"), 0700); + tt_int_op(mkdir(get_fname("cached-status"), 0700), ==, 0); #endif v2 = generate_v2_networkstatus_opinion(); @@ -2364,6 +2367,74 @@ test_dir_fmt_control_ns(void *arg) tor_free(s); } +static void +test_dir_http_handling(void *args) +{ + char *url = NULL; + (void)args; + + /* Parse http url tests: */ + /* Good headers */ + test_eq(parse_http_url("GET /tor/a/b/c.txt HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Mozilla/5.0 (Windows;" + " U; Windows NT 6.1; en-US; rv:1.9.1.5)\r\n", + &url), 0); + test_streq(url, "/tor/a/b/c.txt"); + tor_free(url); + + test_eq(parse_http_url("GET /tor/a/b/c.txt HTTP/1.0\r\n", &url), 0); + test_streq(url, "/tor/a/b/c.txt"); + tor_free(url); + + test_eq(parse_http_url("GET /tor/a/b/c.txt HTTP/1.600\r\n", &url), 0); + test_streq(url, "/tor/a/b/c.txt"); + tor_free(url); + + /* Should prepend '/tor/' to url if required */ + test_eq(parse_http_url("GET /a/b/c.txt HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Mozilla/5.0 (Windows;" + " U; Windows NT 6.1; en-US; rv:1.9.1.5)\r\n", + &url), 0); + test_streq(url, "/tor/a/b/c.txt"); + tor_free(url); + + /* Bad headers -- no HTTP/1.x*/ + test_eq(parse_http_url("GET /a/b/c.txt\r\n" + "Host: example.com\r\n" + "User-Agent: Mozilla/5.0 (Windows;" + " U; Windows NT 6.1; en-US; rv:1.9.1.5)\r\n", + &url), -1); + tt_assert(!url); + + /* Bad headers */ + test_eq(parse_http_url("GET /a/b/c.txt\r\n" + "Host: example.com\r\n" + "User-Agent: Mozilla/5.0 (Windows;" + " U; Windows NT 6.1; en-US; rv:1.9.1.5)\r\n", + &url), -1); + tt_assert(!url); + + test_eq(parse_http_url("GET /tor/a/b/c.txt", &url), -1); + tt_assert(!url); + + test_eq(parse_http_url("GET /tor/a/b/c.txt HTTP/1.1", &url), -1); + tt_assert(!url); + + test_eq(parse_http_url("GET /tor/a/b/c.txt HTTP/1.1x\r\n", &url), -1); + tt_assert(!url); + + test_eq(parse_http_url("GET /tor/a/b/c.txt HTTP/1.", &url), -1); + tt_assert(!url); + + test_eq(parse_http_url("GET /tor/a/b/c.txt HTTP/1.\r", &url), -1); + tt_assert(!url); + + done: + tor_free(url); +} + #define DIR_LEGACY(name) \ { #name, legacy_test_helper, TT_FORK, &legacy_setup, test_dir_ ## name } @@ -2386,6 +2457,7 @@ struct testcase_t dir_tests[] = { DIR_LEGACY(clip_unmeasured_bw_kb_alt), DIR(v2_dir, TT_FORK), DIR(fmt_control_ns, 0), + DIR(http_handling, 0), END_OF_TESTCASES }; diff --git a/src/test/test_extorport.c b/src/test/test_extorport.c new file mode 100644 index 000000000..7e38ba57d --- /dev/null +++ b/src/test/test_extorport.c @@ -0,0 +1,604 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CONNECTION_PRIVATE +#define EXT_ORPORT_PRIVATE +#define MAIN_PRIVATE +#include "or.h" +#include "buffers.h" +#include "connection.h" +#include "connection_or.h" +#include "config.h" +#include "control.h" +#include "ext_orport.h" +#include "main.h" +#include "test.h" + +/* Test connection_or_remove_from_ext_or_id_map and + * connection_or_set_ext_or_identifier */ +static void +test_ext_or_id_map(void *arg) +{ + or_connection_t *c1 = NULL, *c2 = NULL, *c3 = NULL; + char *idp = NULL, *idp2 = NULL; + (void)arg; + + /* pre-initialization */ + tt_ptr_op(NULL, ==, connection_or_get_by_ext_or_id("xxxxxxxxxxxxxxxxxxxx")); + + c1 = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + c2 = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + c3 = or_connection_new(CONN_TYPE_OR, AF_INET); + + tt_ptr_op(c1->ext_or_conn_id, !=, NULL); + tt_ptr_op(c2->ext_or_conn_id, !=, NULL); + tt_ptr_op(c3->ext_or_conn_id, ==, NULL); + + tt_ptr_op(c1, ==, connection_or_get_by_ext_or_id(c1->ext_or_conn_id)); + tt_ptr_op(c2, ==, connection_or_get_by_ext_or_id(c2->ext_or_conn_id)); + tt_ptr_op(NULL, ==, connection_or_get_by_ext_or_id("xxxxxxxxxxxxxxxxxxxx")); + + idp = tor_memdup(c2->ext_or_conn_id, EXT_OR_CONN_ID_LEN); + + /* Give c2 a new ID. */ + connection_or_set_ext_or_identifier(c2); + test_mem_op(idp, !=, c2->ext_or_conn_id, EXT_OR_CONN_ID_LEN); + idp2 = tor_memdup(c2->ext_or_conn_id, EXT_OR_CONN_ID_LEN); + tt_assert(!tor_digest_is_zero(idp2)); + + tt_ptr_op(NULL, ==, connection_or_get_by_ext_or_id(idp)); + tt_ptr_op(c2, ==, connection_or_get_by_ext_or_id(idp2)); + + /* Now remove it. */ + connection_or_remove_from_ext_or_id_map(c2); + tt_ptr_op(NULL, ==, connection_or_get_by_ext_or_id(idp)); + tt_ptr_op(NULL, ==, connection_or_get_by_ext_or_id(idp2)); + + done: + if (c1) + connection_free_(TO_CONN(c1)); + if (c2) + connection_free_(TO_CONN(c2)); + if (c3) + connection_free_(TO_CONN(c3)); + tor_free(idp); + tor_free(idp2); + connection_or_clear_ext_or_id_map(); +} + +/* Simple connection_write_to_buf_impl_ replacement that unconditionally + * writes to outbuf. */ +static void +connection_write_to_buf_impl_replacement(const char *string, size_t len, + connection_t *conn, int zlib) +{ + (void) zlib; + + tor_assert(string); + tor_assert(conn); + write_to_buf(string, len, conn->outbuf); +} + +static char * +buf_get_contents(buf_t *buf, size_t *sz_out) +{ + char *out; + *sz_out = buf_datalen(buf); + if (*sz_out >= ULONG_MAX) + return NULL; /* C'mon, really? */ + out = tor_malloc(*sz_out + 1); + if (fetch_from_buf(out, (unsigned long)*sz_out, buf) != 0) { + tor_free(out); + return NULL; + } + out[*sz_out] = '\0'; /* Hopefully gratuitous. */ + return out; +} + +static void +test_ext_or_write_command(void *arg) +{ + or_connection_t *c1; + char *cp = NULL; + char *buf = NULL; + size_t sz; + + (void) arg; + MOCK(connection_write_to_buf_impl_, + connection_write_to_buf_impl_replacement); + + c1 = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + tt_assert(c1); + + /* Length too long */ + tt_int_op(connection_write_ext_or_command(TO_CONN(c1), 100, "X", 100000), + <, 0); + + /* Empty command */ + tt_int_op(connection_write_ext_or_command(TO_CONN(c1), 0x99, NULL, 0), + ==, 0); + cp = buf_get_contents(TO_CONN(c1)->outbuf, &sz); + tt_int_op(sz, ==, 4); + test_mem_op(cp, ==, "\x00\x99\x00\x00", 4); + tor_free(cp); + + /* Medium command. */ + tt_int_op(connection_write_ext_or_command(TO_CONN(c1), 0x99, + "Wai\0Hello", 9), ==, 0); + cp = buf_get_contents(TO_CONN(c1)->outbuf, &sz); + tt_int_op(sz, ==, 13); + test_mem_op(cp, ==, "\x00\x99\x00\x09Wai\x00Hello", 13); + tor_free(cp); + + /* Long command */ + buf = tor_malloc(65535); + memset(buf, 'x', 65535); + tt_int_op(connection_write_ext_or_command(TO_CONN(c1), 0xf00d, + buf, 65535), ==, 0); + cp = buf_get_contents(TO_CONN(c1)->outbuf, &sz); + tt_int_op(sz, ==, 65539); + test_mem_op(cp, ==, "\xf0\x0d\xff\xff", 4); + test_mem_op(cp+4, ==, buf, 65535); + tor_free(cp); + + done: + if (c1) + connection_free_(TO_CONN(c1)); + tor_free(cp); + tor_free(buf); + UNMOCK(connection_write_to_buf_impl_); +} + +static int +write_bytes_to_file_fail(const char *fname, const char *str, size_t len, + int bin) +{ + (void) fname; + (void) str; + (void) len; + (void) bin; + + return -1; +} + +static void +test_ext_or_init_auth(void *arg) +{ + or_options_t *options = get_options_mutable(); + const char *fn; + char *cp = NULL; + struct stat st; + char cookie0[32]; + (void)arg; + + /* Check default filename location */ + options->DataDirectory = tor_strdup("foo"); + cp = get_ext_or_auth_cookie_file_name(); + tt_str_op(cp, ==, "foo"PATH_SEPARATOR"extended_orport_auth_cookie"); + tor_free(cp); + + /* Shouldn't be initialized already, or our tests will be a bit + * meaningless */ + ext_or_auth_cookie = tor_malloc_zero(32); + test_assert(tor_mem_is_zero((char*)ext_or_auth_cookie, 32)); + + /* Now make sure we use a temporary file */ + fn = get_fname("ext_cookie_file"); + options->ExtORPortCookieAuthFile = tor_strdup(fn); + cp = get_ext_or_auth_cookie_file_name(); + tt_str_op(cp, ==, fn); + tor_free(cp); + + /* Test the initialization function with a broken + write_bytes_to_file(). See if the problem is handled properly. */ + MOCK(write_bytes_to_file, write_bytes_to_file_fail); + tt_int_op(-1, ==, init_ext_or_cookie_authentication(1)); + tt_int_op(ext_or_auth_cookie_is_set, ==, 0); + UNMOCK(write_bytes_to_file); + + /* Now do the actual initialization. */ + tt_int_op(0, ==, init_ext_or_cookie_authentication(1)); + tt_int_op(ext_or_auth_cookie_is_set, ==, 1); + cp = read_file_to_str(fn, RFTS_BIN, &st); + tt_ptr_op(cp, !=, NULL); + tt_int_op(st.st_size, ==, 64); + test_memeq(cp, "! Extended ORPort Auth Cookie !\x0a", 32); + test_memeq(cp+32, ext_or_auth_cookie, 32); + memcpy(cookie0, ext_or_auth_cookie, 32); + test_assert(!tor_mem_is_zero((char*)ext_or_auth_cookie, 32)); + + /* Operation should be idempotent. */ + tt_int_op(0, ==, init_ext_or_cookie_authentication(1)); + test_memeq(cookie0, ext_or_auth_cookie, 32); + + done: + tor_free(cp); + ext_orport_free_all(); +} + +static void +test_ext_or_cookie_auth(void *arg) +{ + char *reply=NULL, *reply2=NULL, *client_hash=NULL, *client_hash2=NULL; + size_t reply_len=0; + char hmac1[32], hmac2[32]; + + const char client_nonce[32] = + "Who is the third who walks alway"; + char server_hash_input[] = + "ExtORPort authentication server-to-client hash" + "Who is the third who walks alway" + "................................"; + char client_hash_input[] = + "ExtORPort authentication client-to-server hash" + "Who is the third who walks alway" + "................................"; + + (void)arg; + + tt_int_op(strlen(client_hash_input), ==, 46+32+32); + tt_int_op(strlen(server_hash_input), ==, 46+32+32); + + ext_or_auth_cookie = tor_malloc_zero(32); + memcpy(ext_or_auth_cookie, "s beside you? When I count, ther", 32); + ext_or_auth_cookie_is_set = 1; + + /* For this authentication, the client sends 32 random bytes (ClientNonce) + * The server replies with 32 byte ServerHash and 32 byte ServerNonce, + * where ServerHash is: + * HMAC-SHA256(CookieString, + * "ExtORPort authentication server-to-client hash" | ClientNonce | + * ServerNonce)" + * The client must reply with 32-byte ClientHash, which we compute as: + * ClientHash is computed as: + * HMAC-SHA256(CookieString, + * "ExtORPort authentication client-to-server hash" | ClientNonce | + * ServerNonce) + */ + + /* Wrong length */ + tt_int_op(-1, ==, + handle_client_auth_nonce(client_nonce, 33, &client_hash, &reply, + &reply_len)); + tt_int_op(-1, ==, + handle_client_auth_nonce(client_nonce, 31, &client_hash, &reply, + &reply_len)); + + /* Now let's try this for real! */ + tt_int_op(0, ==, + handle_client_auth_nonce(client_nonce, 32, &client_hash, &reply, + &reply_len)); + tt_int_op(reply_len, ==, 64); + tt_ptr_op(reply, !=, NULL); + tt_ptr_op(client_hash, !=, NULL); + /* Fill in the server nonce into the hash inputs... */ + memcpy(server_hash_input+46+32, reply+32, 32); + memcpy(client_hash_input+46+32, reply+32, 32); + /* Check the HMACs are correct... */ + crypto_hmac_sha256(hmac1, (char*)ext_or_auth_cookie, 32, server_hash_input, + 46+32+32); + crypto_hmac_sha256(hmac2, (char*)ext_or_auth_cookie, 32, client_hash_input, + 46+32+32); + test_memeq(hmac1, reply, 32); + test_memeq(hmac2, client_hash, 32); + + /* Now do it again and make sure that the results are *different* */ + tt_int_op(0, ==, + handle_client_auth_nonce(client_nonce, 32, &client_hash2, &reply2, + &reply_len)); + test_memneq(reply2, reply, reply_len); + test_memneq(client_hash2, client_hash, 32); + /* But that this one checks out too. */ + memcpy(server_hash_input+46+32, reply2+32, 32); + memcpy(client_hash_input+46+32, reply2+32, 32); + /* Check the HMACs are correct... */ + crypto_hmac_sha256(hmac1, (char*)ext_or_auth_cookie, 32, server_hash_input, + 46+32+32); + crypto_hmac_sha256(hmac2, (char*)ext_or_auth_cookie, 32, client_hash_input, + 46+32+32); + test_memeq(hmac1, reply2, 32); + test_memeq(hmac2, client_hash2, 32); + + done: + tor_free(reply); + tor_free(client_hash); + tor_free(reply2); + tor_free(client_hash2); +} + +static int +crypto_rand_return_tse_str(char *to, size_t n) +{ + if (n != 32) { + TT_FAIL(("Asked for %d bytes, not 32", (int)n)); + return -1; + } + memcpy(to, "te road There is always another ", 32); + return 0; +} + +static void +test_ext_or_cookie_auth_testvec(void *arg) +{ + char *reply=NULL, *client_hash=NULL; + size_t reply_len; + char *mem_op_hex_tmp=NULL; + + const char client_nonce[] = "But when I look ahead up the whi"; + (void)arg; + + ext_or_auth_cookie = tor_malloc_zero(32); + memcpy(ext_or_auth_cookie, "Gliding wrapt in a brown mantle," , 32); + ext_or_auth_cookie_is_set = 1; + + MOCK(crypto_rand, crypto_rand_return_tse_str); + + tt_int_op(0, ==, + handle_client_auth_nonce(client_nonce, 32, &client_hash, &reply, + &reply_len)); + tt_ptr_op(reply, !=, NULL ); + tt_ptr_op(reply_len, ==, 64); + test_memeq(reply+32, "te road There is always another ", 32); + /* HMACSHA256("Gliding wrapt in a brown mantle," + * "ExtORPort authentication server-to-client hash" + * "But when I look ahead up the write road There is always another "); + */ + test_memeq_hex(reply, + "ec80ed6e546d3b36fdfc22fe1315416b" + "029f1ade7610d910878b62eeb7403821"); + /* HMACSHA256("Gliding wrapt in a brown mantle," + * "ExtORPort authentication client-to-server hash" + * "But when I look ahead up the write road There is always another "); + * (Both values computed using Python CLI.) + */ + test_memeq_hex(client_hash, + "ab391732dd2ed968cd40c087d1b1f25b" + "33b3cd77ff79bd80c2074bbf438119a2"); + + done: + UNMOCK(crypto_rand); + tor_free(reply); + tor_free(client_hash); + tor_free(mem_op_hex_tmp); +} + +static void +ignore_bootstrap_problem(const char *warn, int reason) +{ + (void)warn; + (void)reason; +} + +static int is_reading = 1; +static int handshake_start_called = 0; + +static void +note_read_stopped(connection_t *conn) +{ + (void)conn; + is_reading=0; +} +static void +note_read_started(connection_t *conn) +{ + (void)conn; + is_reading=1; +} +static int +handshake_start(or_connection_t *conn, int receiving) +{ + if (!conn || !receiving) + TT_FAIL(("Bad arguments to handshake_start")); + handshake_start_called = 1; + return 0; +} + +#define WRITE(s,n) \ + do { \ + write_to_buf((s), (n), TO_CONN(conn)->inbuf); \ + } while (0) +#define CONTAINS(s,n) \ + do { \ + tt_int_op((n), <=, sizeof(b)); \ + tt_int_op(buf_datalen(TO_CONN(conn)->outbuf), ==, (n)); \ + if ((n)) { \ + fetch_from_buf(b, (n), TO_CONN(conn)->outbuf); \ + test_memeq(b, (s), (n)); \ + } \ + } while (0) + +/* Helper: Do a successful Extended ORPort authentication handshake. */ +static void +do_ext_or_handshake(or_connection_t *conn) +{ + char b[256]; + + tt_int_op(0, ==, connection_ext_or_start_auth(conn)); + CONTAINS("\x01\x00", 2); + WRITE("\x01", 1); + WRITE("But when I look ahead up the whi", 32); + MOCK(crypto_rand, crypto_rand_return_tse_str); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + UNMOCK(crypto_rand); + tt_int_op(TO_CONN(conn)->state, ==, EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH); + CONTAINS("\xec\x80\xed\x6e\x54\x6d\x3b\x36\xfd\xfc\x22\xfe\x13\x15\x41\x6b" + "\x02\x9f\x1a\xde\x76\x10\xd9\x10\x87\x8b\x62\xee\xb7\x40\x38\x21" + "te road There is always another ", 64); + /* Send the right response this time. */ + WRITE("\xab\x39\x17\x32\xdd\x2e\xd9\x68\xcd\x40\xc0\x87\xd1\xb1\xf2\x5b" + "\x33\xb3\xcd\x77\xff\x79\xbd\x80\xc2\x07\x4b\xbf\x43\x81\x19\xa2", + 32); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("\x01", 1); + tt_assert(! TO_CONN(conn)->marked_for_close); + tt_int_op(TO_CONN(conn)->state, ==, EXT_OR_CONN_STATE_OPEN); + + done: ; +} + +static void +test_ext_or_handshake(void *arg) +{ + or_connection_t *conn=NULL; + char b[256]; + + (void) arg; + MOCK(connection_write_to_buf_impl_, + connection_write_to_buf_impl_replacement); + /* Use same authenticators as for test_ext_or_cookie_auth_testvec */ + ext_or_auth_cookie = tor_malloc_zero(32); + memcpy(ext_or_auth_cookie, "Gliding wrapt in a brown mantle," , 32); + ext_or_auth_cookie_is_set = 1; + + init_connection_lists(); + + conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + tt_int_op(0, ==, connection_ext_or_start_auth(conn)); + /* The server starts by telling us about the one supported authtype. */ + CONTAINS("\x01\x00", 2); + /* Say the client hasn't responded yet. */ + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + /* Let's say the client replies badly. */ + WRITE("\x99", 1); + tt_int_op(-1, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("", 0); + tt_assert(TO_CONN(conn)->marked_for_close); + close_closeable_connections(); + conn = NULL; + + /* Okay, try again. */ + conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + tt_int_op(0, ==, connection_ext_or_start_auth(conn)); + CONTAINS("\x01\x00", 2); + /* Let's say the client replies sensibly this time. "Yes, AUTHTYPE_COOKIE + * sounds delicious. Let's have some of that!" */ + WRITE("\x01", 1); + /* Let's say that the client also sends part of a nonce. */ + WRITE("But when I look ", 16); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("", 0); + tt_int_op(TO_CONN(conn)->state, ==, + EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE); + /* Pump it again. Nothing should happen. */ + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + /* send the rest of the nonce. */ + WRITE("ahead up the whi", 16); + MOCK(crypto_rand, crypto_rand_return_tse_str); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + UNMOCK(crypto_rand); + /* We should get the right reply from the server. */ + CONTAINS("\xec\x80\xed\x6e\x54\x6d\x3b\x36\xfd\xfc\x22\xfe\x13\x15\x41\x6b" + "\x02\x9f\x1a\xde\x76\x10\xd9\x10\x87\x8b\x62\xee\xb7\x40\x38\x21" + "te road There is always another ", 64); + /* Send the wrong response. */ + WRITE("not with a bang but a whimper...", 32); + MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + tt_int_op(-1, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("\x00", 1); + tt_assert(TO_CONN(conn)->marked_for_close); + /* XXXX Hold-open-until-flushed. */ + close_closeable_connections(); + conn = NULL; + UNMOCK(control_event_bootstrap_problem); + + MOCK(connection_start_reading, note_read_started); + MOCK(connection_stop_reading, note_read_stopped); + MOCK(connection_tls_start_handshake, handshake_start); + + /* Okay, this time let's succeed. */ + conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + do_ext_or_handshake(conn); + + /* Now let's run through some messages. */ + /* First let's send some junk and make sure it's ignored. */ + WRITE("\xff\xf0\x00\x03""ABC", 7); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("", 0); + /* Now let's send a USERADDR command. */ + WRITE("\x00\x01\x00\x0c""1.2.3.4:5678", 16); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + tt_int_op(TO_CONN(conn)->port, ==, 5678); + tt_int_op(tor_addr_to_ipv4h(&TO_CONN(conn)->addr), ==, 0x01020304); + /* Now let's send a TRANSPORT command. */ + WRITE("\x00\x02\x00\x07""rfc1149", 11); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + tt_ptr_op(NULL, !=, conn->ext_or_transport); + tt_str_op("rfc1149", ==, conn->ext_or_transport); + tt_int_op(is_reading,==,1); + tt_int_op(TO_CONN(conn)->state, ==, EXT_OR_CONN_STATE_OPEN); + /* DONE */ + WRITE("\x00\x00\x00\x00", 4); + tt_int_op(0, ==, connection_ext_or_process_inbuf(conn)); + tt_int_op(TO_CONN(conn)->state, ==, EXT_OR_CONN_STATE_FLUSHING); + tt_int_op(is_reading,==,0); + CONTAINS("\x10\x00\x00\x00", 4); + tt_int_op(handshake_start_called,==,0); + tt_int_op(0, ==, connection_ext_or_finished_flushing(conn)); + tt_int_op(is_reading,==,1); + tt_int_op(handshake_start_called,==,1); + tt_int_op(TO_CONN(conn)->type, ==, CONN_TYPE_OR); + tt_int_op(TO_CONN(conn)->state, ==, 0); + close_closeable_connections(); + conn = NULL; + + /* Okay, this time let's succeed the handshake but fail the USERADDR + command. */ + conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + do_ext_or_handshake(conn); + /* USERADDR command with an extra NUL byte */ + WRITE("\x00\x01\x00\x0d""1.2.3.4:5678\x00", 17); + MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + tt_int_op(-1, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("", 0); + tt_assert(TO_CONN(conn)->marked_for_close); + close_closeable_connections(); + conn = NULL; + UNMOCK(control_event_bootstrap_problem); + + /* Now fail the TRANSPORT command. */ + conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + do_ext_or_handshake(conn); + /* TRANSPORT command with an extra NUL byte */ + WRITE("\x00\x02\x00\x08""rfc1149\x00", 12); + MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + tt_int_op(-1, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("", 0); + tt_assert(TO_CONN(conn)->marked_for_close); + close_closeable_connections(); + conn = NULL; + UNMOCK(control_event_bootstrap_problem); + + /* Now fail the TRANSPORT command. */ + conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); + do_ext_or_handshake(conn); + /* TRANSPORT command with transport name with symbols (not a + C-identifier) */ + WRITE("\x00\x02\x00\x07""rf*1149", 11); + MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + tt_int_op(-1, ==, connection_ext_or_process_inbuf(conn)); + CONTAINS("", 0); + tt_assert(TO_CONN(conn)->marked_for_close); + close_closeable_connections(); + conn = NULL; + UNMOCK(control_event_bootstrap_problem); + + done: + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(crypto_rand); + if (conn) + connection_free_(TO_CONN(conn)); +#undef CONTAINS +#undef WRITE +} + +struct testcase_t extorport_tests[] = { + { "id_map", test_ext_or_id_map, TT_FORK, NULL, NULL }, + { "write_command", test_ext_or_write_command, TT_FORK, NULL, NULL }, + { "init_auth", test_ext_or_init_auth, TT_FORK, NULL, NULL }, + { "cookie_auth", test_ext_or_cookie_auth, TT_FORK, NULL, NULL }, + { "cookie_auth_testvec", test_ext_or_cookie_auth_testvec, TT_FORK, + NULL, NULL }, + { "handshake", test_ext_or_handshake, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_logging.c b/src/test/test_logging.c new file mode 100644 index 000000000..7e558f83b --- /dev/null +++ b/src/test/test_logging.c @@ -0,0 +1,135 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "or.h" +#include "torlog.h" +#include "test.h" + +static void +dummy_cb_fn(int severity, uint32_t domain, const char *msg) +{ + (void)severity; (void)domain; (void)msg; +} + +static void +test_get_sigsafe_err_fds(void *arg) +{ + const int *fds; + int n; + log_severity_list_t include_bug, no_bug, no_bug2; + (void) arg; + init_logging(); + + n = tor_log_get_sigsafe_err_fds(&fds); + tt_int_op(n, ==, 1); + tt_int_op(fds[0], ==, STDERR_FILENO); + + set_log_severity_config(LOG_WARN, LOG_ERR, &include_bug); + set_log_severity_config(LOG_WARN, LOG_ERR, &no_bug); + no_bug.masks[0] &= ~(LD_BUG|LD_GENERAL); + set_log_severity_config(LOG_INFO, LOG_NOTICE, &no_bug2); + + /* Add some logs; make sure the output is as expected. */ + mark_logs_temp(); + add_stream_log(&include_bug, "dummy-1", 3); + add_stream_log(&no_bug, "dummy-2", 4); + add_stream_log(&no_bug2, "dummy-3", 5); + add_callback_log(&include_bug, dummy_cb_fn); + close_temp_logs(); + tor_log_update_sigsafe_err_fds(); + + n = tor_log_get_sigsafe_err_fds(&fds); + tt_int_op(n, ==, 2); + tt_int_op(fds[0], ==, STDERR_FILENO); + tt_int_op(fds[1], ==, 3); + + /* Allow STDOUT to replace STDERR. */ + add_stream_log(&include_bug, "dummy-4", STDOUT_FILENO); + tor_log_update_sigsafe_err_fds(); + n = tor_log_get_sigsafe_err_fds(&fds); + tt_int_op(n, ==, 2); + tt_int_op(fds[0], ==, 3); + tt_int_op(fds[1], ==, STDOUT_FILENO); + + /* But don't allow it to replace explicit STDERR. */ + add_stream_log(&include_bug, "dummy-5", STDERR_FILENO); + tor_log_update_sigsafe_err_fds(); + n = tor_log_get_sigsafe_err_fds(&fds); + tt_int_op(n, ==, 3); + tt_int_op(fds[0], ==, STDERR_FILENO); + tt_int_op(fds[1], ==, STDOUT_FILENO); + tt_int_op(fds[2], ==, 3); + + /* Don't overflow the array. */ + { + int i; + for (i=5; i<20; ++i) { + add_stream_log(&include_bug, "x-dummy", i); + } + } + tor_log_update_sigsafe_err_fds(); + n = tor_log_get_sigsafe_err_fds(&fds); + tt_int_op(n, ==, 8); + + done: + ; +} + +static void +test_sigsafe_err(void *arg) +{ + const char *fn=get_fname("sigsafe_err_log"); + char *content=NULL; + log_severity_list_t include_bug; + smartlist_t *lines = smartlist_new(); + (void)arg; + + set_log_severity_config(LOG_WARN, LOG_ERR, &include_bug); + + init_logging(); + mark_logs_temp(); + add_file_log(&include_bug, fn); + tor_log_update_sigsafe_err_fds(); + close_temp_logs(); + + close(STDERR_FILENO); + log_err(LD_BUG, "Say, this isn't too cool."); + tor_log_err_sigsafe("Minimal.\n", NULL); + + set_log_time_granularity(100*1000); + tor_log_err_sigsafe("Testing any ", + "attempt to manually log ", + "from a signal.\n", + NULL); + mark_logs_temp(); + close_temp_logs(); + close(STDERR_FILENO); + content = read_file_to_str(fn, 0, NULL); + + tt_assert(content != NULL); + tor_split_lines(lines, content, (int)strlen(content)); + tt_int_op(smartlist_len(lines), >=, 5); + + if (strstr(smartlist_get(lines, 0), "opening new log file")) + smartlist_del_keeporder(lines, 0); + tt_assert(strstr(smartlist_get(lines, 0), "Say, this isn't too cool")); + /* Next line is blank. */ + tt_assert(!strcmpstart(smartlist_get(lines, 1), "==============")); + tt_assert(!strcmpstart(smartlist_get(lines, 2), "Minimal.")); + /* Next line is blank. */ + tt_assert(!strcmpstart(smartlist_get(lines, 3), "==============")); + tt_str_op(smartlist_get(lines, 4), ==, + "Testing any attempt to manually log from a signal."); + + done: + tor_free(content); + smartlist_free(lines); +} + +struct testcase_t logging_tests[] = { + { "sigsafe_err_fds", test_get_sigsafe_err_fds, TT_FORK, NULL, NULL }, + { "sigsafe_err", test_sigsafe_err, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_options.c b/src/test/test_options.c new file mode 100644 index 000000000..737f658e2 --- /dev/null +++ b/src/test/test_options.c @@ -0,0 +1,170 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CONFIG_PRIVATE +#include "or.h" +#include "confparse.h" +#include "config.h" +#include "test.h" + +typedef struct { + int severity; + uint32_t domain; + char *msg; +} logmsg_t; + +static smartlist_t *messages = NULL; + +static void +log_cback(int severity, uint32_t domain, const char *msg) +{ + logmsg_t *x = tor_malloc(sizeof(*x)); + x->severity = severity; + x->domain = domain; + x->msg = tor_strdup(msg); + if (!messages) + messages = smartlist_new(); + smartlist_add(messages, x); +} + +static void +setup_log_callback(void) +{ + log_severity_list_t lst; + memset(&lst, 0, sizeof(lst)); + lst.masks[LOG_ERR - LOG_ERR] = ~0; + lst.masks[LOG_WARN - LOG_ERR] = ~0; + lst.masks[LOG_NOTICE - LOG_ERR] = ~0; + add_callback_log(&lst, log_cback); +} + +static char * +dump_logs(void) +{ + smartlist_t *msgs; + char *out; + if (! messages) + return tor_strdup(""); + msgs = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(messages, logmsg_t *, x) { + smartlist_add_asprintf(msgs, "[%s] %s", + log_level_to_string(x->severity), x->msg); + } SMARTLIST_FOREACH_END(x); + out = smartlist_join_strings(msgs, "", 0, NULL); + SMARTLIST_FOREACH(msgs, char *, cp, tor_free(cp)); + smartlist_free(msgs); + return out; +} + +static void +clear_log_messages(void) +{ + if (!messages) + return; + SMARTLIST_FOREACH(messages, logmsg_t *, m, + { tor_free(m->msg); tor_free(m); }); + smartlist_free(messages); + messages = NULL; +} + +static void +test_options_validate_impl(const char *configuration, + const char *expect_errmsg, + int expect_log_severity, + const char *expect_log) +{ + or_options_t *opt = options_new(); + or_options_t *dflt; + config_line_t *cl=NULL; + char *msg=NULL; + int r; + opt->command = CMD_RUN_TOR; + options_init(opt); + + dflt = config_dup(&options_format, opt); + clear_log_messages(); + + r = config_get_lines(configuration, &cl, 1); + tt_int_op(r, ==, 0); + + r = config_assign(&options_format, opt, cl, 0, 0, &msg); + tt_int_op(r, ==, 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)); + } + tt_int_op((r == 0), ==, (msg == NULL)); + + 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)); + } + } + + done: + config_free_lines(cl); + or_options_free(opt); + or_options_free(dflt); + tor_free(msg); + 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) + +static void +test_options_validate(void *arg) +{ + (void)arg; + setup_log_callback(); + + WANT_ERR("ExtORPort 500000", "Invalid ExtORPort"); + + WANT_ERR_LOG("ServerTransportOptions trebuchet", + "ServerTransportOptions did not parse", + LOG_WARN, "Too few arguments"); + OK("ServerTransportOptions trebuchet sling=snappy"); + OK("ServerTransportOptions trebuchet sling="); + WANT_ERR_LOG("ServerTransportOptions trebuchet slingsnappy", + "ServerTransportOptions did not parse", + LOG_WARN, "\"slingsnappy\" is not a k=v"); + + clear_log_messages(); + return; +} + +struct testcase_t options_tests[] = { + { "validate", test_options_validate, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_pt.c b/src/test/test_pt.c index 80707f437..327792105 100644 --- a/src/test/test_pt.c +++ b/src/test/test_pt.c @@ -5,9 +5,17 @@ #include "orconfig.h" #define PT_PRIVATE +#define UTIL_PRIVATE +#define STATEFILE_PRIVATE +#define CONTROL_PRIVATE #include "or.h" +#include "config.h" +#include "confparse.h" +#include "control.h" #include "transports.h" #include "circuitbuild.h" +#include "util.h" +#include "statefile.h" #include "test.h" static void @@ -22,64 +30,102 @@ static void test_pt_parsing(void) { char line[200]; + transport_t *transport = NULL; + tor_addr_t test_addr; managed_proxy_t *mp = tor_malloc(sizeof(managed_proxy_t)); mp->conf_state = PT_PROTO_INFANT; mp->transports = smartlist_new(); /* incomplete cmethod */ - strcpy(line,"CMETHOD trebuchet"); + strlcpy(line,"CMETHOD trebuchet",sizeof(line)); test_assert(parse_cmethod_line(line, mp) < 0); reset_mp(mp); /* wrong proxy type */ - strcpy(line,"CMETHOD trebuchet dog 127.0.0.1:1999"); + strlcpy(line,"CMETHOD trebuchet dog 127.0.0.1:1999",sizeof(line)); test_assert(parse_cmethod_line(line, mp) < 0); reset_mp(mp); /* wrong addrport */ - strcpy(line,"CMETHOD trebuchet socks4 abcd"); + strlcpy(line,"CMETHOD trebuchet socks4 abcd",sizeof(line)); test_assert(parse_cmethod_line(line, mp) < 0); reset_mp(mp); /* correct line */ - strcpy(line,"CMETHOD trebuchet socks5 127.0.0.1:1999"); + strlcpy(line,"CMETHOD trebuchet socks5 127.0.0.1:1999",sizeof(line)); test_assert(parse_cmethod_line(line, mp) == 0); - test_assert(smartlist_len(mp->transports)); + test_assert(smartlist_len(mp->transports) == 1); + transport = smartlist_get(mp->transports, 0); + /* test registered address of transport */ + tor_addr_parse(&test_addr, "127.0.0.1"); + test_assert(tor_addr_eq(&test_addr, &transport->addr)); + /* test registered port of transport */ + test_assert(transport->port == 1999); + /* test registered SOCKS version of transport */ + test_assert(transport->socks_version == PROXY_SOCKS5); + /* test registered name of transport */ + test_streq(transport->name, "trebuchet"); reset_mp(mp); /* incomplete smethod */ - strcpy(line,"SMETHOD trebuchet"); + strlcpy(line,"SMETHOD trebuchet",sizeof(line)); test_assert(parse_smethod_line(line, mp) < 0); reset_mp(mp); /* wrong addr type */ - strcpy(line,"SMETHOD trebuchet abcd"); + strlcpy(line,"SMETHOD trebuchet abcd",sizeof(line)); test_assert(parse_smethod_line(line, mp) < 0); reset_mp(mp); /* cowwect */ - strcpy(line,"SMETHOD trebuchy 127.0.0.1:1999"); + strlcpy(line,"SMETHOD trebuchy 127.0.0.2:2999",sizeof(line)); test_assert(parse_smethod_line(line, mp) == 0); + test_assert(smartlist_len(mp->transports) == 1); + transport = smartlist_get(mp->transports, 0); + /* test registered address of transport */ + tor_addr_parse(&test_addr, "127.0.0.2"); + test_assert(tor_addr_eq(&test_addr, &transport->addr)); + /* test registered port of transport */ + test_assert(transport->port == 2999); + /* test registered name of transport */ + test_streq(transport->name, "trebuchy"); reset_mp(mp); + /* Include some arguments. Good ones. */ + strlcpy(line,"SMETHOD trebuchet 127.0.0.1:9999 " + "ARGS:counterweight=3,sling=snappy", + sizeof(line)); + test_assert(parse_smethod_line(line, mp) == 0); + tt_int_op(1, ==, smartlist_len(mp->transports)); + { + const transport_t *transport = smartlist_get(mp->transports, 0); + tt_assert(transport); + tt_str_op(transport->name, ==, "trebuchet"); + tt_int_op(transport->port, ==, 9999); + tt_str_op(fmt_addr(&transport->addr), ==, "127.0.0.1"); + tt_str_op(transport->extra_info_args, ==, + "counterweight=3,sling=snappy"); + } + reset_mp(mp); + /* unsupported version */ - strcpy(line,"VERSION 666"); + strlcpy(line,"VERSION 666",sizeof(line)); test_assert(parse_version(line, mp) < 0); /* incomplete VERSION */ - strcpy(line,"VERSION "); + strlcpy(line,"VERSION ",sizeof(line)); test_assert(parse_version(line, mp) < 0); /* correct VERSION */ - strcpy(line,"VERSION 1"); + strlcpy(line,"VERSION 1",sizeof(line)); test_assert(parse_version(line, mp) == 0); done: @@ -87,6 +133,58 @@ test_pt_parsing(void) } static void +test_pt_get_transport_options(void *arg) +{ + char **execve_args; + smartlist_t *transport_list = smartlist_new(); + managed_proxy_t *mp; + or_options_t *options = get_options_mutable(); + char *opt_str = NULL; + config_line_t *cl = NULL; + (void)arg; + + execve_args = tor_malloc(sizeof(char*)*2); + execve_args[0] = tor_strdup("cheeseshop"); + execve_args[1] = NULL; + + mp = managed_proxy_create(transport_list, execve_args, 1); + tt_ptr_op(mp, !=, NULL); + opt_str = get_transport_options_for_server_proxy(mp); + tt_ptr_op(opt_str, ==, NULL); + + smartlist_add(mp->transports_to_launch, tor_strdup("gruyere")); + smartlist_add(mp->transports_to_launch, tor_strdup("roquefort")); + smartlist_add(mp->transports_to_launch, tor_strdup("stnectaire")); + + tt_assert(options); + + cl = tor_malloc_zero(sizeof(config_line_t)); + cl->value = tor_strdup("gruyere melty=10 hardness=se;ven"); + options->ServerTransportOptions = cl; + + cl = tor_malloc_zero(sizeof(config_line_t)); + cl->value = tor_strdup("stnectaire melty=4 hardness=three"); + cl->next = options->ServerTransportOptions; + options->ServerTransportOptions = cl; + + cl = tor_malloc_zero(sizeof(config_line_t)); + cl->value = tor_strdup("pepperjack melty=12 hardness=five"); + cl->next = options->ServerTransportOptions; + options->ServerTransportOptions = cl; + + opt_str = get_transport_options_for_server_proxy(mp); + tt_str_op(opt_str, ==, + "gruyere:melty=10;gruyere:hardness=se\\;ven;" + "stnectaire:melty=4;stnectaire:hardness=three"); + + done: + tor_free(opt_str); + config_free_lines(cl); + managed_proxy_destroy(mp, 0); + smartlist_free(transport_list); +} + +static void test_pt_protocol(void) { char line[200]; @@ -99,32 +197,32 @@ test_pt_protocol(void) /* various wrong protocol runs: */ - strcpy(line,"VERSION 1"); + strlcpy(line,"VERSION 1",sizeof(line)); handle_proxy_line(line, mp); test_assert(mp->conf_state == PT_PROTO_ACCEPTING_METHODS); - strcpy(line,"VERSION 1"); + strlcpy(line,"VERSION 1",sizeof(line)); handle_proxy_line(line, mp); test_assert(mp->conf_state == PT_PROTO_BROKEN); reset_mp(mp); - strcpy(line,"CMETHOD trebuchet socks5 127.0.0.1:1999"); + strlcpy(line,"CMETHOD trebuchet socks5 127.0.0.1:1999",sizeof(line)); handle_proxy_line(line, mp); test_assert(mp->conf_state == PT_PROTO_BROKEN); reset_mp(mp); /* correct protocol run: */ - strcpy(line,"VERSION 1"); + strlcpy(line,"VERSION 1",sizeof(line)); handle_proxy_line(line, mp); test_assert(mp->conf_state == PT_PROTO_ACCEPTING_METHODS); - strcpy(line,"CMETHOD trebuchet socks5 127.0.0.1:1999"); + strlcpy(line,"CMETHOD trebuchet socks5 127.0.0.1:1999",sizeof(line)); handle_proxy_line(line, mp); test_assert(mp->conf_state == PT_PROTO_ACCEPTING_METHODS); - strcpy(line,"CMETHODS DONE"); + strlcpy(line,"CMETHODS DONE",sizeof(line)); handle_proxy_line(line, mp); test_assert(mp->conf_state == PT_PROTO_CONFIGURED); @@ -132,12 +230,223 @@ test_pt_protocol(void) tor_free(mp); } +static void +test_pt_get_extrainfo_string(void *arg) +{ + managed_proxy_t *mp1 = NULL, *mp2 = NULL; + char **argv1, **argv2; + smartlist_t *t1 = smartlist_new(), *t2 = smartlist_new(); + int r; + char *s = NULL; + (void) arg; + + argv1 = tor_malloc_zero(sizeof(char*)*3); + argv1[0] = tor_strdup("ewige"); + argv1[1] = tor_strdup("Blumenkraft"); + argv1[2] = NULL; + argv2 = tor_malloc_zero(sizeof(char*)*4); + argv2[0] = tor_strdup("und"); + argv2[1] = tor_strdup("ewige"); + argv2[2] = tor_strdup("Schlangenkraft"); + argv2[3] = NULL; + + mp1 = managed_proxy_create(t1, argv1, 1); + mp2 = managed_proxy_create(t2, argv2, 1); + + r = parse_smethod_line("SMETHOD hagbard 127.0.0.1:5555", mp1); + tt_int_op(r, ==, 0); + r = parse_smethod_line("SMETHOD celine 127.0.0.1:1723 ARGS:card=no-enemy", + mp2); + tt_int_op(r, ==, 0); + + /* Force these proxies to look "completed" or they won't generate output. */ + mp1->conf_state = mp2->conf_state = PT_PROTO_COMPLETED; + + s = pt_get_extra_info_descriptor_string(); + tt_assert(s); + tt_str_op(s, ==, + "transport hagbard 127.0.0.1:5555\n" + "transport celine 127.0.0.1:1723 card=no-enemy\n"); + + done: + /* XXXX clean up better */ + smartlist_free(t1); + smartlist_free(t2); + tor_free(s); +} + +#ifdef _WIN32 +#define STDIN_HANDLE HANDLE +#else +#define STDIN_HANDLE FILE +#endif + +static smartlist_t * +tor_get_lines_from_handle_replacement(STDIN_HANDLE *handle, + enum stream_status *stream_status_out) +{ + static int times_called = 0; + smartlist_t *retval_sl = smartlist_new(); + + (void) handle; + (void) stream_status_out; + + /* Generate some dummy CMETHOD lines the first 5 times. The 6th + time, send 'CMETHODS DONE' to finish configuring the proxy. */ + if (times_called++ != 5) { + smartlist_add_asprintf(retval_sl, "SMETHOD mock%d 127.0.0.1:555%d", + times_called, times_called); + } else { + smartlist_add(retval_sl, tor_strdup("SMETHODS DONE")); + } + + return retval_sl; +} + +/* NOP mock */ +static void +tor_process_handle_destroy_replacement(process_handle_t *process_handle, + int also_terminate_process) +{ + (void) process_handle; + (void) also_terminate_process; +} + +static or_state_t *dummy_state = NULL; + +static or_state_t * +get_or_state_replacement(void) +{ + return dummy_state; +} + +static int controlevent_n = 0; +static uint16_t controlevent_event = 0; +static smartlist_t *controlevent_msgs = NULL; + +static void +send_control_event_string_replacement(uint16_t event, event_format_t which, + const char *msg) +{ + (void) which; + ++controlevent_n; + controlevent_event = event; + if (!controlevent_msgs) + controlevent_msgs = smartlist_new(); + smartlist_add(controlevent_msgs, tor_strdup(msg)); +} + +/* Test the configure_proxy() function. */ +static void +test_pt_configure_proxy(void *arg) +{ + int i, retval; + managed_proxy_t *mp = NULL; + (void) arg; + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + + MOCK(tor_get_lines_from_handle, + tor_get_lines_from_handle_replacement); + MOCK(tor_process_handle_destroy, + tor_process_handle_destroy_replacement); + MOCK(get_or_state, + get_or_state_replacement); + MOCK(send_control_event_string, + send_control_event_string_replacement); + + control_testing_set_global_event_mask(EVENT_TRANSPORT_LAUNCHED); + + mp = tor_malloc(sizeof(managed_proxy_t)); + mp->conf_state = PT_PROTO_ACCEPTING_METHODS; + mp->transports = smartlist_new(); + mp->transports_to_launch = smartlist_new(); + mp->process_handle = tor_malloc_zero(sizeof(process_handle_t)); + mp->argv = tor_malloc_zero(sizeof(char*)*2); + mp->argv[0] = tor_strdup("<testcase>"); + mp->is_server = 1; + + /* Test the return value of configure_proxy() by calling it some + times while it is uninitialized and then finally finalizing its + configuration. */ + for (i = 0 ; i < 5 ; i++) { + retval = configure_proxy(mp); + /* retval should be zero because proxy hasn't finished configuring yet */ + test_assert(retval == 0); + /* check the number of registered transports */ + test_assert(smartlist_len(mp->transports) == i+1); + /* check that the mp is still waiting for transports */ + test_assert(mp->conf_state == PT_PROTO_ACCEPTING_METHODS); + } + + /* this last configure_proxy() should finalize the proxy configuration. */ + retval = configure_proxy(mp); + /* retval should be 1 since the proxy finished configuring */ + test_assert(retval == 1); + /* check the mp state */ + test_assert(mp->conf_state == PT_PROTO_COMPLETED); + + tt_int_op(controlevent_n, ==, 5); + tt_int_op(controlevent_event, ==, EVENT_TRANSPORT_LAUNCHED); + tt_int_op(smartlist_len(controlevent_msgs), ==, 5); + smartlist_sort_strings(controlevent_msgs); + tt_str_op(smartlist_get(controlevent_msgs, 0), ==, + "650 TRANSPORT_LAUNCHED server mock1 127.0.0.1 5551\r\n"); + tt_str_op(smartlist_get(controlevent_msgs, 1), ==, + "650 TRANSPORT_LAUNCHED server mock2 127.0.0.1 5552\r\n"); + tt_str_op(smartlist_get(controlevent_msgs, 2), ==, + "650 TRANSPORT_LAUNCHED server mock3 127.0.0.1 5553\r\n"); + tt_str_op(smartlist_get(controlevent_msgs, 3), ==, + "650 TRANSPORT_LAUNCHED server mock4 127.0.0.1 5554\r\n"); + tt_str_op(smartlist_get(controlevent_msgs, 4), ==, + "650 TRANSPORT_LAUNCHED server mock5 127.0.0.1 5555\r\n"); + + { /* check that the transport info were saved properly in the tor state */ + config_line_t *transport_in_state = NULL; + smartlist_t *transport_info_sl = smartlist_new(); + char *name_of_transport = NULL; + char *bindaddr = NULL; + + /* Get the bindaddr for "mock1" and check it against the bindaddr + that the mocked tor_get_lines_from_handle() generated. */ + transport_in_state = get_transport_in_state_by_name("mock1"); + test_assert(transport_in_state); + smartlist_split_string(transport_info_sl, transport_in_state->value, + NULL, 0, 0); + name_of_transport = smartlist_get(transport_info_sl, 0); + bindaddr = smartlist_get(transport_info_sl, 1); + tt_str_op(name_of_transport, ==, "mock1"); + tt_str_op(bindaddr, ==, "127.0.0.1:5551"); + + SMARTLIST_FOREACH(transport_info_sl, char *, cp, tor_free(cp)); + smartlist_free(transport_info_sl); + } + + done: + tor_free(dummy_state); + UNMOCK(tor_get_lines_from_handle); + UNMOCK(tor_process_handle_destroy); + UNMOCK(get_or_state); + UNMOCK(send_control_event_string); + if (controlevent_msgs) { + SMARTLIST_FOREACH(controlevent_msgs, char *, cp, tor_free(cp)); + smartlist_free(controlevent_msgs); + controlevent_msgs = NULL; + } +} + #define PT_LEGACY(name) \ { #name, legacy_test_helper, 0, &legacy_setup, test_pt_ ## name } struct testcase_t pt_tests[] = { PT_LEGACY(parsing), PT_LEGACY(protocol), + { "get_transport_options", test_pt_get_transport_options, TT_FORK, + NULL, NULL }, + { "get_extrainfo_string", test_pt_get_extrainfo_string, TT_FORK, + NULL, NULL }, + { "configure_proxy",test_pt_configure_proxy, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_replay.c b/src/test/test_replay.c index de841ad59..b48f582f5 100644 --- a/src/test/test_replay.c +++ b/src/test/test_replay.c @@ -32,6 +32,40 @@ test_replaycache_alloc(void) } static void +test_replaycache_badalloc(void) +{ + replaycache_t *r = NULL; + + /* Negative horizon should fail */ + r = replaycache_new(-600, 300); + test_assert(r == NULL); + /* Negative interval should get adjusted to zero */ + r = replaycache_new(600, -300); + test_assert(r != NULL); + test_eq(r->scrub_interval, 0); + replaycache_free(r); + /* Negative horizon and negative interval should still fail */ + r = replaycache_new(-600, -300); + test_assert(r == NULL); + + done: + if (r) replaycache_free(r); + + return; +} + +static void +test_replaycache_free_null(void) +{ + replaycache_free(NULL); + /* Assert that we're here without horrible death */ + test_assert(1); + + done: + return; +} + +static void test_replaycache_miss(void) { replaycache_t *r = NULL; @@ -42,7 +76,13 @@ test_replaycache_miss(void) result = replaycache_add_and_test_internal(1200, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); + test_eq(result, 0); + + /* poke the bad-parameter error case too */ + result = + replaycache_add_and_test_internal(1200, NULL, test_buffer, + strlen(test_buffer), NULL); test_eq(result, 0); done: @@ -62,12 +102,12 @@ test_replaycache_hit(void) result = replaycache_add_and_test_internal(1200, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 0); result = replaycache_add_and_test_internal(1300, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 1); done: @@ -87,17 +127,17 @@ test_replaycache_age(void) result = replaycache_add_and_test_internal(1200, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 0); result = replaycache_add_and_test_internal(1300, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 1); result = replaycache_add_and_test_internal(3000, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 0); done: @@ -118,12 +158,12 @@ test_replaycache_elapsed(void) result = replaycache_add_and_test_internal(1200, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 0); result = replaycache_add_and_test_internal(1300, r, test_buffer, - (int)strlen(test_buffer), &elapsed); + strlen(test_buffer), &elapsed); test_eq(result, 1); test_eq(elapsed, 100); @@ -144,18 +184,102 @@ test_replaycache_noexpire(void) result = replaycache_add_and_test_internal(1200, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 0); result = replaycache_add_and_test_internal(1300, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); test_eq(result, 1); result = replaycache_add_and_test_internal(3000, r, test_buffer, - (int)strlen(test_buffer), NULL); + strlen(test_buffer), NULL); + test_eq(result, 1); + + done: + if (r) replaycache_free(r); + + return; +} + +static void +test_replaycache_scrub(void) +{ + replaycache_t *r = NULL; + int result; + + r = replaycache_new(600, 300); + test_assert(r != NULL); + + /* Set up like in test_replaycache_hit() */ + result = + replaycache_add_and_test_internal(100, r, test_buffer, + strlen(test_buffer), NULL); + test_eq(result, 0); + + result = + replaycache_add_and_test_internal(200, r, test_buffer, + strlen(test_buffer), NULL); + test_eq(result, 1); + + /* + * Poke a few replaycache_scrub_if_needed_internal() error cases that + * can't happen through replaycache_add_and_test_internal() + */ + + /* Null cache */ + replaycache_scrub_if_needed_internal(300, NULL); + /* Assert we're still here */ + test_assert(1); + + /* Make sure we hit the aging-out case too */ + replaycache_scrub_if_needed_internal(1500, r); + /* Assert that we aged it */ + test_eq(digestmap_size(r->digests_seen), 0); + + done: + if (r) replaycache_free(r); + + return; +} + +static void +test_replaycache_future(void) +{ + replaycache_t *r = NULL; + int result; + time_t elapsed = 0; + + r = replaycache_new(600, 300); + test_assert(r != NULL); + + /* Set up like in test_replaycache_hit() */ + result = + replaycache_add_and_test_internal(100, r, test_buffer, + strlen(test_buffer), &elapsed); + test_eq(result, 0); + /* elapsed should still be 0, since it wasn't written */ + test_eq(elapsed, 0); + + result = + replaycache_add_and_test_internal(200, r, test_buffer, + strlen(test_buffer), &elapsed); + test_eq(result, 1); + /* elapsed should be the time since the last hit */ + test_eq(elapsed, 100); + + /* + * Now let's turn the clock back to get coverage on the cache entry from the + * future not-supposed-to-happen case. + */ + result = + replaycache_add_and_test_internal(150, r, test_buffer, + strlen(test_buffer), &elapsed); + /* We should still get a hit */ test_eq(result, 1); + /* ...but it shouldn't let us see a negative elapsed time */ + test_eq(elapsed, 0); done: if (r) replaycache_free(r); @@ -163,16 +287,62 @@ test_replaycache_noexpire(void) return; } +static void +test_replaycache_realtime(void) +{ + replaycache_t *r = NULL; + /* + * Negative so we fail if replaycache_add_test_and_elapsed() doesn't + * write to elapsed. + */ + time_t elapsed = -1; + int result; + + /* Test the realtime as well as *_internal() entry points */ + r = replaycache_new(600, 300); + test_assert(r != NULL); + + /* This should miss */ + result = + replaycache_add_and_test(r, test_buffer, strlen(test_buffer)); + test_eq(result, 0); + + /* This should hit */ + result = + replaycache_add_and_test(r, test_buffer, strlen(test_buffer)); + test_eq(result, 1); + + /* This should hit and return a small elapsed time */ + result = + replaycache_add_test_and_elapsed(r, test_buffer, + strlen(test_buffer), &elapsed); + test_eq(result, 1); + test_assert(elapsed >= 0); + test_assert(elapsed <= 5); + + /* Scrub it to exercise that entry point too */ + replaycache_scrub_if_needed(r); + + done: + if (r) replaycache_free(r); + return; +} + #define REPLAYCACHE_LEGACY(name) \ { #name, legacy_test_helper, 0, &legacy_setup, test_replaycache_ ## name } struct testcase_t replaycache_tests[] = { REPLAYCACHE_LEGACY(alloc), + REPLAYCACHE_LEGACY(badalloc), + REPLAYCACHE_LEGACY(free_null), REPLAYCACHE_LEGACY(miss), REPLAYCACHE_LEGACY(hit), REPLAYCACHE_LEGACY(age), REPLAYCACHE_LEGACY(elapsed), REPLAYCACHE_LEGACY(noexpire), + REPLAYCACHE_LEGACY(scrub), + REPLAYCACHE_LEGACY(future), + REPLAYCACHE_LEGACY(realtime), END_OF_TESTCASES }; diff --git a/src/test/test_socks.c b/src/test/test_socks.c new file mode 100644 index 000000000..4ce61e068 --- /dev/null +++ b/src/test/test_socks.c @@ -0,0 +1,393 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "config.h" +#include "test.h" + +typedef struct socks_test_data_t { + socks_request_t *req; + buf_t *buf; +} socks_test_data_t; + +static void * +socks_test_setup(const struct testcase_t *testcase) +{ + socks_test_data_t *data = tor_malloc(sizeof(socks_test_data_t)); + (void)testcase; + data->buf = buf_new_with_capacity(256); + data->req = socks_request_new(); + config_register_addressmaps(get_options()); + return data; +} +static int +socks_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + socks_test_data_t *data = ptr; + (void)testcase; + buf_free(data->buf); + socks_request_free(data->req); + tor_free(data); + return 1; +} + +const struct testcase_setup_t socks_setup = { + socks_test_setup, socks_test_cleanup +}; + +#define SOCKS_TEST_INIT() \ + socks_test_data_t *testdata = ptr; \ + buf_t *buf = testdata->buf; \ + socks_request_t *socks = testdata->req; +#define ADD_DATA(buf, s) \ + write_to_buf(s, sizeof(s)-1, buf) + +static void +socks_request_clear(socks_request_t *socks) +{ + tor_free(socks->username); + tor_free(socks->password); + memset(socks, 0, sizeof(socks_request_t)); +} + +/** Perform unsupported SOCKS 4 commands */ +static void +test_socks_4_unsupported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 4 Send BIND [02] to IP address 2.2.2.2:4369 */ + ADD_DATA(buf, "\x04\x02\x11\x11\x02\x02\x02\x02\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == -1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + + done: + ; +} + +/** Perform supported SOCKS 4 commands */ +static void +test_socks_4_supported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + test_eq(0, buf_datalen(buf)); + + /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4370 */ + ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x03\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_eq(SOCKS_COMMAND_CONNECT, socks->command); + test_streq("2.2.2.3", socks->address); + test_eq(4370, socks->port); + test_assert(socks->got_auth == 0); + test_assert(! socks->username); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4369 with userid*/ + ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x04me\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_eq(SOCKS_COMMAND_CONNECT, socks->command); + test_streq("2.2.2.4", socks->address); + test_eq(4370, socks->port); + test_assert(socks->got_auth == 1); + test_assert(socks->username); + test_eq(2, socks->usernamelen); + test_memeq("me", socks->username, 2); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 4a Send RESOLVE [F0] request for torproject.org */ + ADD_DATA(buf, "\x04\xF0\x01\x01\x00\x00\x00\x02me\x00torproject.org\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_streq("torproject.org", socks->address); + + test_eq(0, buf_datalen(buf)); + + done: + ; +} + +/** Perform unsupported SOCKS 5 commands */ +static void +test_socks_5_unsupported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Send unsupported BIND [02] command */ + ADD_DATA(buf, "\x05\x02\x00\x01"); + + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 0); + test_eq(0, buf_datalen(buf)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + ADD_DATA(buf, "\x05\x02\x00\x01\x02\x02\x02\x01\x01\x01"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), -1); + /* XXX: shouldn't tor reply 'command not supported' [07]? */ + + buf_clear(buf); + socks_request_clear(socks); + + /* SOCKS 5 Send unsupported UDP_ASSOCIATE [03] command */ + ADD_DATA(buf, "\x05\x03\x00\x01\x02"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 0); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(2, socks->reply[1]); + ADD_DATA(buf, "\x05\x03\x00\x01\x02\x02\x02\x01\x01\x01"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), -1); + /* XXX: shouldn't tor reply 'command not supported' [07]? */ + + done: + ; +} + +/** Perform supported SOCKS 5 commands */ +static void +test_socks_5_supported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ + ADD_DATA(buf, "\x05\x01\x00"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 0); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + + ADD_DATA(buf, "\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 1); + test_streq("2.2.2.2", socks->address); + test_eq(4369, socks->port); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 5 Send CONNECT [01] to FQDN torproject.org:4369 */ + ADD_DATA(buf, "\x05\x01\x00"); + ADD_DATA(buf, "\x05\x01\x00\x03\x0Etorproject.org\x11\x11"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 1); + + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("torproject.org", socks->address); + test_eq(4369, socks->port); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 5 Send RESOLVE [F0] request for torproject.org:4369 */ + ADD_DATA(buf, "\x05\x01\x00"); + ADD_DATA(buf, "\x05\xF0\x00\x03\x0Etorproject.org\x01\x02"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("torproject.org", socks->address); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 5 Send RESOLVE_PTR [F1] for IP address 2.2.2.5 */ + ADD_DATA(buf, "\x05\x01\x00"); + ADD_DATA(buf, "\x05\xF1\x00\x01\x02\x02\x02\x05\x01\x03"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("2.2.2.5", socks->address); + + test_eq(0, buf_datalen(buf)); + + done: + ; +} + +/** Perform SOCKS 5 authentication */ +static void +test_socks_5_no_authenticate(void *ptr) +{ + SOCKS_TEST_INIT(); + + /*SOCKS 5 No Authentication */ + ADD_DATA(buf,"\x05\x01\x00"); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_NO_AUTH, socks->reply[1]); + + test_eq(0, buf_datalen(buf)); + + /*SOCKS 5 Send username/password anyway - pretend to be broken */ + ADD_DATA(buf,"\x01\x02\x01\x01\x02\x01\x01"); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(1, socks->reply[0]); + test_eq(0, socks->reply[1]); + + test_eq(2, socks->usernamelen); + test_eq(2, socks->passwordlen); + + test_memeq("\x01\x01", socks->username, 2); + test_memeq("\x01\x01", socks->password, 2); + + done: + ; +} + +/** Perform SOCKS 5 authentication */ +static void +test_socks_5_authenticate(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Negotiate username/password authentication */ + ADD_DATA(buf, "\x05\x01\x02"); + + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_USER_PASS, socks->reply[1]); + test_eq(5, socks->socks_version); + + test_eq(0, buf_datalen(buf)); + + /* SOCKS 5 Send username/password */ + ADD_DATA(buf, "\x01\x02me\x08mypasswd"); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(1, socks->reply[0]); + test_eq(0, socks->reply[1]); + + test_eq(2, socks->usernamelen); + test_eq(8, socks->passwordlen); + + test_memeq("me", socks->username, 2); + test_memeq("mypasswd", socks->password, 8); + + done: + ; +} + +/** Perform SOCKS 5 authentication and send data all in one go */ +static void +test_socks_5_authenticate_with_data(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Negotiate username/password authentication */ + ADD_DATA(buf, "\x05\x01\x02"); + + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_USER_PASS, socks->reply[1]); + test_eq(5, socks->socks_version); + + test_eq(0, buf_datalen(buf)); + + /* SOCKS 5 Send username/password */ + /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ + ADD_DATA(buf, "\x01\x02me\x03you\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"); + test_assert(fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(1, socks->reply[0]); + test_eq(0, socks->reply[1]); + + test_streq("2.2.2.2", socks->address); + test_eq(4369, socks->port); + + test_eq(2, socks->usernamelen); + test_eq(3, socks->passwordlen); + test_memeq("me", socks->username, 2); + test_memeq("you", socks->password, 3); + + done: + ; +} + +/** Perform SOCKS 5 authentication before method negotiated */ +static void +test_socks_5_auth_before_negotiation(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Send username/password */ + ADD_DATA(buf, "\x01\x02me\x02me"); + test_assert(fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks) == -1); + test_eq(0, socks->socks_version); + test_eq(0, socks->replylen); + test_eq(0, socks->reply[0]); + test_eq(0, socks->reply[1]); + + done: + ; +} + +#define SOCKSENT(name) \ + { #name, test_socks_##name, TT_FORK, &socks_setup, NULL } + +struct testcase_t socks_tests[] = { + SOCKSENT(4_unsupported_commands), + SOCKSENT(4_supported_commands), + + SOCKSENT(5_unsupported_commands), + SOCKSENT(5_supported_commands), + SOCKSENT(5_no_authenticate), + SOCKSENT(5_auth_before_negotiation), + SOCKSENT(5_authenticate), + SOCKSENT(5_authenticate_with_data), + + END_OF_TESTCASES +}; + diff --git a/src/test/test_util.c b/src/test/test_util.c index 65d9d2f87..376e3aab8 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -4,6 +4,7 @@ /* See LICENSE for licensing information */ #include "orconfig.h" +#define COMPAT_PRIVATE #define CONTROL_PRIVATE #define MEMPOOL_PRIVATE #define UTIL_PRIVATE @@ -101,6 +102,107 @@ test_util_read_file_eof_zero_bytes(void *arg) test_util_read_until_eof_impl("tor_test_fifo_empty", 0, 10000); } +/* Test the basic expected behaviour for write_chunks_to_file. + * NOTE: This will need to be updated if we ever change the tempfile location + * or extension */ +static void +test_util_write_chunks_to_file(void *arg) +{ + char *fname = NULL; + char *tempname = NULL; + char *str = NULL; + int r; + struct stat st; + + /* These should be two different sizes to ensure the data is different + * between the data file and the temp file's 'known string' */ + int temp_str_len = 1024; + int data_str_len = 512; + char *data_str = tor_malloc(data_str_len); + char *temp_str = tor_malloc(temp_str_len); + + smartlist_t *chunks = smartlist_new(); + sized_chunk_t c = {data_str, data_str_len/2}; + sized_chunk_t c2 = {data_str + data_str_len/2, data_str_len/2}; + (void)arg; + + crypto_rand(temp_str, temp_str_len); + crypto_rand(data_str, data_str_len); + + // Ensure it can write multiple chunks + + smartlist_add(chunks, &c); + smartlist_add(chunks, &c2); + + /* + * Check if it writes using a tempfile + */ + fname = tor_strdup(get_fname("write_chunks_with_tempfile")); + tor_asprintf(&tempname, "%s.tmp", fname); + + // write a known string to a file where the tempfile will be + r = write_bytes_to_file(tempname, temp_str, temp_str_len, 1); + tt_int_op(r, ==, 0); + + // call write_chunks_to_file + r = write_chunks_to_file(fname, chunks, 1, 0); + tt_int_op(r, ==, 0); + + // assert the file has been written (expected size) + str = read_file_to_str(fname, RFTS_BIN, &st); + tt_assert(str != NULL); + tt_int_op(st.st_size, ==, data_str_len); + test_mem_op(data_str, ==, str, data_str_len); + tor_free(str); + + // assert that the tempfile is removed (should not leave artifacts) + str = read_file_to_str(tempname, RFTS_BIN|RFTS_IGNORE_MISSING, &st); + tt_assert(str == NULL); + + // Remove old testfile for second test + r = unlink(fname); + tt_int_op(r, ==, 0); + tor_free(fname); + tor_free(tempname); + + /* + * Check if it skips using a tempfile with flags + */ + fname = tor_strdup(get_fname("write_chunks_with_no_tempfile")); + tor_asprintf(&tempname, "%s.tmp", fname); + + // write a known string to a file where the tempfile will be + r = write_bytes_to_file(tempname, temp_str, temp_str_len, 1); + tt_int_op(r, ==, 0); + + // call write_chunks_to_file with no_tempfile = true + r = write_chunks_to_file(fname, chunks, 1, 1); + tt_int_op(r, ==, 0); + + // assert the file has been written (expected size) + str = read_file_to_str(fname, RFTS_BIN, &st); + tt_assert(str != NULL); + tt_int_op(st.st_size, ==, data_str_len); + test_mem_op(data_str, ==, str, data_str_len); + tor_free(str); + + // assert the tempfile still contains the known string + str = read_file_to_str(tempname, RFTS_BIN, &st); + tt_assert(str != NULL); + tt_int_op(st.st_size, ==, temp_str_len); + test_mem_op(temp_str, ==, str, temp_str_len); + + done: + unlink(fname); + unlink(tempname); + smartlist_free(chunks); + tor_free(fname); + tor_free(tempname); + tor_free(str); + tor_free(data_str); + tor_free(temp_str); +} + static void test_util_time(void) { @@ -796,6 +898,64 @@ test_util_expand_filename(void) } #endif +/** Test tor_escape_str_for_pt_args(). */ +static void +test_util_escape_string_socks(void) +{ + char *escaped_string = NULL; + + /** Simple backslash escape. */ + escaped_string = tor_escape_str_for_pt_args("This is a backslash: \\",";\\"); + test_assert(escaped_string); + test_streq(escaped_string, "This is a backslash: \\\\"); + tor_free(escaped_string); + + /** Simple semicolon escape. */ + escaped_string = tor_escape_str_for_pt_args("First rule:Do not use ;",";\\"); + test_assert(escaped_string); + test_streq(escaped_string, "First rule:Do not use \\;"); + tor_free(escaped_string); + + /** Empty string. */ + escaped_string = tor_escape_str_for_pt_args("", ";\\"); + test_assert(escaped_string); + test_streq(escaped_string, ""); + tor_free(escaped_string); + + /** Escape all characters. */ + escaped_string = tor_escape_str_for_pt_args(";\\;\\", ";\\"); + test_assert(escaped_string); + test_streq(escaped_string, "\\;\\\\\\;\\\\"); + tor_free(escaped_string); + + escaped_string = tor_escape_str_for_pt_args(";", ";\\"); + test_assert(escaped_string); + test_streq(escaped_string, "\\;"); + tor_free(escaped_string); + + done: + tor_free(escaped_string); +} + +static void +test_util_string_is_key_value(void *ptr) +{ + (void)ptr; + test_assert(string_is_key_value(LOG_WARN, "key=value")); + test_assert(string_is_key_value(LOG_WARN, "k=v")); + test_assert(string_is_key_value(LOG_WARN, "key=")); + test_assert(string_is_key_value(LOG_WARN, "x=")); + test_assert(string_is_key_value(LOG_WARN, "xx=")); + test_assert(!string_is_key_value(LOG_WARN, "=value")); + test_assert(!string_is_key_value(LOG_WARN, "=x")); + test_assert(!string_is_key_value(LOG_WARN, "=")); + + /* ??? */ + /* test_assert(!string_is_key_value(LOG_WARN, "===")); */ + done: + ; +} + /** Test basic string functionality. */ static void test_util_strmisc(void) @@ -2223,6 +2383,7 @@ test_util_load_win_lib(void *ptr) } #endif +#ifndef _WIN32 static void clear_hex_errno(char *hex_errno) { @@ -2267,6 +2428,7 @@ test_util_exit_status(void *ptr) done: ; } +#endif #ifndef _WIN32 /** Check that fgets waits until a full line, and not return a partial line, on @@ -2568,14 +2730,14 @@ test_util_spawn_background_partial_read(void *ptr) } /** - * Test for format_hex_number_for_helper_exit_status() + * Test for format_hex_number_sigsafe() */ static void test_util_format_hex_number(void *ptr) { int i, len; - char buf[HEX_ERRNO_SIZE + 1]; + char buf[33]; const struct { const char *str; unsigned int x; @@ -2584,6 +2746,8 @@ test_util_format_hex_number(void *ptr) {"1", 1}, {"273A", 0x273a}, {"FFFF", 0xffff}, + {"7FFFFFFF", 0x7fffffff}, + {"FFFFFFFF", 0xffffffff}, #if UINT_MAX >= 0xffffffff {"31BC421D", 0x31bc421d}, {"FFFFFFFF", 0xffffffff}, @@ -2594,19 +2758,73 @@ test_util_format_hex_number(void *ptr) (void)ptr; for (i = 0; test_data[i].str != NULL; ++i) { - len = format_hex_number_for_helper_exit_status(test_data[i].x, - buf, HEX_ERRNO_SIZE); + len = format_hex_number_sigsafe(test_data[i].x, buf, sizeof(buf)); test_neq(len, 0); - buf[len] = '\0'; + test_eq(len, strlen(buf)); test_streq(buf, test_data[i].str); } + test_eq(4, format_hex_number_sigsafe(0xffff, buf, 5)); + test_streq(buf, "FFFF"); + test_eq(0, format_hex_number_sigsafe(0xffff, buf, 4)); + test_eq(0, format_hex_number_sigsafe(0, buf, 1)); + done: return; } /** - * Test that we can properly format q Windows command line + * Test for format_hex_number_sigsafe() + */ + +static void +test_util_format_dec_number(void *ptr) +{ + int i, len; + char buf[33]; + const struct { + const char *str; + unsigned int x; + } test_data[] = { + {"0", 0}, + {"1", 1}, + {"1234", 1234}, + {"12345678", 12345678}, + {"99999999", 99999999}, + {"100000000", 100000000}, + {"4294967295", 4294967295u}, +#if UINT_MAX > 0xffffffff + {"18446744073709551615", 18446744073709551615u }, +#endif + {NULL, 0} + }; + + (void)ptr; + + for (i = 0; test_data[i].str != NULL; ++i) { + len = format_dec_number_sigsafe(test_data[i].x, buf, sizeof(buf)); + test_neq(len, 0); + test_eq(len, strlen(buf)); + test_streq(buf, test_data[i].str); + + len = format_dec_number_sigsafe(test_data[i].x, buf, + (int)(strlen(test_data[i].str) + 1)); + test_eq(len, strlen(buf)); + test_streq(buf, test_data[i].str); + } + + test_eq(4, format_dec_number_sigsafe(7331, buf, 5)); + test_streq(buf, "7331"); + test_eq(0, format_dec_number_sigsafe(7331, buf, 4)); + test_eq(1, format_dec_number_sigsafe(0, buf, 2)); + test_eq(0, format_dec_number_sigsafe(0, buf, 1)); + + done: + return; +} + +/** + * Test that we can properly format a Windows command line */ static void test_util_join_win_cmdline(void *ptr) @@ -2817,7 +3035,7 @@ test_util_eat_whitespace(void *ptr) (void)ptr; /* Try one leading ws */ - strcpy(str, "fuubaar"); + strlcpy(str, "fuubaar", sizeof(str)); for (i = 0; i < sizeof(ws); ++i) { str[0] = ws[i]; test_eq_ptr(str + 1, eat_whitespace(str)); @@ -2832,14 +3050,14 @@ test_util_eat_whitespace(void *ptr) test_eq_ptr(str, eat_whitespace_eos_no_nl(str, str + strlen(str))); /* Empty string */ - strcpy(str, ""); + strlcpy(str, "", sizeof(str)); test_eq_ptr(str, eat_whitespace(str)); test_eq_ptr(str, eat_whitespace_eos(str, str)); test_eq_ptr(str, eat_whitespace_no_nl(str)); test_eq_ptr(str, eat_whitespace_eos_no_nl(str, str)); /* Only ws */ - strcpy(str, " \t\r\n"); + strlcpy(str, " \t\r\n", sizeof(str)); test_eq_ptr(str + strlen(str), eat_whitespace(str)); test_eq_ptr(str + strlen(str), eat_whitespace_eos(str, str + strlen(str))); test_eq_ptr(str + strlen(str) - 1, @@ -2847,7 +3065,7 @@ test_util_eat_whitespace(void *ptr) test_eq_ptr(str + strlen(str) - 1, eat_whitespace_eos_no_nl(str, str + strlen(str))); - strcpy(str, " \t\r "); + strlcpy(str, " \t\r ", sizeof(str)); test_eq_ptr(str + strlen(str), eat_whitespace(str)); test_eq_ptr(str + strlen(str), eat_whitespace_eos(str, str + strlen(str))); @@ -2856,7 +3074,7 @@ test_util_eat_whitespace(void *ptr) eat_whitespace_eos_no_nl(str, str + strlen(str))); /* Multiple ws */ - strcpy(str, "fuubaar"); + strlcpy(str, "fuubaar", sizeof(str)); for (i = 0; i < sizeof(ws); ++i) str[i] = ws[i]; test_eq_ptr(str + sizeof(ws), eat_whitespace(str)); @@ -2866,28 +3084,28 @@ test_util_eat_whitespace(void *ptr) eat_whitespace_eos_no_nl(str, str + strlen(str))); /* Eat comment */ - strcpy(str, "# Comment \n No Comment"); + strlcpy(str, "# Comment \n No Comment", sizeof(str)); test_streq("No Comment", eat_whitespace(str)); test_streq("No Comment", eat_whitespace_eos(str, str + strlen(str))); test_eq_ptr(str, eat_whitespace_no_nl(str)); test_eq_ptr(str, eat_whitespace_eos_no_nl(str, str + strlen(str))); /* Eat comment & ws mix */ - strcpy(str, " # \t Comment \n\t\nNo Comment"); + strlcpy(str, " # \t Comment \n\t\nNo Comment", sizeof(str)); test_streq("No Comment", eat_whitespace(str)); test_streq("No Comment", eat_whitespace_eos(str, str + strlen(str))); test_eq_ptr(str + 1, eat_whitespace_no_nl(str)); test_eq_ptr(str + 1, eat_whitespace_eos_no_nl(str, str + strlen(str))); /* Eat entire comment */ - strcpy(str, "#Comment"); + strlcpy(str, "#Comment", sizeof(str)); test_eq_ptr(str + strlen(str), eat_whitespace(str)); test_eq_ptr(str + strlen(str), eat_whitespace_eos(str, str + strlen(str))); test_eq_ptr(str, eat_whitespace_no_nl(str)); test_eq_ptr(str, eat_whitespace_eos_no_nl(str, str + strlen(str))); /* Blank line, then comment */ - strcpy(str, " \t\n # Comment"); + strlcpy(str, " \t\n # Comment", sizeof(str)); test_eq_ptr(str + strlen(str), eat_whitespace(str)); test_eq_ptr(str + strlen(str), eat_whitespace_eos(str, str + strlen(str))); test_eq_ptr(str + 2, eat_whitespace_no_nl(str)); @@ -3212,12 +3430,176 @@ test_util_mathlog(void *arg) ; } +static void +test_util_round_to_next_multiple_of(void *arg) +{ + (void)arg; + + test_assert(round_uint64_to_next_multiple_of(0,1) == 0); + test_assert(round_uint64_to_next_multiple_of(0,7) == 0); + + test_assert(round_uint64_to_next_multiple_of(99,1) == 99); + test_assert(round_uint64_to_next_multiple_of(99,7) == 105); + test_assert(round_uint64_to_next_multiple_of(99,9) == 99); + + done: + ; +} + +static void +test_util_strclear(void *arg) +{ + static const char *vals[] = { "", "a", "abcdef", "abcdefgh", NULL }; + int i; + char *v = NULL; + (void)arg; + + for (i = 0; vals[i]; ++i) { + size_t n; + v = tor_strdup(vals[i]); + n = strlen(v); + tor_strclear(v); + tt_assert(tor_mem_is_zero(v, n+1)); + tor_free(v); + } + done: + tor_free(v); +} + #define UTIL_LEGACY(name) \ { #name, legacy_test_helper, 0, &legacy_setup, test_util_ ## name } #define UTIL_TEST(name, flags) \ { #name, test_util_ ## name, flags, NULL, NULL } +#ifdef FD_CLOEXEC +#define CAN_CHECK_CLOEXEC +static int +fd_is_cloexec(tor_socket_t fd) +{ + int flags = fcntl(fd, F_GETFD, 0); + return (flags & FD_CLOEXEC) == FD_CLOEXEC; +} +#endif + +#ifndef _WIN32 +#define CAN_CHECK_NONBLOCK +static int +fd_is_nonblocking(tor_socket_t fd) +{ + int flags = fcntl(fd, F_GETFL, 0); + return (flags & O_NONBLOCK) == O_NONBLOCK; +} +#endif + +static void +test_util_socket(void *arg) +{ + tor_socket_t fd1 = TOR_INVALID_SOCKET; + tor_socket_t fd2 = TOR_INVALID_SOCKET; + tor_socket_t fd3 = TOR_INVALID_SOCKET; + tor_socket_t fd4 = TOR_INVALID_SOCKET; + int n = get_n_open_sockets(); + + TT_BLATHER(("Starting with %d open sockets.", n)); + + (void)arg; + + fd1 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 0, 0); + fd2 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 0, 1); + tt_assert(SOCKET_OK(fd1)); + tt_assert(SOCKET_OK(fd2)); + tt_int_op(get_n_open_sockets(), ==, n + 2); + //fd3 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 1, 0); + //fd4 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 1, 1); + fd3 = tor_open_socket(AF_INET, SOCK_STREAM, 0); + fd4 = tor_open_socket_nonblocking(AF_INET, SOCK_STREAM, 0); + tt_assert(SOCKET_OK(fd3)); + tt_assert(SOCKET_OK(fd4)); + tt_int_op(get_n_open_sockets(), ==, n + 4); + +#ifdef CAN_CHECK_CLOEXEC + tt_int_op(fd_is_cloexec(fd1), ==, 0); + tt_int_op(fd_is_cloexec(fd2), ==, 0); + tt_int_op(fd_is_cloexec(fd3), ==, 1); + tt_int_op(fd_is_cloexec(fd4), ==, 1); +#endif +#ifdef CAN_CHECK_NONBLOCK + tt_int_op(fd_is_nonblocking(fd1), ==, 0); + tt_int_op(fd_is_nonblocking(fd2), ==, 1); + tt_int_op(fd_is_nonblocking(fd3), ==, 0); + tt_int_op(fd_is_nonblocking(fd4), ==, 1); +#endif + + tor_close_socket(fd1); + tor_close_socket(fd2); + fd1 = fd2 = TOR_INVALID_SOCKET; + tt_int_op(get_n_open_sockets(), ==, n + 2); + tor_close_socket(fd3); + tor_close_socket(fd4); + fd3 = fd4 = TOR_INVALID_SOCKET; + tt_int_op(get_n_open_sockets(), ==, n); + + done: + if (SOCKET_OK(fd1)) + tor_close_socket(fd1); + if (SOCKET_OK(fd2)) + tor_close_socket(fd2); + if (SOCKET_OK(fd3)) + tor_close_socket(fd3); + if (SOCKET_OK(fd4)) + tor_close_socket(fd4); +} + +static void * +socketpair_test_setup(const struct testcase_t *testcase) +{ + return testcase->setup_data; +} +static int +socketpair_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + (void)testcase; + (void)ptr; + return 1; +} + +static const struct testcase_setup_t socketpair_setup = { + socketpair_test_setup, socketpair_test_cleanup +}; + +/* Test for socketpair and ersatz_socketpair(). We test them both, since + * the latter is a tolerably good way to exersize tor_accept_socket(). */ +static void +test_util_socketpair(void *arg) +{ + const int ersatz = !strcmp(arg, "1"); + int (*const tor_socketpair_fn)(int, int, int, tor_socket_t[2]) = + ersatz ? tor_ersatz_socketpair : tor_socketpair; + int n = get_n_open_sockets(); + tor_socket_t fds[2] = {TOR_INVALID_SOCKET, TOR_INVALID_SOCKET}; + const int family = AF_UNIX; + + tt_int_op(0, ==, tor_socketpair_fn(family, SOCK_STREAM, 0, fds)); + tt_assert(SOCKET_OK(fds[0])); + tt_assert(SOCKET_OK(fds[1])); + tt_int_op(get_n_open_sockets(), ==, n + 2); +#ifdef CAN_CHECK_CLOEXEC + tt_int_op(fd_is_cloexec(fds[0]), ==, 1); + tt_int_op(fd_is_cloexec(fds[1]), ==, 1); +#endif +#ifdef CAN_CHECK_NONBLOCK + tt_int_op(fd_is_nonblocking(fds[0]), ==, 0); + tt_int_op(fd_is_nonblocking(fds[1]), ==, 0); +#endif + + done: + if (SOCKET_OK(fds[0])) + tor_close_socket(fds[0]); + if (SOCKET_OK(fds[1])) + tor_close_socket(fds[1]); +} + struct testcase_t util_tests[] = { UTIL_LEGACY(time), UTIL_TEST(parse_http_time, 0), @@ -3228,6 +3610,8 @@ struct testcase_t util_tests[] = { #ifndef _WIN32 UTIL_LEGACY(expand_filename), #endif + UTIL_LEGACY(escape_string_socks), + UTIL_LEGACY(string_is_key_value), UTIL_LEGACY(strmisc), UTIL_LEGACY(pow2), UTIL_LEGACY(gzip), @@ -3241,6 +3625,8 @@ struct testcase_t util_tests[] = { UTIL_LEGACY(path_is_relative), UTIL_LEGACY(strtok), UTIL_LEGACY(di_ops), + UTIL_TEST(round_to_next_multiple_of, 0), + UTIL_TEST(strclear, 0), UTIL_TEST(find_str_at_start_of_line, 0), UTIL_TEST(string_is_C_identifier, 0), UTIL_TEST(asprintf, 0), @@ -3249,14 +3635,15 @@ struct testcase_t util_tests[] = { #ifdef _WIN32 UTIL_TEST(load_win_lib, 0), #endif - UTIL_TEST(exit_status, 0), #ifndef _WIN32 + UTIL_TEST(exit_status, 0), UTIL_TEST(fgets_eagain, TT_SKIP), #endif UTIL_TEST(spawn_background_ok, 0), UTIL_TEST(spawn_background_fail, 0), UTIL_TEST(spawn_background_partial_read, 0), UTIL_TEST(format_hex_number, 0), + UTIL_TEST(format_dec_number, 0), UTIL_TEST(join_win_cmdline, 0), UTIL_TEST(split_lines, 0), UTIL_TEST(n_bits_set, 0), @@ -3268,8 +3655,14 @@ struct testcase_t util_tests[] = { UTIL_TEST(read_file_eof_tiny_limit, 0), UTIL_TEST(read_file_eof_two_loops, 0), UTIL_TEST(read_file_eof_zero_bytes, 0), + UTIL_TEST(write_chunks_to_file, 0), UTIL_TEST(mathlog, 0), UTIL_TEST(weak_random, 0), + UTIL_TEST(socket, TT_FORK), + { "socketpair", test_util_socketpair, TT_FORK, &socketpair_setup, + (void*)"0" }, + { "socketpair_ersatz", test_util_socketpair, TT_FORK, + &socketpair_setup, (void*)"1" }, END_OF_TESTCASES }; diff --git a/src/tools/tor-checkkey.c b/src/tools/tor-checkkey.c index a3860ca4b..d50f12ed2 100644 --- a/src/tools/tor-checkkey.c +++ b/src/tools/tor-checkkey.c @@ -1,8 +1,6 @@ /* Copyright (c) 2008-2013, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define CRYPTO_PRIVATE - #include "orconfig.h" #include <stdio.h> diff --git a/src/tools/tor-fw-helper/tor-fw-helper.c b/src/tools/tor-fw-helper/tor-fw-helper.c index bb6e70aaa..84cc21e34 100644 --- a/src/tools/tor-fw-helper/tor-fw-helper.c +++ b/src/tools/tor-fw-helper/tor-fw-helper.c @@ -496,6 +496,6 @@ main(int argc, char **argv) smartlist_free(tor_fw_options.ports_to_forward); } - exit(r); + exit(0); } diff --git a/src/tools/tor-gencert.c b/src/tools/tor-gencert.c index 3809b22d4..2f95cf7c5 100644 --- a/src/tools/tor-gencert.c +++ b/src/tools/tor-gencert.c @@ -27,8 +27,6 @@ #include <assert.h> #endif -#define CRYPTO_PRIVATE - #include "compat.h" #include "../common/util.h" #include "../common/torlog.h" @@ -36,7 +34,7 @@ #include "address.h" #define IDENTITY_KEY_BITS 3072 -#define SIGNING_KEY_BITS 1024 +#define SIGNING_KEY_BITS 2048 #define DEFAULT_LIFETIME 12 /* These globals are set via command line options. */ diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index f5d5cf446..0d6fe692b 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -241,7 +241,7 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.2.4.10-alpha-dev" +#define VERSION "0.2.5.1-alpha-dev" |