diff options
Diffstat (limited to 'src')
84 files changed, 8093 insertions, 2201 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am index b1e03cd710..20e3f5ae16 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -11,12 +11,44 @@ else libor_extra_source= endif -libor_a_SOURCES = address.c log.c util.c compat.c container.c mempool.c \ - memarea.c util_codedigest.c $(libor_extra_source) -libor_crypto_a_SOURCES = crypto.c aes.c tortls.c torgzip.c +libor_a_SOURCES = \ + address.c \ + compat.c \ + container.c \ + log.c \ + memarea.c \ + mempool.c \ + util.c \ + util_codedigest.c \ + $(libor_extra_source) + +libor_crypto_a_SOURCES = \ + aes.c \ + crypto.c \ + torgzip.c \ + tortls.c + libor_event_a_SOURCES = compat_libevent.c -noinst_HEADERS = address.h torlog.h crypto.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h ciphers.inc compat_libevent.h tortls_states.h +noinst_HEADERS = \ + address.h \ + aes.h \ + ciphers.inc \ + compat.h \ + compat_libevent.h \ + container.h \ + crypto.h \ + ht.h \ + memarea.h \ + mempool.h \ + strlcat.c \ + strlcpy.c \ + torgzip.h \ + torint.h \ + torlog.h \ + tortls.h \ + tortls_states.h \ + util.h common_sha1.i: $(libor_SOURCES) $(libor_crypto_a_SOURCES) $(noinst_HEADERS) if test "@SHA1SUM@" != none; then \ diff --git a/src/common/address.c b/src/common/address.c index 0e57528ae8..1f94377284 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -53,9 +53,7 @@ * socklen object in *<b>sa_out</b> of object size <b>len</b>. If not enough * room is free, or on error, return -1. Else return the length of the * sockaddr. */ -/* XXXX021 This returns socklen_t. socklen_t is sometimes unsigned. This - * function claims to return -1 sometimes. Problematic! */ -socklen_t +int tor_addr_to_sockaddr(const tor_addr_t *a, uint16_t port, struct sockaddr *sa_out, @@ -311,7 +309,7 @@ tor_addr_is_internal(const tor_addr_t *addr, int for_listening) * brackets. */ const char * -tor_addr_to_str(char *dest, const tor_addr_t *addr, int len, int decorate) +tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, int decorate) { const char *ptr; tor_assert(addr && dest); @@ -927,6 +925,19 @@ fmt_addr(const tor_addr_t *addr) return buf; } +/** Like fmt_addr(), but takes <b>addr</b> as a host-order IPv4 + * addresses. Also not thread-safe, also clobbers its return buffer on + * repeated calls. */ +const char * +fmt_addr32(uint32_t addr) +{ + static char buf[INET_NTOA_BUF_LEN]; + struct in_addr in; + in.s_addr = htonl(addr); + tor_inet_ntoa(&in, buf, sizeof(buf)); + return buf; +} + /** Convert the string in <b>src</b> to a tor_addr_t <b>addr</b>. The string * may be an IPv4 address, an IPv6 address, or an IPv6 address surrounded by * square brackets. diff --git a/src/common/address.h b/src/common/address.h index 371c6da190..6e9a84be8f 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -39,7 +39,7 @@ static INLINE sa_family_t tor_addr_family(const tor_addr_t *a); static INLINE const struct in_addr *tor_addr_to_in(const tor_addr_t *a); static INLINE int tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u); -socklen_t tor_addr_to_sockaddr(const tor_addr_t *a, uint16_t port, +int tor_addr_to_sockaddr(const tor_addr_t *a, uint16_t port, struct sockaddr *sa_out, socklen_t len); int tor_addr_from_sockaddr(tor_addr_t *a, const struct sockaddr *sa, uint16_t *port_out); @@ -108,6 +108,7 @@ tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u) int tor_addr_lookup(const char *name, uint16_t family, tor_addr_t *addr_out); char *tor_dup_addr(const tor_addr_t *addr) ATTR_MALLOC; const char *fmt_addr(const tor_addr_t *addr); +const char * fmt_addr32(uint32_t addr); int get_interface_address6(int severity, sa_family_t family, tor_addr_t *addr); /** Flag to specify how to do a comparison between addresses. In an "exact" @@ -144,7 +145,7 @@ int tor_addr_port_parse(const char *s, tor_addr_t *addr_out, int tor_addr_parse_mask_ports(const char *s, tor_addr_t *addr_out, maskbits_t *mask_out, uint16_t *port_min_out, uint16_t *port_max_out); -const char * tor_addr_to_str(char *dest, const tor_addr_t *addr, int len, +const char * tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, int decorate); int tor_addr_from_str(tor_addr_t *addr, const char *src); void tor_addr_copy(tor_addr_t *dest, const tor_addr_t *src); diff --git a/src/common/aes.c b/src/common/aes.c index 39ab2946c9..f03a76b037 100644 --- a/src/common/aes.c +++ b/src/common/aes.c @@ -288,11 +288,20 @@ void aes_crypt(aes_cnt_cipher_t *cipher, const char *input, size_t len, char *output) { - - /* XXXX This function is up to 5% of our runtime in some profiles; - * we should look into unrolling some of the loops; taking advantage - * of alignment, using a bigger buffer, and so on. Not till after 0.1.2.x, - * though. */ + /* This function alone is up to 5% of our runtime in some profiles; anything + * we could do to make it faster would be great. + * + * Experimenting suggests that unrolling the inner loop into a switch + * statement doesn't help. What does seem to help is making the input and + * output buffers word aligned, and never crypting anything besides an + * integer number of words at a time -- it shaves maybe 4-5% of the per-byte + * encryption time measured by bench_aes. We can't do that with the current + * Tor protocol, though: Tor really likes to crypt things in 509-byte + * chunks. + * + * If we were really ambitous, we'd force len to be a multiple of the block + * size, and shave maybe another 4-5% off. + */ int c = cipher->pos; if (PREDICT_UNLIKELY(!len)) return; diff --git a/src/common/compat.c b/src/common/compat.c index aa42514ddf..2737005cb0 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -101,6 +101,35 @@ #include "strlcat.c" #endif +/** As open(path, flags, mode), but return an fd with the close-on-exec mode + * set. */ +int +tor_open_cloexec(const char *path, int flags, unsigned mode) +{ +#ifdef O_CLOEXEC + return open(path, flags|O_CLOEXEC, mode); +#else + int fd = open(path, flags, mode); +#ifdef FD_CLOEXEC + if (fd >= 0) + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + return fd; +#endif +} + +/** DOCDOC */ +FILE * +tor_fopen_cloexec(const char *path, const char *mode) +{ + FILE *result = fopen(path, mode); +#ifdef FD_CLOEXEC + if (result != NULL) + fcntl(fileno(result), F_SETFD, FD_CLOEXEC); +#endif + return result; +} + #ifdef HAVE_SYS_MMAN_H /** Try to create a memory mapping for <b>filename</b> and return it. On * failure, return NULL. Sets errno properly, using ERANGE to mean @@ -116,7 +145,7 @@ tor_mmap_file(const char *filename) tor_assert(filename); - fd = open(filename, O_RDONLY, 0); + fd = tor_open_cloexec(filename, O_RDONLY, 0); if (fd<0) { int save_errno = errno; int severity = (errno == ENOENT) ? LOG_INFO : LOG_WARN; @@ -686,7 +715,7 @@ tor_lockfile_lock(const char *filename, int blocking, int *locked_out) *locked_out = 0; log_info(LD_FS, "Locking \"%s\"", filename); - fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0600); + fd = tor_open_cloexec(filename, O_RDWR|O_CREAT|O_TRUNC, 0600); if (fd < 0) { log_warn(LD_FS,"Couldn't open \"%s\" for locking: %s", filename, strerror(errno)); @@ -905,8 +934,16 @@ mark_socket_open(int s) int tor_open_socket(int domain, int type, int protocol) { - int s = socket(domain, type, protocol); + int s; +#ifdef SOCK_CLOEXEC +#define LINUX_CLOEXEC_OPEN_SOCKET + type |= SOCK_CLOEXEC; +#endif + s = socket(domain, type, protocol); if (s >= 0) { +#if !defined(LINUX_CLOEXEC_OPEN_SOCKET) && defined(FD_CLOEXEC) + fcntl(s, F_SETFD, FD_CLOEXEC); +#endif socket_accounting_lock(); ++n_sockets_open; mark_socket_open(s); @@ -919,8 +956,17 @@ tor_open_socket(int domain, int type, int protocol) int tor_accept_socket(int sockfd, struct sockaddr *addr, socklen_t *len) { - int s = accept(sockfd, addr, len); + int s; +#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) +#define LINUX_CLOEXEC_ACCEPT + s = accept4(sockfd, addr, len, SOCK_CLOEXEC); +#else + s = accept(sockfd, addr, len); +#endif if (s >= 0) { +#if !defined(LINUX_CLOEXEC_ACCEPT) && defined(FD_CLOEXEC) + fcntl(s, F_SETFD, FD_CLOEXEC); +#endif socket_accounting_lock(); ++n_sockets_open; mark_socket_open(s); @@ -976,8 +1022,17 @@ tor_socketpair(int family, int type, int protocol, int fd[2]) //don't use win32 socketpairs (they are always bad) #if defined(HAVE_SOCKETPAIR) && !defined(MS_WINDOWS) int r; +#ifdef SOCK_CLOEXEC + type |= SOCK_CLOEXEC; +#endif r = socketpair(family, type, protocol, fd); if (r == 0) { +#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC) + if (fd[0] >= 0) + fcntl(fd[0], F_SETFD, FD_CLOEXEC); + if (fd[1] >= 0) + fcntl(fd[1], F_SETFD, FD_CLOEXEC); +#endif socket_accounting_lock(); if (fd[0] >= 0) { ++n_sockets_open; @@ -1923,6 +1978,52 @@ spawn_exit(void) #endif } +/** Implementation logic for compute_num_cpus(). */ +static int +compute_num_cpus_impl(void) +{ +#ifdef MS_WINDOWS + SYSTEM_INFO info; + memset(&info, 0, sizeof(info)); + GetSystemInfo(&info); + if (info.dwNumberOfProcessors >= 1 && info.dwNumberOfProcessors < INT_MAX) + return (int)info.dwNumberOfProcessors; + else + return -1; +#elif defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_CONF) + long cpus = sysconf(_SC_NPROCESSORS_CONF); + if (cpus >= 1 && cpus < INT_MAX) + return (int)cpus; + else + return -1; +#else + return -1; +#endif +} + +#define MAX_DETECTABLE_CPUS 16 + +/** Return how many CPUs we are running with. We assume that nobody is + * using hot-swappable CPUs, so we don't recompute this after the first + * time. Return -1 if we don't know how to tell the number of CPUs on this + * system. + */ +int +compute_num_cpus(void) +{ + static int num_cpus = -2; + if (num_cpus == -2) { + num_cpus = compute_num_cpus_impl(); + tor_assert(num_cpus != -2); + if (num_cpus > MAX_DETECTABLE_CPUS) + log_notice(LD_GENERAL, "Wow! I detected that you have %d CPUs. I " + "will not autodetect any more than %d, though. If you " + "want to configure more, set NumCPUs in your torrc", + num_cpus, MAX_DETECTABLE_CPUS); + } + return num_cpus; +} + /** Set *timeval to the current time of day. On error, log and terminate. * (Same as gettimeofday(timeval,NULL), but never returns -1.) */ diff --git a/src/common/compat.h b/src/common/compat.h index ee20e716c0..a7970109d7 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -51,6 +51,8 @@ #include <netinet6/in6.h> #endif +#include <stdio.h> + #if defined (WINCE) #include <fcntl.h> #include <io.h> @@ -332,7 +334,17 @@ struct tm *tor_localtime_r(const time_t *timep, struct tm *result); struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); #endif +/** Return true iff the tvp is related to uvp according to the relational + * operator cmp. Recognized values for cmp are ==, <=, <, >=, and >. */ +#define tor_timercmp(tvp, uvp, cmp) \ + (((tvp)->tv_sec == (uvp)->tv_sec) ? \ + ((tvp)->tv_usec cmp (uvp)->tv_usec) : \ + ((tvp)->tv_sec cmp (uvp)->tv_sec)) + /* ===== File compatibility */ +int tor_open_cloexec(const char *path, int flags, unsigned mode); +FILE *tor_fopen_cloexec(const char *path, const char *mode); + int replace_file(const char *from, const char *to); int touch_file(const char *fname); @@ -527,6 +539,8 @@ void spawn_exit(void) ATTR_NORETURN; #undef TOR_IS_MULTITHREADED #endif +int compute_num_cpus(void); + /* Because we use threads instead of processes on most platforms (Windows, * Linux, etc), we need locking for them. On platforms with poor thread * support or broken gethostbyname_r, these functions are no-ops. */ diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index b9af61be88..b89c9cf7ba 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -159,9 +159,11 @@ struct event_base *the_event_base = NULL; /** Initialize the Libevent library and set up the event base. */ void -tor_libevent_initialize(void) +tor_libevent_initialize(tor_libevent_cfg *torcfg) { tor_assert(the_event_base == NULL); + /* some paths below don't use torcfg, so avoid unused variable warnings */ + (void)torcfg; #ifdef __APPLE__ if (MACOSX_KQUEUE_IS_BROKEN || @@ -171,7 +173,29 @@ tor_libevent_initialize(void) #endif #ifdef HAVE_EVENT2_EVENT_H - the_event_base = event_base_new(); + { + struct event_config *cfg = event_config_new(); + +#if defined(MS_WINDOWS) && defined(USE_BUFFEREVENTS) + if (! torcfg->disable_iocp) + event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP); +#endif + +#if defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER >= V(2,0,7) + if (torcfg->num_cpus > 0) + event_config_set_num_cpus_hint(cfg, torcfg->num_cpus); +#endif + +#if LIBEVENT_VERSION_NUMBER >= V(2,0,9) + /* We can enable changelist support with epoll, since we don't give + * Libevent any dup'd fds. This lets us avoid some syscalls. */ + event_config_set_flag(cfg, EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST); +#endif + + the_event_base = event_base_new_with_config(cfg); + + event_config_free(cfg); + } #else the_event_base = event_init(); #endif @@ -324,17 +348,21 @@ tor_check_libevent_version(const char *m, int server, version = tor_get_libevent_version(&v); - /* XXX Would it be worthwhile disabling the methods that we know - * are buggy, rather than just warning about them and then proceeding - * to use them? If so, we should probably not wrap this whole thing - * in HAVE_EVENT_GET_VERSION and HAVE_EVENT_GET_METHOD. -RD */ - /* XXXX The problem is that it's not trivial to get libevent to change it's - * method once it's initialized, and it's not trivial to tell what method it - * will use without initializing it. I guess we could preemptively disable - * buggy libevent modes based on the version _before_ initializing it, - * though, but then there's no good way (afaict) to warn "I would have used - * kqueue, but instead I'm using select." -NM */ - /* XXXX022 revist the above; it is fixable now. */ + /* It would be better to disable known-buggy methods than to simply + warn about them. However, it's not trivial to get libevent to change its + method once it's initialized, and it's not trivial to tell what method it + will use without initializing it. + + If we saw that the version was definitely bad, we could disable all the + methods that were bad for that version. But the issue with that is that + if you've found a libevent before 1.1, you are not at all guaranteed to + have _any_ good method to use. + + As of Libevent 2, we can do better, and have more control over what + methods get used. But the problem here is that there are no versions of + Libevent 2 that have buggy event cores, so there's no point in writing + disable code yet. + */ if (!strcmp(m, "kqueue")) { if (version < V_OLD(1,1,'b')) buggy = 1; @@ -551,3 +579,29 @@ periodic_timer_free(periodic_timer_t *timer) tor_free(timer); } +#ifdef USE_BUFFEREVENTS +static const struct timeval *one_tick = NULL; +/** + * Return a special timeout to be passed whenever libevent's O(1) timeout + * implementation should be used. Only use this when the timer is supposed + * to fire after 1 / TOR_LIBEVENT_TICKS_PER_SECOND seconds have passed. +*/ +const struct timeval * +tor_libevent_get_one_tick_timeout(void) +{ + if (PREDICT_UNLIKELY(one_tick == NULL)) { + struct event_base *base = tor_libevent_get_base(); + struct timeval tv; + if (TOR_LIBEVENT_TICKS_PER_SECOND == 1) { + tv.tv_sec = 1; + tv.tv_usec = 0; + } else { + tv.tv_sec = 0; + tv.tv_usec = 1000000 / TOR_LIBEVENT_TICKS_PER_SECOND; + } + one_tick = event_base_init_common_timeout(base, &tv); + } + return one_tick; +} +#endif + diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index fdf5e0a18f..ecf25806d5 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -8,6 +8,9 @@ struct event; struct event_base; +#ifdef USE_BUFFEREVENTS +struct bufferevent; +#endif #ifdef HAVE_EVENT2_EVENT_H #include <event2/util.h> @@ -53,7 +56,12 @@ struct timeval; int tor_event_base_loopexit(struct event_base *base, struct timeval *tv); #endif -void tor_libevent_initialize(void); +typedef struct tor_libevent_cfg { + int disable_iocp; + int num_cpus; +} tor_libevent_cfg; + +void tor_libevent_initialize(tor_libevent_cfg *cfg); struct event_base *tor_libevent_get_base(void); const char *tor_libevent_get_method(void); void tor_check_libevent_version(const char *m, int server, @@ -61,5 +69,10 @@ void tor_check_libevent_version(const char *m, int server, void tor_check_libevent_header_compatibility(void); const char *tor_libevent_get_version_str(void); +#ifdef USE_BUFFEREVENTS +#define TOR_LIBEVENT_TICKS_PER_SECOND 3 +const struct timeval *tor_libevent_get_one_tick_timeout(void); +#endif + #endif diff --git a/src/common/container.c b/src/common/container.c index 979e097d99..f4dc07024d 100644 --- a/src/common/container.c +++ b/src/common/container.c @@ -268,7 +268,6 @@ smartlist_subtract(smartlist_t *sl1, const smartlist_t *sl2) /** Remove the <b>idx</b>th element of sl; if idx is not the last * element, swap the last element of sl into the <b>idx</b>th space. - * Return the old value of the <b>idx</b>th element. */ void smartlist_del(smartlist_t *sl, int idx) diff --git a/src/common/crypto.h b/src/common/crypto.h index 0801728281..8fcc58c3ca 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -238,7 +238,8 @@ void secret_to_key(char *key_out, size_t key_out_len, const char *secret, size_t secret_len, const char *s2k_specifier); #ifdef CRYPTO_PRIVATE -/* Prototypes for private functions only used by tortls.c and crypto.c */ +/* Prototypes for private functions only used by tortls.c, crypto.c, and the + * unit tests. */ struct rsa_st; struct evp_pkey_st; struct dh_st; diff --git a/src/common/log.c b/src/common/log.c index 2fef2cc5d9..5d98ae6891 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -142,6 +142,17 @@ log_set_application_name(const char *name) appname = name ? tor_strdup(name) : NULL; } +/** Log time granularity in milliseconds. */ +static int log_time_granularity = 1; + +/** Define log time granularity for all logs to be <b>granularity_msec</b> + * milliseconds. */ +void +set_log_time_granularity(int granularity_msec) +{ + log_time_granularity = granularity_msec; +} + /** Helper: Write the standard prefix for log lines to a * <b>buf_len</b> character buffer in <b>buf</b>. */ @@ -152,14 +163,22 @@ _log_prefix(char *buf, size_t buf_len, int severity) struct timeval now; struct tm tm; size_t n; - int r; + int r, ms; tor_gettimeofday(&now); t = (time_t)now.tv_sec; + ms = (int)now.tv_usec / 1000; + if (log_time_granularity >= 1000) { + t -= t % (log_time_granularity / 1000); + ms = 0; + } else { + ms -= ((int)now.tv_usec / 1000) % log_time_granularity; + } n = strftime(buf, buf_len, "%b %d %H:%M:%S", tor_localtime_r(&t, &tm)); - r = tor_snprintf(buf+n, buf_len-n, ".%.3i [%s] ", - (int)now.tv_usec / 1000, sev_to_string(severity)); + r = tor_snprintf(buf+n, buf_len-n, ".%.3i [%s] ", ms, + sev_to_string(severity)); + if (r<0) return buf_len-1; else @@ -739,7 +758,7 @@ add_file_log(const log_severity_list_t *severity, const char *filename) int fd; logfile_t *lf; - fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0644); + fd = tor_open_cloexec(filename, O_WRONLY|O_CREAT|O_APPEND, 0644); if (fd<0) return -1; if (tor_fd_seekend(fd)<0) diff --git a/src/common/memarea.c b/src/common/memarea.c index 194deb8d2c..b91cad050b 100644 --- a/src/common/memarea.c +++ b/src/common/memarea.c @@ -53,7 +53,7 @@ realign_pointer(void *ptr) { uintptr_t x = (uintptr_t)ptr; x = (x+MEMAREA_ALIGN_MASK) & ~MEMAREA_ALIGN_MASK; - tor_assert(((void*)x) >= ptr); // XXXX021 remove this once bug 930 is solved + tor_assert(((void*)x) >= ptr); return (void*)x; } @@ -232,9 +232,10 @@ memarea_alloc(memarea_t *area, size_t sz) } result = chunk->next_mem; chunk->next_mem = chunk->next_mem + sz; - // XXXX021 remove these once bug 930 is solved. + tor_assert(chunk->next_mem >= chunk->u.mem); tor_assert(chunk->next_mem <= chunk->u.mem+chunk->mem_size); + chunk->next_mem = realign_pointer(chunk->next_mem); return result; } diff --git a/src/common/mempool.c b/src/common/mempool.c index c444923189..520e470c7f 100644 --- a/src/common/mempool.c +++ b/src/common/mempool.c @@ -137,7 +137,8 @@ struct mp_chunk_t { int capacity; /**< Number of items that can be fit into this chunk. */ size_t mem_size; /**< Number of usable bytes in mem. */ char *next_mem; /**< Pointer into part of <b>mem</b> not yet carved up. */ - char mem[1]; /**< Storage for this chunk. (Not actual size.) */ + /** Storage for this chunk */ + char mem[FLEXIBLE_ARRAY_MEMBER]; }; /** Number of extra bytes needed beyond mem_size to allocate a chunk. */ diff --git a/src/common/torlog.h b/src/common/torlog.h index f0be732270..1fe5806d16 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -143,6 +143,7 @@ void change_callback_log_severity(int loglevelMin, int loglevelMax, log_callback cb); void flush_pending_log_callbacks(void); void log_set_application_name(const char *name); +void set_log_time_granularity(int granularity_msec); /* Outputs a message to stdout */ void tor_log(int severity, log_domain_mask_t domain, const char *format, ...) diff --git a/src/common/tortls.c b/src/common/tortls.c index 9d22657f6d..aa5dd1bc97 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -44,7 +44,14 @@ #error "We require OpenSSL >= 0.9.7" #endif +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent_ssl.h> +#include <event2/buffer.h> +#include "compat_libevent.h" +#endif + #define CRYPTO_PRIVATE /* to import prototypes from crypto.h */ +#define TORTLS_PRIVATE #include "crypto.h" #include "tortls.h" @@ -109,6 +116,7 @@ struct tor_tls_t { enum { TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE, TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED, TOR_TLS_ST_RENEGOTIATE, + TOR_TLS_ST_BUFFEREVENT } state : 3; /**< The current SSL state, depending on which operations have * completed successfully. */ unsigned int isServer:1; /**< True iff this is a server-side connection */ @@ -117,8 +125,10 @@ struct tor_tls_t { * of the connection protocol (client sends * different cipher list, server sends only * one certificate). */ - /** True iff we should call negotiated_callback when we're done reading. */ + /** True iff we should call negotiated_callback when we're done reading. */ unsigned int got_renegotiate:1; + /** Incremented every time we start the server side of a handshake. */ + uint8_t server_handshake_count; size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last * time. */ /** Last values retrieved from BIO_number_read()/write(); see @@ -189,7 +199,7 @@ static X509* tor_tls_create_certificate(crypto_pk_env_t *rsa, const char *cname, const char *cname_sign, unsigned int lifetime); -static void tor_tls_unblock_renegotiation(tor_tls_t *tls); + static int tor_tls_context_init_one(tor_tls_context_t **ppcontext, crypto_pk_env_t *identity, unsigned int key_lifetime); @@ -200,6 +210,7 @@ static tor_tls_context_t *tor_tls_context_new(crypto_pk_env_t *identity, * to touch them. */ static tor_tls_context_t *server_tls_context = NULL; static tor_tls_context_t *client_tls_context = NULL; + /** True iff tor_tls_init() has been called. */ static int tls_library_is_initialized = 0; @@ -223,36 +234,46 @@ ssl_state_to_string(int ssl_state) return buf; } +void +tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, + int severity, int domain, const char *doing) +{ + const char *state = NULL, *addr; + const char *msg, *lib, *func; + int st; + + st = (tls && tls->ssl) ? tls->ssl->state : -1; + state = (st>=0)?ssl_state_to_string(st):"---"; + + addr = tls ? tls->address : NULL; + + msg = (const char*)ERR_reason_error_string(err); + lib = (const char*)ERR_lib_error_string(err); + func = (const char*)ERR_func_error_string(err); + if (!msg) msg = "(null)"; + if (!lib) lib = "(null)"; + if (!func) func = "(null)"; + if (doing) { + log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)", + doing, addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } else { + log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)", + addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } +} + /** Log all pending tls errors at level <b>severity</b>. Use * <b>doing</b> to describe our current activities. */ static void tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) { - const char *state = NULL; - int st; unsigned long err; - const char *msg, *lib, *func, *addr; - addr = tls ? tls->address : NULL; - st = (tls && tls->ssl) ? tls->ssl->state : -1; + while ((err = ERR_get_error()) != 0) { - msg = (const char*)ERR_reason_error_string(err); - lib = (const char*)ERR_lib_error_string(err); - func = (const char*)ERR_func_error_string(err); - if (!state) - state = (st>=0)?ssl_state_to_string(st):"---"; - if (!msg) msg = "(null)"; - if (!lib) lib = "(null)"; - if (!func) func = "(null)"; - if (doing) { - log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)", - doing, addr?" with ":"", addr?addr:"", - msg, lib, func, state); - } else { - log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)", - addr?" with ":"", addr?addr:"", - msg, lib, func, state); - } + tor_tls_log_one_error(tls, err, severity, domain, doing); } } @@ -912,6 +933,8 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) /* Check whether we're watching for renegotiates. If so, this is one! */ if (tls->negotiated_callback) tls->got_renegotiate = 1; + if (tls->server_handshake_count < 127) /*avoid any overflow possibility*/ + ++tls->server_handshake_count; } else { log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); } @@ -930,6 +953,10 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) if (tls) { tls->wasV2Handshake = 1; +#ifdef USE_BUFFEREVENTS + if (use_unsafe_renegotiation_flag) + tls->ssl->s3->flags |= SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +#endif } else { log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); } @@ -1116,7 +1143,7 @@ tor_tls_set_renegotiate_callback(tor_tls_t *tls, /** If this version of openssl requires it, turn on renegotiation on * <b>tls</b>. */ -static void +void tor_tls_unblock_renegotiation(tor_tls_t *tls) { /* Yes, we know what we are doing here. No, we do not treat a renegotiation @@ -1140,6 +1167,19 @@ tor_tls_block_renegotiation(tor_tls_t *tls) tls->ssl->s3->flags &= ~SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; } +void +tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) +{ + if (use_unsafe_renegotiation_flag) { + tor_assert(0 != (tls->ssl->s3->flags & + SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); + } + if (use_unsafe_renegotiation_op) { + long options = SSL_get_options(tls->ssl); + tor_assert(0 != (options & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); + } +} + /** Return whether this tls initiated the connect (client) or * received it (server). */ int @@ -1284,56 +1324,86 @@ tor_tls_handshake(tor_tls_t *tls) } if (r == TOR_TLS_DONE) { tls->state = TOR_TLS_ST_OPEN; - if (tls->isServer) { - SSL_set_info_callback(tls->ssl, NULL); - SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); - /* There doesn't seem to be a clear OpenSSL API to clear mode flags. */ - tls->ssl->mode &= ~SSL_MODE_NO_AUTO_CHAIN; + return tor_tls_finish_handshake(tls); + } + return r; +} + +/** Perform the final part of the intial TLS handshake on <b>tls</b>. This + * should be called for the first handshake only: it determines whether the v1 + * or the v2 handshake was used, and adjusts things for the renegotiation + * handshake as appropriate. + * + * tor_tls_handshake() calls this on its own; you only need to call this if + * bufferevent is doing the handshake for you. + */ +int +tor_tls_finish_handshake(tor_tls_t *tls) +{ + int r = TOR_TLS_DONE; + if (tls->isServer) { + SSL_set_info_callback(tls->ssl, NULL); + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); + /* There doesn't seem to be a clear OpenSSL API to clear mode flags. */ + tls->ssl->mode &= ~SSL_MODE_NO_AUTO_CHAIN; #ifdef V2_HANDSHAKE_SERVER - if (tor_tls_client_is_using_v2_ciphers(tls->ssl, ADDR(tls))) { - /* This check is redundant, but back when we did it in the callback, - * we might have not been able to look up the tor_tls_t if the code - * was buggy. Fixing that. */ - if (!tls->wasV2Handshake) { - log_warn(LD_BUG, "For some reason, wasV2Handshake didn't" - " get set. Fixing that."); - } - tls->wasV2Handshake = 1; - log_debug(LD_HANDSHAKE, - "Completed V2 TLS handshake with client; waiting " - "for renegotiation."); - } else { - tls->wasV2Handshake = 0; + if (tor_tls_client_is_using_v2_ciphers(tls->ssl, ADDR(tls))) { + /* This check is redundant, but back when we did it in the callback, + * we might have not been able to look up the tor_tls_t if the code + * was buggy. Fixing that. */ + if (!tls->wasV2Handshake) { + log_warn(LD_BUG, "For some reason, wasV2Handshake didn't" + " get set. Fixing that."); } -#endif + tls->wasV2Handshake = 1; + log_debug(LD_HANDSHAKE, "Completed V2 TLS handshake with client; waiting" + " for renegotiation."); } else { + tls->wasV2Handshake = 0; + } +#endif + } else { #ifdef V2_HANDSHAKE_CLIENT - /* If we got no ID cert, we're a v2 handshake. */ - X509 *cert = SSL_get_peer_certificate(tls->ssl); - STACK_OF(X509) *chain = SSL_get_peer_cert_chain(tls->ssl); - int n_certs = sk_X509_num(chain); - if (n_certs > 1 || (n_certs == 1 && cert != sk_X509_value(chain, 0))) { - log_debug(LD_HANDSHAKE, "Server sent back multiple certificates; it " - "looks like a v1 handshake on %p", tls); - tls->wasV2Handshake = 0; - } else { - log_debug(LD_HANDSHAKE, - "Server sent back a single certificate; looks like " - "a v2 handshake on %p.", tls); - tls->wasV2Handshake = 1; - } - if (cert) - X509_free(cert); + /* If we got no ID cert, we're a v2 handshake. */ + X509 *cert = SSL_get_peer_certificate(tls->ssl); + STACK_OF(X509) *chain = SSL_get_peer_cert_chain(tls->ssl); + int n_certs = sk_X509_num(chain); + if (n_certs > 1 || (n_certs == 1 && cert != sk_X509_value(chain, 0))) { + log_debug(LD_HANDSHAKE, "Server sent back multiple certificates; it " + "looks like a v1 handshake on %p", tls); + tls->wasV2Handshake = 0; + } else { + log_debug(LD_HANDSHAKE, + "Server sent back a single certificate; looks like " + "a v2 handshake on %p.", tls); + tls->wasV2Handshake = 1; + } + if (cert) + X509_free(cert); #endif - if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { - tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); - r = TOR_TLS_ERROR_MISC; - } + if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { + tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); + r = TOR_TLS_ERROR_MISC; } } return r; } +#ifdef USE_BUFFEREVENTS +/** Put <b>tls</b>, which must be a client connection, into renegotiation + * mode. */ +int +tor_tls_start_renegotiating(tor_tls_t *tls) +{ + int r = SSL_renegotiate(tls->ssl); + if (r <= 0) { + return tor_tls_get_error(tls, r, 0, "renegotiating", LOG_WARN, + LD_HANDSHAKE); + } + return 0; +} +#endif + /** Client only: Renegotiate a TLS session. When finished, returns * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, or * TOR_TLS_WANTWRITE. @@ -1550,6 +1620,8 @@ tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_env_t **identity_key) log_fn(severity,LD_PROTOCOL,"No distinct identity certificate found"); goto done; } + tls_log_errors(tls, severity, LD_HANDSHAKE, "before verifying certificate"); + if (!(id_pkey = X509_get_pubkey(id_cert)) || X509_verify(cert, id_pkey) <= 0) { log_fn(severity,LD_PROTOCOL,"X509_verify on cert and pkey returned <= 0"); @@ -1699,6 +1771,22 @@ tor_tls_used_v1_handshake(tor_tls_t *tls) return 1; } +/** Return the number of server handshakes that we've noticed doing on + * <b>tls</b>. */ +int +tor_tls_get_num_server_handshakes(tor_tls_t *tls) +{ + return tls->server_handshake_count; +} + +/** Return true iff the server TLS connection <b>tls</b> got the renegotiation + * request it was waiting for. */ +int +tor_tls_server_got_renegotiate(tor_tls_t *tls) +{ + return tls->got_renegotiate; +} + /** Examine the amount of memory used and available for buffers in <b>tls</b>. * Set *<b>rbuf_capacity</b> to the amount of storage allocated for the read * buffer and *<b>rbuf_bytes</b> to the amount actually used. @@ -1721,3 +1809,71 @@ tor_tls_get_buffer_sizes(tor_tls_t *tls, *wbuf_bytes = tls->ssl->s3->wbuf.left; } +#ifdef USE_BUFFEREVENTS +/** Construct and return an TLS-encrypting bufferevent to send data over + * <b>socket</b>, which must match the socket of the underlying bufferevent + * <b>bufev_in</b>. The TLS object <b>tls</b> is used for encryption. + * + * This function will either create a filtering bufferevent that wraps around + * <b>bufev_in</b>, or it will free bufev_in and return a new bufferevent that + * uses the <b>tls</b> to talk to the network directly. Do not use + * <b>bufev_in</b> after calling this function. + * + * The connection will start out doing a server handshake if <b>receiving</b> + * is strue, and a client handshake otherwise. + * + * Returns NULL on failure. + */ +struct bufferevent * +tor_tls_init_bufferevent(tor_tls_t *tls, struct bufferevent *bufev_in, + evutil_socket_t socket, int receiving, + int filter) +{ + struct bufferevent *out; + const enum bufferevent_ssl_state state = receiving ? + BUFFEREVENT_SSL_ACCEPTING : BUFFEREVENT_SSL_CONNECTING; + + if (filter) { + /* Grab an extra reference to the SSL, since BEV_OPT_CLOSE_ON_FREE + means that the SSL will get freed too. + + This increment makes our SSL usage not-threadsafe, BTW. We should + see if we're allowed to use CRYPTO_add from outside openssl. */ + tls->ssl->references += 1; + out = bufferevent_openssl_filter_new(tor_libevent_get_base(), + bufev_in, + tls->ssl, + state, + BEV_OPT_DEFER_CALLBACKS| + BEV_OPT_CLOSE_ON_FREE); + } else { + if (bufev_in) { + evutil_socket_t s = bufferevent_getfd(bufev_in); + tor_assert(s == -1 || s == socket); + tor_assert(evbuffer_get_length(bufferevent_get_input(bufev_in)) == 0); + tor_assert(evbuffer_get_length(bufferevent_get_output(bufev_in)) == 0); + tor_assert(BIO_number_read(SSL_get_rbio(tls->ssl)) == 0); + tor_assert(BIO_number_written(SSL_get_rbio(tls->ssl)) == 0); + bufferevent_free(bufev_in); + } + + /* Current versions (as of 2.0.x) of Libevent need to defer + * bufferevent_openssl callbacks, or else our callback functions will + * get called reentrantly, which is bad for us. + */ + out = bufferevent_openssl_socket_new(tor_libevent_get_base(), + socket, + tls->ssl, + state, + BEV_OPT_DEFER_CALLBACKS); + } + tls->state = TOR_TLS_ST_BUFFEREVENT; + + /* Unblock _after_ creating the bufferevent, since accept/connect tend to + * clear flags. */ + tor_tls_unblock_renegotiation(tls); + + return out; +} +#endif + diff --git a/src/common/tortls.h b/src/common/tortls.h index 55fee81aea..ecb5bd2fbe 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -67,8 +67,11 @@ int tor_tls_check_lifetime(tor_tls_t *tls, int tolerance); int tor_tls_read(tor_tls_t *tls, char *cp, size_t len); int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n); int tor_tls_handshake(tor_tls_t *tls); +int tor_tls_finish_handshake(tor_tls_t *tls); int tor_tls_renegotiate(tor_tls_t *tls); +void tor_tls_unblock_renegotiation(tor_tls_t *tls); void tor_tls_block_renegotiation(tor_tls_t *tls); +void tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls); int tor_tls_shutdown(tor_tls_t *tls); int tor_tls_get_pending_bytes(tor_tls_t *tls); size_t tor_tls_get_forced_write_size(tor_tls_t *tls); @@ -81,12 +84,24 @@ void tor_tls_get_buffer_sizes(tor_tls_t *tls, size_t *wbuf_capacity, size_t *wbuf_bytes); int tor_tls_used_v1_handshake(tor_tls_t *tls); +int tor_tls_get_num_server_handshakes(tor_tls_t *tls); +int tor_tls_server_got_renegotiate(tor_tls_t *tls); /* Log and abort if there are unhandled TLS errors in OpenSSL's error stack. */ #define check_no_tls_errors() _check_no_tls_errors(__FILE__,__LINE__) void _check_no_tls_errors(const char *fname, int line); +void tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, + int severity, int domain, const char *doing); + +#ifdef USE_BUFFEREVENTS +int tor_tls_start_renegotiating(tor_tls_t *tls); +struct bufferevent *tor_tls_init_bufferevent(tor_tls_t *tls, + struct bufferevent *bufev_in, + evutil_socket_t socket, int receiving, + int filter); +#endif #endif diff --git a/src/common/util.c b/src/common/util.c index abd87ea652..e676530b29 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -14,6 +14,7 @@ #define _GNU_SOURCE #include "orconfig.h" +#define UTIL_PRIVATE #include "util.h" #include "torlog.h" #undef log @@ -942,7 +943,7 @@ esc_for_log(const char *s) char *result, *outp; size_t len = 3; if (!s) { - return tor_strdup(""); + return tor_strdup("(null)"); } for (cp = s; *cp; ++cp) { @@ -1511,83 +1512,6 @@ update_approx_time(time_t now) #endif /* ===== - * Fuzzy time - * XXXX022 Use this consistently or rip most of it out. - * ===== */ - -/* In a perfect world, everybody would run NTP, and NTP would be perfect, so - * if we wanted to know "Is the current time before time X?" we could just say - * "time(NULL) < X". - * - * But unfortunately, many users are running Tor in an imperfect world, on - * even more imperfect computers. Hence, we need to track time oddly. We - * model the user's computer as being "skewed" from accurate time by - * -<b>ftime_skew</b> seconds, such that our best guess of the current time is - * time(NULL)+ftime_skew. We also assume that our measurements of time may - * have up to <b>ftime_slop</b> seconds of inaccuracy; IOW, our window of - * estimate for the current time is now + ftime_skew +/- ftime_slop. - */ -/** Our current estimate of our skew, such that we think the current time is - * closest to time(NULL)+ftime_skew. */ -static int ftime_skew = 0; -/** Tolerance during time comparisons, in seconds. */ -static int ftime_slop = 60; -/** Set the largest amount of sloppiness we'll allow in fuzzy time - * comparisons. */ -void -ftime_set_maximum_sloppiness(int seconds) -{ - tor_assert(seconds >= 0); - ftime_slop = seconds; -} -/** Set the amount by which we believe our system clock to differ from - * real time. */ -void -ftime_set_estimated_skew(int seconds) -{ - ftime_skew = seconds; -} -#if 0 -void -ftime_get_window(time_t now, ftime_t *ft_out) -{ - ft_out->earliest = now + ftime_skew - ftime_slop; - ft_out->latest = now + ftime_skew + ftime_slop; -} -#endif -/** Return true iff we think that <b>now</b> might be after <b>when</b>. */ -int -ftime_maybe_after(time_t now, time_t when) -{ - /* It may be after when iff the latest possible current time is after when */ - return (now + ftime_skew + ftime_slop) >= when; -} -/** Return true iff we think that <b>now</b> might be before <b>when</b>. */ -int -ftime_maybe_before(time_t now, time_t when) -{ - /* It may be before when iff the earliest possible current time is before */ - return (now + ftime_skew - ftime_slop) < when; -} -/** Return true if we think that <b>now</b> is definitely after <b>when</b>. */ -int -ftime_definitely_after(time_t now, time_t when) -{ - /* It is definitely after when if the earliest time it could be is still - * after when. */ - return (now + ftime_skew - ftime_slop) >= when; -} -/** Return true if we think that <b>now</b> is definitely before <b>when</b>. - */ -int -ftime_definitely_before(time_t now, time_t when) -{ - /* It is definitely before when if the latest time it could be is still - * before when. */ - return (now + ftime_skew + ftime_slop) < when; -} - -/* ===== * Rate limiting * ===== */ @@ -1899,7 +1823,7 @@ start_writing_to_file(const char *fname, int open_flags, int mode, if (open_flags & O_BINARY) new_file->binary = 1; - new_file->fd = open(open_name, open_flags, mode); + new_file->fd = tor_open_cloexec(open_name, open_flags, mode); if (new_file->fd < 0) { log_warn(LD_FS, "Couldn't open \"%s\" (%s) for writing: %s", open_name, fname, strerror(errno)); @@ -2120,7 +2044,7 @@ read_file_to_str(const char *filename, int flags, struct stat *stat_out) tor_assert(filename); - fd = open(filename,O_RDONLY|(bin?O_BINARY:O_TEXT),0); + fd = tor_open_cloexec(filename,O_RDONLY|(bin?O_BINARY:O_TEXT),0); if (fd<0) { int severity = LOG_WARN; int save_errno = errno; @@ -2506,18 +2430,21 @@ digit_to_num(char d) * success, store the result in <b>out</b>, advance bufp to the next * character, and return 0. On failure, return -1. */ static int -scan_unsigned(const char **bufp, unsigned *out, int width) +scan_unsigned(const char **bufp, unsigned *out, int width, int base) { unsigned result = 0; int scanned_so_far = 0; + const int hex = base==16; + tor_assert(base == 10 || base == 16); if (!bufp || !*bufp || !out) return -1; if (width<0) width=MAX_SCANF_WIDTH; - while (**bufp && TOR_ISDIGIT(**bufp) && scanned_so_far < width) { - int digit = digit_to_num(*(*bufp)++); - unsigned new_result = result * 10 + digit; + while (**bufp && (hex?TOR_ISXDIGIT(**bufp):TOR_ISDIGIT(**bufp)) + && scanned_so_far < width) { + int digit = hex?hex_decode_digit(*(*bufp)++):digit_to_num(*(*bufp)++); + unsigned new_result = result * base + digit; if (new_result > UINT32_MAX || new_result < result) return -1; /* over/underflow. */ result = new_result; @@ -2579,11 +2506,12 @@ tor_vsscanf(const char *buf, const char *pattern, va_list ap) if (!width) /* No zero-width things. */ return -1; } - if (*pattern == 'u') { + if (*pattern == 'u' || *pattern == 'x') { unsigned *u = va_arg(ap, unsigned *); + const int base = (*pattern == 'u') ? 10 : 16; if (!*buf) return n_matched; - if (scan_unsigned(&buf, u, width)<0) + if (scan_unsigned(&buf, u, width, base)<0) return n_matched; ++pattern; ++n_matched; @@ -2620,9 +2548,9 @@ tor_vsscanf(const char *buf, const char *pattern, va_list ap) /** Minimal sscanf replacement: parse <b>buf</b> according to <b>pattern</b> * and store the results in the corresponding argument fields. Differs from - * sscanf in that it: Only handles %u and %Ns. Does not handle arbitrarily - * long widths. %u does not consume any space. Is locale-independent. - * Returns -1 on malformed patterns. + * sscanf in that it: Only handles %u and %x and %Ns. Does not handle + * arbitrarily long widths. %u and %x do not consume any space. Is + * locale-independent. Returns -1 on malformed patterns. * * (As with other locale-independent functions, we need this to parse data that * is in ASCII without worrying that the C library's locale-handling will make @@ -2816,7 +2744,7 @@ finish_daemon(const char *desired_cwd) exit(1); } - nullfd = open("/dev/null", O_RDWR); + nullfd = tor_open_cloexec("/dev/null", O_RDWR, 0); if (nullfd < 0) { log_err(LD_GENERAL,"/dev/null can't be opened. Exiting."); exit(1); @@ -2887,3 +2815,449 @@ load_windows_system_library(const TCHAR *library_name) } #endif +/** 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. + * + * <b>hex_errno</b> must have at least HEX_ERRNO_SIZE bytes available. + * + * The format of <b>hex_errno</b> is: "CHILD_STATE/ERRNO\n", left-padded + * with spaces. Note that there is no trailing \0. CHILD_STATE indicates where + * in the processs of starting the child process did the failure occur (see + * CHILD_STATE_* macros for definition), and SAVED_ERRNO is the value of + * errno when the failure occurred. + */ + +void +format_helper_exit_status(unsigned char child_state, int saved_errno, + char *hex_errno) +{ + unsigned int unsigned_errno; + char *cur; + size_t i; + + /* Fill hex_errno with spaces, and a trailing newline (memset may + not be signal handler safe, so we can't use it) */ + for (i = 0; i < (HEX_ERRNO_SIZE - 1); i++) + hex_errno[i] = ' '; + hex_errno[HEX_ERRNO_SIZE - 1] = '\n'; + + /* Convert errno to be unsigned for hex conversion */ + if (saved_errno < 0) { + unsigned_errno = (unsigned int) -saved_errno; + } else { + unsigned_errno = (unsigned int) saved_errno; + } + + /* Convert errno to hex (start before \n) */ + cur = hex_errno + HEX_ERRNO_SIZE - 2; + + /* Check for overflow on first iteration of the loop */ + if (cur < hex_errno) + return; + + do { + *cur-- = "0123456789ABCDEF"[unsigned_errno % 16]; + unsigned_errno /= 16; + } while (unsigned_errno != 0 && cur >= hex_errno); + + /* Prepend the minus sign if errno was negative */ + if (saved_errno < 0 && cur >= hex_errno) + *cur-- = '-'; + + /* Leave a gap */ + if (cur >= hex_errno) + *cur-- = '/'; + + /* Check for overflow on first iteration of the loop */ + if (cur < hex_errno) + return; + + /* Convert child_state to hex */ + do { + *cur-- = "0123456789ABCDEF"[child_state % 16]; + child_state /= 16; + } while (child_state != 0 && cur >= hex_errno); +} + +/* Maximum number of file descriptors, if we cannot get it via sysconf() */ +#define DEFAULT_MAX_FD 256 + +#define CHILD_STATE_INIT 0 +#define CHILD_STATE_PIPE 1 +#define CHILD_STATE_MAXFD 2 +#define CHILD_STATE_FORK 3 +#define CHILD_STATE_DUPOUT 4 +#define CHILD_STATE_DUPERR 5 +#define CHILD_STATE_REDIRECT 6 +#define CHILD_STATE_CLOSEFD 7 +#define CHILD_STATE_EXEC 8 +#define CHILD_STATE_FAILEXEC 9 + +#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code " + +/** Start a program in the background. If <b>filename</b> contains a '/', + * then it will be treated as an absolute or relative path. Otherwise the + * system path will be searched for <b>filename</b>. The strings in + * <b>argv</b> will be passed as the command line arguments of the child + * program (following convention, argv[0] should normally be the filename of + * the executable). The last element of argv must be NULL. If the child + * program is launched, the PID will be returned and <b>stdout_read</b> and + * <b>stdout_err</b> will be set to file descriptors from which the stdout + * and stderr, respectively, output of the child program can be read, and the + * stdin of the child process shall be set to /dev/null. Otherwise returns + * -1. Some parts of this code are based on the POSIX subprocess module from + * Python. + */ +int +tor_spawn_background(const char *const filename, int *stdout_read, + int *stderr_read, const char **argv) +{ +#ifdef MS_WINDOWS + (void) filename; (void) stdout_read; (void) stderr_read; (void) argv; + log_warn(LD_BUG, "not yet implemented on Windows."); + return -1; +#else + pid_t pid; + int stdout_pipe[2]; + int stderr_pipe[2]; + int fd, retval; + ssize_t nbytes; + + const char *error_message = SPAWN_ERROR_MESSAGE; + size_t error_message_length; + + /* Represents where in the process of spawning the program is; + this is used for printing out the error message */ + unsigned char child_state = CHILD_STATE_INIT; + + char hex_errno[HEX_ERRNO_SIZE]; + + static int max_fd = -1; + + /* We do the strlen here because strlen() is not signal handler safe, + and we are not allowed to use unsafe functions between fork and exec */ + error_message_length = strlen(error_message); + + child_state = CHILD_STATE_PIPE; + + /* Set up pipe for redirecting stdout and stderr of child */ + retval = pipe(stdout_pipe); + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to set up pipe for stdout communication with child process: %s", + strerror(errno)); + return -1; + } + + retval = pipe(stderr_pipe); + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to set up pipe for stderr communication with child process: %s", + strerror(errno)); + return -1; + } + + child_state = CHILD_STATE_MAXFD; + +#ifdef _SC_OPEN_MAX + if (-1 != max_fd) { + max_fd = (int) sysconf(_SC_OPEN_MAX); + if (max_fd == -1) + max_fd = DEFAULT_MAX_FD; + log_warn(LD_GENERAL, + "Cannot find maximum file descriptor, assuming %d", max_fd); + } +#else + max_fd = DEFAULT_MAX_FD; +#endif + + child_state = CHILD_STATE_FORK; + + pid = fork(); + if (0 == pid) { + /* In child */ + + child_state = CHILD_STATE_DUPOUT; + + /* Link child stdout to the write end of the pipe */ + retval = dup2(stdout_pipe[1], STDOUT_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_DUPERR; + + /* Link child stderr to the write end of the pipe */ + retval = dup2(stderr_pipe[1], STDERR_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_REDIRECT; + + /* Link stdin to /dev/null */ + fd = open("/dev/null", O_RDONLY); /* NOT cloexec, obviously. */ + if (fd != -1) + dup2(fd, STDIN_FILENO); + else + goto error; + + child_state = CHILD_STATE_CLOSEFD; + + close(stderr_pipe[0]); + close(stderr_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(fd); + + /* Close all other fds, including the read end of the pipe */ + /* XXX: We should now be doing enough FD_CLOEXEC setting to make + * this needless. */ + for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) { + close(fd); + } + + child_state = CHILD_STATE_EXEC; + + /* Call the requested program. We need the cast because + execvp doesn't define argv as const, even though it + does not modify the arguments */ + execvp(filename, (char *const *) argv); + + /* If we got here, the exec or open(/dev/null) failed */ + + child_state = CHILD_STATE_FAILEXEC; + + error: + /* XXX: are we leaking fds from the pipe? */ + + format_helper_exit_status(child_state, errno, hex_errno); + + /* Write the error message. GCC requires that we check the return + value, but there is nothing we can do if it fails */ + nbytes = write(STDOUT_FILENO, error_message, error_message_length); + nbytes = write(STDOUT_FILENO, hex_errno, sizeof(hex_errno)); + + _exit(255); + return -1; /* Never reached, but avoids compiler warning */ + } + + /* In parent */ + + if (-1 == pid) { + log_warn(LD_GENERAL, "Failed to fork child process: %s", strerror(errno)); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + return -1; + } + + /* Return read end of the pipes to caller, and close write end */ + *stdout_read = stdout_pipe[0]; + retval = close(stdout_pipe[1]); + + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to close write end of stdout pipe in parent process: %s", + strerror(errno)); + /* Do not return -1, because the child is running, so the parent + needs to know about the pid in order to reap it later */ + } + + *stderr_read = stderr_pipe[0]; + retval = close(stderr_pipe[1]); + + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to close write end of stderr pipe in parent process: %s", + strerror(errno)); + /* Do not return -1, because the child is running, so the parent + needs to know about the pid in order to reap it later */ + } + + return pid; +#endif +} + +/** Read from stream, and send lines to log at the specified log level. + * Returns 1 if stream is closed normally, -1 if there is a error reading, and + * 0 otherwise. Handles lines from tor-fw-helper and + * tor_spawn_background() specially. + */ +static int +log_from_pipe(FILE *stream, int severity, const char *executable, + int *child_status) +{ + char buf[256]; + + for (;;) { + char *retval; + retval = fgets(buf, sizeof(buf), stream); + + if (NULL == retval) { + if (feof(stream)) { + /* Program has closed stream (probably it exited) */ + /* TODO: check error */ + fclose(stream); + return 1; + } else { + if (EAGAIN == errno) { + /* Nothing more to read, try again next time */ + return 0; + } else { + /* There was a problem, abandon this child process */ + fclose(stream); + return -1; + } + } + } else { + /* We have some data, log it and keep asking for more */ + size_t len; + + len = strlen(buf); + if (buf[len - 1] == '\n') { + /* Remove the trailing newline */ + buf[len - 1] = '\0'; + } else { + /* No newline; check whether we overflowed the buffer */ + if (!feof(stream)) + log_warn(LD_GENERAL, + "Line from port forwarding helper was truncated: %s", buf); + /* TODO: What to do with this error? */ + } + + /* Check if buf starts with SPAWN_ERROR_MESSAGE */ + if (strcmpstart(buf, SPAWN_ERROR_MESSAGE) == 0) { + /* Parse error message */ + int retval, child_state, saved_errno; + retval = tor_sscanf(buf, SPAWN_ERROR_MESSAGE "%x/%x", + &child_state, &saved_errno); + if (retval == 2) { + log_warn(LD_GENERAL, + "Failed to start child process \"%s\" in state %d: %s", + executable, child_state, strerror(saved_errno)); + if (child_status) + *child_status = 1; + } else { + /* Failed to parse message from child process, log it as a + warning */ + log_warn(LD_GENERAL, + "Unexpected message from port forwarding helper \"%s\": %s", + executable, buf); + } + } else { + log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", buf); + } + } + } + + /* We should never get here */ + return -1; +} + +void +tor_check_port_forwarding(const char *filename, int dir_port, int or_port, + time_t now) +{ +#ifdef MS_WINDOWS + (void) filename; (void) dir_port; (void) or_port; (void) now; + (void) tor_spawn_background; + (void) log_from_pipe; + log_warn(LD_GENERAL, "Sorry, port forwarding is not yet supported " + "on windows."); +#else +/* When fw-helper succeeds, how long do we wait until running it again */ +#define TIME_TO_EXEC_FWHELPER_SUCCESS 300 +/* When fw-helper fails, how long do we wait until running it again */ +#define TIME_TO_EXEC_FWHELPER_FAIL 60 + + static int child_pid = -1; + static FILE *stdout_read = NULL; + static FILE *stderr_read = NULL; + static time_t time_to_run_helper = 0; + int stdout_status, stderr_status, retval; + const char *argv[10]; + char s_dirport[6], s_orport[6]; + + tor_assert(filename); + + /* Set up command line for tor-fw-helper */ + snprintf(s_dirport, sizeof s_dirport, "%d", dir_port); + snprintf(s_orport, sizeof s_orport, "%d", or_port); + + /* TODO: Allow different internal and external ports */ + argv[0] = filename; + argv[1] = "--internal-or-port"; + argv[2] = s_orport; + argv[3] = "--external-or-port"; + argv[4] = s_orport; + argv[5] = "--internal-dir-port"; + argv[6] = s_dirport; + argv[7] = "--external-dir-port"; + argv[8] = s_dirport; + argv[9] = NULL; + + /* Start the child, if it is not already running */ + if (-1 == child_pid && + time_to_run_helper < now) { + int fd_out=-1, fd_err=-1; + + /* Assume tor-fw-helper will succeed, start it later*/ + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_SUCCESS; + + child_pid = tor_spawn_background(filename, &fd_out, &fd_err, argv); + if (child_pid < 0) { + log_warn(LD_GENERAL, "Failed to start port forwarding helper %s", + filename); + child_pid = -1; + return; + } + /* Set stdout/stderr pipes to be non-blocking */ + fcntl(fd_out, F_SETFL, O_NONBLOCK); + fcntl(fd_err, F_SETFL, O_NONBLOCK); + /* Open the buffered IO streams */ + stdout_read = fdopen(fd_out, "r"); + stderr_read = fdopen(fd_err, "r"); + + log_info(LD_GENERAL, + "Started port forwarding helper (%s) with pid %d", filename, child_pid); + } + + /* If child is running, read from its stdout and stderr) */ + if (child_pid > 0) { + /* Read from stdout/stderr and log result */ + retval = 0; + stdout_status = log_from_pipe(stdout_read, LOG_INFO, filename, &retval); + stderr_status = log_from_pipe(stderr_read, LOG_WARN, filename, &retval); + if (retval) { + /* There was a problem in the child process */ + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL; + } + + /* Combine the two statuses in order of severity */ + if (-1 == stdout_status || -1 == stderr_status) + /* There was a failure */ + retval = -1; + else if (1 == stdout_status || 1 == stderr_status) + /* stdout or stderr was closed */ + retval = 1; + else + /* Both are fine */ + retval = 0; + + /* If either pipe indicates a failure, act on it */ + if (0 != retval) { + if (1 == retval) { + log_info(LD_GENERAL, "Port forwarding helper terminated"); + } else { + log_warn(LD_GENERAL, "Failed to read from port forwarding helper"); + } + + /* TODO: The child might not actually be finished (maybe it failed or + closed stdout/stderr), so maybe we shouldn't start another? */ + child_pid = -1; + } + } +#endif +} + diff --git a/src/common/util.h b/src/common/util.h index 3736237b32..97fd4f7aa2 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -248,14 +248,22 @@ void update_approx_time(time_t now); #endif /* Fuzzy time. */ -void ftime_set_maximum_sloppiness(int seconds); -void ftime_set_estimated_skew(int seconds); -/* typedef struct ftime_t { time_t earliest; time_t latest; } ftime_t; */ -/* void ftime_get_window(time_t now, ftime_t *ft_out); */ -int ftime_maybe_after(time_t now, time_t when); -int ftime_maybe_before(time_t now, time_t when); -int ftime_definitely_after(time_t now, time_t when); -int ftime_definitely_before(time_t now, time_t when); + +/** Return true iff <a>a</b> is definitely after <b>b</b>, even if there + * could be up to <b>allow_seconds</b> of skew in one of them. */ +static INLINE int +time_definitely_after(time_t a, time_t b, int allow_skew) +{ + return a-allow_skew > b; +} + +/** Return true iff <a>a</b> is definitely before <b>b</b>, even if there + * could be up to <b>allow_seconds</b> of skew in one of them. */ +static INLINE int +time_definitely_before(time_t a, time_t b, int allow_skew) +{ + return a+allow_skew < b; +} /* Rate-limiter */ @@ -340,10 +348,27 @@ void start_daemon(void); void finish_daemon(const char *desired_cwd); void write_pidfile(char *filename); +/* Port forwarding */ +void tor_check_port_forwarding(const char *filename, + int dir_port, int or_port, time_t now); + #ifdef MS_WINDOWS HANDLE load_windows_system_library(const TCHAR *library_name); #endif +#ifdef UTIL_PRIVATE +/* Prototypes for private functions only used by util.c (and unit tests) */ +int tor_spawn_background(const char *const filename, int *stdout_read, + int *stderr_read, const char **argv); +void 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 + leading minus) and newline (no null) */ +#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \ + 1 + sizeof(int) * 2 + 1) +#endif + const char *libor_get_digests(void); #endif diff --git a/src/or/Makefile.am b/src/or/Makefile.am index a9ac3cdee1..b6637ba631 100644 --- a/src/or/Makefile.am +++ b/src/or/Makefile.am @@ -15,16 +15,44 @@ else evdns_source=eventdns.c endif -libtor_a_SOURCES = buffers.c circuitbuild.c circuitlist.c \ - circuituse.c command.c config.c \ - connection.c connection_edge.c connection_or.c control.c \ - cpuworker.c directory.c dirserv.c dirvote.c \ - dns.c dnsserv.c geoip.c hibernate.c main.c $(tor_platform_source) \ - microdesc.c \ - networkstatus.c onion.c policies.c \ - reasons.c relay.c rendcommon.c rendclient.c rendmid.c \ - rendservice.c rephist.c router.c routerlist.c routerparse.c \ - $(evdns_source) config_codedigest.c +libtor_a_SOURCES = \ + buffers.c \ + circuitbuild.c \ + circuitlist.c \ + circuituse.c \ + command.c \ + config.c \ + connection.c \ + connection_edge.c \ + connection_or.c \ + control.c \ + cpuworker.c \ + directory.c \ + dirserv.c \ + dirvote.c \ + dns.c \ + dnsserv.c \ + geoip.c \ + hibernate.c \ + main.c \ + microdesc.c \ + networkstatus.c \ + nodelist.c \ + onion.c \ + policies.c \ + reasons.c \ + relay.c \ + rendclient.c \ + rendcommon.c \ + rendmid.c \ + rendservice.c \ + rephist.c \ + router.c \ + routerlist.c \ + routerparse.c \ + $(evdns_source) \ + $(tor_platform_source) \ + config_codedigest.c #libtor_a_LIBADD = ../common/libor.a ../common/libor-crypto.a \ # ../common/libor-event.a @@ -40,18 +68,58 @@ AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ # 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. +if USE_BUFFEREVENTS +levent_openssl_lib = -levent_openssl +else +levent_openssl_lib = +endif + tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@ tor_LDADD = ./libtor.a ../common/libor.a ../common/libor-crypto.a \ ../common/libor-event.a \ - @TOR_ZLIB_LIBS@ -lm @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ - -noinst_HEADERS = buffers.h circuitbuild.h circuitlist.h circuituse.h \ - command.h config.h connection_edge.h connection.h connection_or.h \ - control.h cpuworker.h directory.h dirserv.h dirvote.h dns.h \ - dnsserv.h geoip.h hibernate.h main.h microdesc.h networkstatus.h \ - ntmain.h onion.h policies.h reasons.h relay.h rendclient.h \ - rendcommon.h rendmid.h rendservice.h rephist.h router.h routerlist.h \ - routerparse.h or.h eventdns.h eventdns_tor.h micro-revision.i + @TOR_ZLIB_LIBS@ -lm @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ $(levent_openssl_lib) + +noinst_HEADERS = \ + buffers.h \ + circuitbuild.h \ + circuitlist.h \ + circuituse.h \ + command.h \ + config.h \ + connection.h \ + connection_edge.h \ + connection_or.h \ + control.h \ + cpuworker.h \ + directory.h \ + dirserv.h \ + dirvote.h \ + dns.h \ + dnsserv.h \ + eventdns.h \ + eventdns_tor.h \ + geoip.h \ + hibernate.h \ + main.h \ + microdesc.h \ + networkstatus.h \ + nodelist.h \ + ntmain.h \ + onion.h \ + or.h \ + policies.h \ + reasons.h \ + relay.h \ + rendclient.h \ + rendcommon.h \ + rendmid.h \ + rendservice.h \ + rephist.h \ + router.h \ + routerlist.h \ + routerparse.h \ + micro-revision.i config_codedigest.o: or_sha1.i diff --git a/src/or/buffers.c b/src/or/buffers.c index de0c219e85..2a5b4e15ce 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -23,9 +23,6 @@ #ifdef HAVE_UNISTD_H #include <unistd.h> #endif -#ifdef HAVE_SYS_UIO_H -#include <sys/uio.h> -#endif //#define PARANOIA @@ -56,6 +53,13 @@ * forever. */ +static int parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out); +static int parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out); + /* Chunk manipulation functions */ /** A single chunk on a buffer or in a freelist. */ @@ -64,8 +68,8 @@ typedef struct chunk_t { size_t datalen; /**< The number of bytes stored in this chunk */ size_t memlen; /**< The number of usable bytes of storage in <b>mem</b>. */ char *data; /**< A pointer to the first byte of data stored in <b>mem</b>. */ - char mem[1]; /**< The actual memory used for storage in this chunk. May be - * more than one byte long. */ + char mem[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in + * this chunk. */ } chunk_t; #define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0]) @@ -547,6 +551,7 @@ buf_free(buf_t *buf) { if (!buf) return; + buf_clear(buf); buf->magic = 0xdeadbeef; tor_free(buf); @@ -578,10 +583,6 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) return chunk; } -/** If we're using readv and writev, how many chunks are we willing to - * read/write at a time? */ -#define N_IOV 3 - /** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into * <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set * *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking, @@ -591,25 +592,9 @@ read_to_chunk(buf_t *buf, chunk_t *chunk, int fd, size_t at_most, int *reached_eof, int *socket_error) { ssize_t read_result; -#if 0 && defined(HAVE_READV) && !defined(WIN32) - struct iovec iov[N_IOV]; - int i; - size_t remaining = at_most; - for (i=0; chunk && i < N_IOV && remaining; ++i) { - iov[i].iov_base = CHUNK_WRITE_PTR(chunk); - if (remaining > CHUNK_REMAINING_CAPACITY(chunk)) - iov[i].iov_len = CHUNK_REMAINING_CAPACITY(chunk); - else - iov[i].iov_len = remaining; - remaining -= iov[i].iov_len; - chunk = chunk->next; - } - read_result = readv(fd, iov, i); -#else if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) at_most = CHUNK_REMAINING_CAPACITY(chunk); read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); -#endif if (read_result < 0) { int e = tor_socket_errno(fd); @@ -628,14 +613,6 @@ read_to_chunk(buf_t *buf, chunk_t *chunk, int fd, size_t at_most, return 0; } else { /* actually got bytes. */ buf->datalen += read_result; -#if 0 && defined(HAVE_READV) && !defined(WIN32) - while ((size_t)read_result > CHUNK_REMAINING_CAPACITY(chunk)) { - chunk->datalen += CHUNK_REMAINING_CAPACITY(chunk); - read_result -= CHUNK_REMAINING_CAPACITY(chunk); - chunk = chunk->next; - tor_assert(chunk); - } -#endif chunk->datalen += read_result; log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, (int)buf->datalen); @@ -666,14 +643,10 @@ read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, * (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on * error; else return the number of bytes read. */ -/* XXXX021 indicate "read blocked" somehow? */ int read_to_buf(int s, size_t at_most, buf_t *buf, int *reached_eof, int *socket_error) { - /* XXXX021 It's stupid to overload the return values for these functions: - * "error status" and "number of bytes read" are not mutually exclusive. - */ int r = 0; size_t total_read = 0; @@ -771,25 +744,10 @@ flush_chunk(int s, buf_t *buf, chunk_t *chunk, size_t sz, size_t *buf_flushlen) { ssize_t write_result; -#if 0 && defined(HAVE_WRITEV) && !defined(WIN32) - struct iovec iov[N_IOV]; - int i; - size_t remaining = sz; - for (i=0; chunk && i < N_IOV && remaining; ++i) { - iov[i].iov_base = chunk->data; - if (remaining > chunk->datalen) - iov[i].iov_len = chunk->datalen; - else - iov[i].iov_len = remaining; - remaining -= iov[i].iov_len; - chunk = chunk->next; - } - write_result = writev(s, iov, i); -#else + if (sz > chunk->datalen) sz = chunk->datalen; write_result = tor_socket_send(s, chunk->data, sz, 0); -#endif if (write_result < 0) { int e = tor_socket_errno(s); @@ -856,9 +814,6 @@ flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, int flush_buf(int s, buf_t *buf, size_t sz, size_t *buf_flushlen) { - /* XXXX021 It's stupid to overload the return values for these functions: - * "error status" and "number of bytes flushed" are not mutually exclusive. - */ int r; size_t flushed = 0; tor_assert(buf_flushlen); @@ -1056,6 +1011,103 @@ fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) return 1; } +#ifdef USE_BUFFEREVENTS +/** Try to read <b>n</b> bytes from <b>buf</b> at <b>pos</b> (which may be + * NULL for the start of the buffer), copying the data only if necessary. Set + * *<b>data</b> to a pointer to the desired bytes. Set <b>free_out</b> to 1 + * if we needed to malloc *<b>data</b> because the original bytes were + * noncontiguous; 0 otherwise. Return the number of bytes actually available + * at <b>data</b>. + */ +static ssize_t +inspect_evbuffer(struct evbuffer *buf, char **data, size_t n, int *free_out, + struct evbuffer_ptr *pos) +{ + int n_vecs, i; + + if (evbuffer_get_length(buf) < n) + n = evbuffer_get_length(buf); + if (n == 0) + return 0; + n_vecs = evbuffer_peek(buf, n, pos, NULL, 0); + tor_assert(n_vecs > 0); + if (n_vecs == 1) { + struct evbuffer_iovec v; + i = evbuffer_peek(buf, n, pos, &v, 1); + tor_assert(i == 1); + *data = v.iov_base; + *free_out = 0; + return v.iov_len; + } else { + struct evbuffer_iovec *vecs = + tor_malloc(sizeof(struct evbuffer_iovec)*n_vecs); + size_t copied = 0; + i = evbuffer_peek(buf, n, NULL, vecs, n_vecs); + tor_assert(i == n_vecs); + *data = tor_malloc(n); + for (i=0; i < n_vecs; ++i) { + size_t copy = n - copied; + if (copy > vecs[i].iov_len) + copy = vecs[i].iov_len; + tor_assert(copied+copy <= n); + memcpy(data+copied, vecs[i].iov_base, copy); + copied += copy; + } + *free_out = 1; + return copied; + } +} + +/** As fetch_var_cell_from_buf, buf works on an evbuffer. */ +int +fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out, + int linkproto) +{ + char *hdr = NULL; + int free_hdr = 0; + size_t n; + size_t buf_len; + uint8_t command; + uint16_t cell_length; + var_cell_t *cell; + int result = 0; + if (linkproto == 1) + return 0; + + *out = NULL; + buf_len = evbuffer_get_length(buf); + if (buf_len < VAR_CELL_HEADER_SIZE) + return 0; + + n = inspect_evbuffer(buf, &hdr, VAR_CELL_HEADER_SIZE, &free_hdr, NULL); + tor_assert(n >= VAR_CELL_HEADER_SIZE); + + command = get_uint8(hdr+2); + if (!(CELL_COMMAND_IS_VAR_LENGTH(command))) { + goto done; + } + + cell_length = ntohs(get_uint16(hdr+3)); + if (buf_len < (size_t)(VAR_CELL_HEADER_SIZE+cell_length)) { + result = 1; /* Not all here yet. */ + goto done; + } + + cell = var_cell_new(cell_length); + cell->command = command; + cell->circ_id = ntohs(get_uint16(hdr)); + evbuffer_drain(buf, VAR_CELL_HEADER_SIZE); + evbuffer_remove(buf, cell->payload, cell_length); + *out = cell; + result = 1; + + done: + if (free_hdr && hdr) + tor_free(hdr); + return result; +} +#endif + /** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. * Return the number of bytes actually copied. @@ -1063,8 +1115,7 @@ fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) { - /* XXXX we can do way better here, but this doesn't turn up in any - * profiles. */ + /* We can do way better here, but this doesn't turn up in any profiles. */ char b[4096]; size_t cp, len; len = *buf_flushlen; @@ -1299,6 +1350,94 @@ fetch_from_buf_http(buf_t *buf, return 1; } +#ifdef USE_BUFFEREVENTS +/** As fetch_from_buf_http, buf works on an evbuffer. */ +int +fetch_from_evbuffer_http(struct evbuffer *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete) +{ + struct evbuffer_ptr crlf, content_length; + size_t headerlen, bodylen, contentlen; + + /* Find the first \r\n\r\n in the buffer */ + crlf = evbuffer_search(buf, "\r\n\r\n", 4, NULL); + if (crlf.pos < 0) { + /* We didn't find one. */ + if (evbuffer_get_length(buf) > max_headerlen) + return -1; /* Headers too long. */ + return 0; /* Headers not here yet. */ + } else if (crlf.pos > (int)max_headerlen) { + return -1; /* Headers too long. */ + } + + headerlen = crlf.pos + 4; /* Skip over the \r\n\r\n */ + bodylen = evbuffer_get_length(buf) - headerlen; + if (bodylen > max_bodylen) + return -1; /* body too long */ + + /* Look for the first occurrence of CONTENT_LENGTH insize buf before the + * crlfcrlf */ + content_length = evbuffer_search_range(buf, CONTENT_LENGTH, + strlen(CONTENT_LENGTH), NULL, &crlf); + + if (content_length.pos >= 0) { + /* We found a content_length: parse it and figure out if the body is here + * yet. */ + struct evbuffer_ptr eol; + char *data = NULL; + int free_data = 0; + int n, i; + n = evbuffer_ptr_set(buf, &content_length, strlen(CONTENT_LENGTH), + EVBUFFER_PTR_ADD); + tor_assert(n == 0); + eol = evbuffer_search_eol(buf, &content_length, NULL, EVBUFFER_EOL_CRLF); + tor_assert(eol.pos > content_length.pos); + tor_assert(eol.pos <= crlf.pos); + inspect_evbuffer(buf, &data, eol.pos - content_length.pos, &free_data, + &content_length); + + i = atoi(data); + if (free_data) + tor_free(data); + if (i < 0) { + log_warn(LD_PROTOCOL, "Content-Length is less than zero; it looks like " + "someone is trying to crash us."); + return -1; + } + contentlen = i; + /* if content-length is malformed, then our body length is 0. fine. */ + log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); + if (bodylen < contentlen) { + if (!force_complete) { + log_debug(LD_HTTP,"body not all here yet."); + return 0; /* not all there yet */ + } + } + if (bodylen > contentlen) { + bodylen = contentlen; + log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); + } + } + + if (headers_out) { + *headers_out = tor_malloc(headerlen+1); + evbuffer_remove(buf, *headers_out, headerlen); + (*headers_out)[headerlen] = '\0'; + } + if (body_out) { + tor_assert(headers_out); + tor_assert(body_used); + *body_used = bodylen; + *body_out = tor_malloc(bodylen+1); + evbuffer_remove(buf, *body_out, bodylen); + (*body_out)[bodylen] = '\0'; + } + return 1; +} +#endif + /** * Wait this many seconds before warning the user about using SOCKS unsafely * again (requires that WarnUnsafeSocks is turned on). */ @@ -1369,6 +1508,128 @@ int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, int log_sockstype, int safe_socks) { + int res; + ssize_t n_drain; + size_t want_length = 128; + + if (buf->datalen < 2) /* version and another byte */ + return 0; + + do { + n_drain = 0; + buf_pullup(buf, want_length, 0); + tor_assert(buf->head && buf->head->datalen >= 2); + want_length = 0; + + res = parse_socks(buf->head->data, buf->head->datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + buf_clear(buf); + else if (n_drain > 0) + buf_remove_from_front(buf, n_drain); + + } while (res == 0 && buf->head && + buf->datalen > buf->head->datalen && + want_length < buf->head->datalen); + + return res; +} + +#ifdef USE_BUFFEREVENTS +/* As fetch_from_buf_socks(), but targets an evbuffer instead. */ +int +fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req, + int log_sockstype, int safe_socks) +{ + char *data; + ssize_t n_drain; + size_t datalen, buflen, want_length; + int res; + + buflen = evbuffer_get_length(buf); + if (buflen < 2) + return 0; + + { + /* See if we can find the socks request in the first chunk of the buffer. + */ + struct evbuffer_iovec v; + int i; + want_length = evbuffer_get_contiguous_space(buf); + n_drain = 0; + i = evbuffer_peek(buf, want_length, NULL, &v, 1); + tor_assert(i == 1); + data = v.iov_base; + datalen = v.iov_len; + + res = parse_socks(data, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + evbuffer_drain(buf, evbuffer_get_length(buf)); + else if (n_drain > 0) + evbuffer_drain(buf, n_drain); + + if (res) + return res; + } + + /* Okay, the first chunk of the buffer didn't have a complete socks request. + * That means that either we don't have a whole socks request at all, or + * it's gotten split up. We're going to try passing parse_socks() bigger + * and bigger chunks until either it says "Okay, I got it", or it says it + * will need more data than we currently have. */ + + /* Loop while we have more data that we haven't given parse_socks() yet. */ + while (evbuffer_get_length(buf) > datalen) { + int free_data = 0; + n_drain = 0; + data = NULL; + datalen = inspect_evbuffer(buf, &data, want_length, &free_data, NULL); + + res = parse_socks(data, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (free_data) + tor_free(data); + + if (n_drain < 0) + evbuffer_drain(buf, evbuffer_get_length(buf)); + else if (n_drain > 0) + evbuffer_drain(buf, n_drain); + + if (res) /* If res is nonzero, parse_socks() made up its mind. */ + return res; + + /* If parse_socks says that we want less data than we actually tried to + give it, we've got some kind of weird situation; just exit the loop for + now. + */ + if (want_length <= datalen) + break; + /* Otherwise, it wants more data than we gave it. If we can provide more + * data than we gave it, we'll try to do so in the next iteration of the + * loop. If we can't, the while loop will exit. It's okay if it asked for + * more than we have total; maybe it doesn't really need so much. */ + } + + return res; +} +#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 + * <b>drain_out</b> to the amount of data that should be removed (or -1 if the + * buffer should be cleared). Instead of pulling more data into the first + * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes + * we'd like to see in the input buffer, if they're available. */ +static int +parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out) +{ unsigned int len; char tmpbuf[TOR_ADDR_BUF_LEN+1]; tor_addr_t destaddr; @@ -1378,25 +1639,20 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, char *next, *startaddr; struct in_addr in; - if (buf->datalen < 2) /* version and another byte */ - return 0; - - buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, 0); - tor_assert(buf->head && buf->head->datalen >= 2); - - socksver = *buf->head->data; + socksver = *data; switch (socksver) { /* which version of socks? */ case 5: /* socks5 */ if (req->socks_version != 5) { /* we need to negotiate a method */ - unsigned char nummethods = (unsigned char)*(buf->head->data+1); + unsigned char nummethods = (unsigned char)*(data+1); tor_assert(!req->socks_version); - if (buf->datalen < 2u+nummethods) + if (datalen < 2u+nummethods) { + *want_length_out = 2u+nummethods; return 0; - buf_pullup(buf, 2u+nummethods, 0); - if (!nummethods || !memchr(buf->head->data+2, 0, nummethods)) { + } + if (!nummethods || !memchr(data+2, 0, nummethods)) { log_warn(LD_APP, "socks5: offered methods don't include 'no auth'. " "Rejecting."); @@ -1407,7 +1663,7 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, } /* remove packet from buf. also remove any other extraneous * bytes, to support broken socks clients. */ - buf_clear(buf); + *drain_out = -1; req->replylen = 2; /* 2 bytes of response */ req->reply[0] = 5; /* socks5 reply */ @@ -1418,10 +1674,11 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, } /* we know the method; read in the request */ log_debug(LD_APP,"socks5: checking request"); - if (buf->datalen < 8) /* basic info plus >=2 for addr plus 2 for port */ + if (datalen < 8) {/* basic info plus >=2 for addr plus 2 for port */ + *want_length_out = 8; return 0; /* not yet */ - tor_assert(buf->head->datalen >= 8); - req->command = (unsigned char) *(buf->head->data+1); + } + req->command = (unsigned char) *(data+1); if (req->command != SOCKS_COMMAND_CONNECT && req->command != SOCKS_COMMAND_RESOLVE && req->command != SOCKS_COMMAND_RESOLVE_PTR) { @@ -1430,19 +1687,21 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, req->command); return -1; } - switch (*(buf->head->data+3)) { /* address type */ + switch (*(data+3)) { /* address type */ case 1: /* IPv4 address */ case 4: /* IPv6 address */ { - const int is_v6 = *(buf->head->data+3) == 4; + const int is_v6 = *(data+3) == 4; const unsigned addrlen = is_v6 ? 16 : 4; log_debug(LD_APP,"socks5: ipv4 address type"); - if (buf->datalen < 6+addrlen) /* ip/port there? */ + if (datalen < 6+addrlen) {/* ip/port there? */ + *want_length_out = 6+addrlen; return 0; /* not yet */ + } if (is_v6) - tor_addr_from_ipv6_bytes(&destaddr, buf->head->data+4); + tor_addr_from_ipv6_bytes(&destaddr, data+4); else - tor_addr_from_ipv4n(&destaddr, get_uint32(buf->head->data+4)); + tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); @@ -1454,8 +1713,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return -1; } strlcpy(req->address,tmpbuf,sizeof(req->address)); - req->port = ntohs(get_uint16(buf->head->data+4+addrlen)); - buf_remove_from_front(buf, 6+addrlen); + req->port = ntohs(get_uint16(data+4+addrlen)); + *drain_out = 6+addrlen; if (req->command != SOCKS_COMMAND_RESOLVE_PTR && !addressmap_have_mapping(req->address,0)) { log_unsafe_socks_warning(5, req->address, req->port, safe_socks); @@ -1471,21 +1730,21 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, "hostname type. Rejecting."); return -1; } - len = (unsigned char)*(buf->head->data+4); - if (buf->datalen < 7+len) /* addr/port there? */ + len = (unsigned char)*(data+4); + if (datalen < 7+len) { /* addr/port there? */ + *want_length_out = 7+len; return 0; /* not yet */ - buf_pullup(buf, 7+len, 0); - tor_assert(buf->head->datalen >= 7+len); + } if (len+1 > MAX_SOCKS_ADDR_LEN) { log_warn(LD_APP, "socks5 hostname is %d bytes, which doesn't fit in " "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); return -1; } - memcpy(req->address,buf->head->data+5,len); + memcpy(req->address,data+5,len); req->address[len] = 0; - req->port = ntohs(get_uint16(buf->head->data+5+len)); - buf_remove_from_front(buf, 5+len+2); + req->port = ntohs(get_uint16(data+5+len)); + *drain_out = 5+len+2; if (!tor_strisprint(req->address) || strchr(req->address,'\"')) { log_warn(LD_PROTOCOL, "Your application (using socks5 to port %d) gave Tor " @@ -1501,7 +1760,7 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return 1; default: /* unsupported */ log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", - (int) *(buf->head->data+3)); + (int) *(data+3)); return -1; } tor_assert(0); @@ -1510,10 +1769,12 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, /* http://archive.socks.permeo.com/protocol/socks4a.protocol */ req->socks_version = 4; - if (buf->datalen < SOCKS4_NETWORK_LEN) /* basic info available? */ + if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ + *want_length_out = SOCKS4_NETWORK_LEN; return 0; /* not yet */ - buf_pullup(buf, 1280, 0); - req->command = (unsigned char) *(buf->head->data+1); + } + // buf_pullup(buf, 1280, 0); + req->command = (unsigned char) *(data+1); if (req->command != SOCKS_COMMAND_CONNECT && req->command != SOCKS_COMMAND_RESOLVE) { /* not a connect or resolve? we don't support it. (No resolve_ptr with @@ -1523,8 +1784,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return -1; } - req->port = ntohs(get_uint16(buf->head->data+2)); - destip = ntohl(get_uint32(buf->head->data+4)); + req->port = ntohs(get_uint16(data+2)); + destip = ntohl(get_uint32(data+4)); if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); return -1; @@ -1544,17 +1805,18 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, socks4_prot = socks4; } - next = memchr(buf->head->data+SOCKS4_NETWORK_LEN, 0, - buf->head->datalen-SOCKS4_NETWORK_LEN); + next = memchr(data+SOCKS4_NETWORK_LEN, 0, + datalen-SOCKS4_NETWORK_LEN); if (!next) { - if (buf->head->datalen >= 1024) { + if (datalen >= 1024) { log_debug(LD_APP, "Socks4 user name too long; rejecting."); return -1; } log_debug(LD_APP,"socks4: Username not here yet."); + *want_length_out = datalen+1024; /* ???? */ return 0; } - tor_assert(next < CHUNK_WRITE_PTR(buf->head)); + tor_assert(next < data+datalen); startaddr = NULL; if (socks4_prot != socks4a && @@ -1565,18 +1827,20 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return -1; } if (socks4_prot == socks4a) { - if (next+1 == CHUNK_WRITE_PTR(buf->head)) { + if (next+1 == data+datalen) { log_debug(LD_APP,"socks4: No part of destaddr here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ return 0; } startaddr = next+1; - next = memchr(startaddr, 0, CHUNK_WRITE_PTR(buf->head)-startaddr); + next = memchr(startaddr, 0, data + datalen - startaddr); if (!next) { - if (buf->head->datalen >= 1024) { + if (datalen >= 1024) { log_debug(LD_APP,"socks4: Destaddr too long."); return -1; } log_debug(LD_APP,"socks4: Destaddr not all here yet."); + *want_length_out = datalen + 1024; return 0; } if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { @@ -1602,7 +1866,7 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return -1; } /* next points to the final \0 on inbuf */ - buf_remove_from_front(buf, next - buf->head->data + 1); + *drain_out = next - data + 1; return 1; case 'G': /* get */ @@ -1640,9 +1904,10 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, default: /* version is not socks4 or socks5 */ log_warn(LD_APP, "Socks version %d not recognized. (Tor is not an http proxy.)", - *(buf->head->data)); + *(data)); { - char *tmp = tor_strndup(buf->head->data, 8); /*XXXX what if longer?*/ + /* Tell the controller the first 8 bytes. */ + char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); control_event_client_status(LOG_WARN, "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", escaped(tmp)); @@ -1664,21 +1929,67 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) { - unsigned char *data; - size_t addrlen; - + ssize_t drain = 0; + int r; if (buf->datalen < 2) return 0; - buf_pullup(buf, 128, 0); + buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, 0); tor_assert(buf->head && buf->head->datalen >= 2); - data = (unsigned char *) buf->head->data; + r = parse_socks_client((uint8_t*)buf->head->data, buf->head->datalen, + state, reason, &drain); + if (drain > 0) + buf_remove_from_front(buf, drain); + else if (drain < 0) + buf_clear(buf); + + return r; +} + +#ifdef USE_BUFFEREVENTS +/** As fetch_from_buf_socks_client, buf works on an evbuffer */ +int +fetch_from_evbuffer_socks_client(struct evbuffer *buf, int state, + char **reason) +{ + ssize_t drain = 0; + uint8_t *data; + size_t datalen; + int r; + + /* Linearize the SOCKS response in the buffer, up to 128 bytes. + * (parse_socks_client shouldn't need to see anything beyond that.) */ + datalen = evbuffer_get_length(buf); + if (datalen > MAX_SOCKS_MESSAGE_LEN) + datalen = MAX_SOCKS_MESSAGE_LEN; + data = evbuffer_pullup(buf, datalen); + + r = parse_socks_client(data, datalen, state, reason, &drain); + if (drain > 0) + evbuffer_drain(buf, drain); + else if (drain < 0) + evbuffer_drain(buf, evbuffer_get_length(buf)); + + return r; +} +#endif + +/** Implementation logic for fetch_from_*_socks_client. */ +static int +parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out) +{ + unsigned int addrlen; + *drain_out = 0; + if (datalen < 2) + return 0; switch (state) { case PROXY_SOCKS4_WANT_CONNECT_OK: /* Wait for the complete response */ - if (buf->head->datalen < 8) + if (datalen < 8) return 0; if (data[1] != 0x5a) { @@ -1687,7 +1998,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } /* Success */ - buf_remove_from_front(buf, 8); + *drain_out = 8; return 1; case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: @@ -1699,7 +2010,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); - buf_clear(buf); + *drain_out = -1; return 1; case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: @@ -1709,11 +2020,11 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) case 0x00: log_info(LD_NET, "SOCKS 5 client: we have auth details but server " "doesn't require authentication."); - buf_clear(buf); + *drain_out = -1; return 1; case 0x02: log_info(LD_NET, "SOCKS 5 client: need authentication."); - buf_clear(buf); + *drain_out = -1; return 2; /* fall through */ } @@ -1730,7 +2041,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } log_info(LD_NET, "SOCKS 5 client: authentication successful."); - buf_clear(buf); + *drain_out = -1; return 1; case PROXY_SOCKS5_WANT_CONNECT_OK: @@ -1739,7 +2050,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) * the data used */ /* wait for address type field to arrive */ - if (buf->datalen < 4) + if (datalen < 4) return 0; switch (data[3]) { @@ -1750,7 +2061,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) addrlen = 16; break; case 0x03: /* fqdn (can this happen here?) */ - if (buf->datalen < 5) + if (datalen < 5) return 0; addrlen = 1 + data[4]; break; @@ -1760,7 +2071,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } /* wait for address and port */ - if (buf->datalen < 6 + addrlen) + if (datalen < 6 + addrlen) return 0; if (data[1] != 0x00) { @@ -1768,7 +2079,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) return -1; } - buf_remove_from_front(buf, 6 + addrlen); + *drain_out = 6 + addrlen; return 1; } @@ -1794,6 +2105,27 @@ peek_buf_has_control0_command(buf_t *buf) return 0; } +#ifdef USE_BUFFEREVENTS +int +peek_evbuffer_has_control0_command(struct evbuffer *buf) +{ + int result = 0; + if (evbuffer_get_length(buf) >= 4) { + int free_out = 0; + char *data = NULL; + size_t n = inspect_evbuffer(buf, &data, 4, &free_out, NULL); + uint16_t cmd; + tor_assert(n >= 4); + cmd = ntohs(get_uint16(data+2)); + if (cmd <= 0x14) + result = 1; + if (free_out) + tor_free(data); + } + return result; +} +#endif + /** Return the index within <b>buf</b> at which <b>ch</b> first appears, * or -1 if <b>ch</b> does not appear on buf. */ static off_t @@ -1811,12 +2143,12 @@ buf_find_offset_of_char(buf_t *buf, char ch) return -1; } -/** Try to read a single LF-terminated line from <b>buf</b>, and write it, - * NUL-terminated, into the *<b>data_len</b> byte buffer at <b>data_out</b>. - * Set *<b>data_len</b> to the number of bytes in the line, not counting the - * terminating NUL. Return 1 if we read a whole line, return 0 if we don't - * have a whole line yet, and return -1 if the line length exceeds - * *<b>data_len</b>. +/** Try to read a single LF-terminated line from <b>buf</b>, and write it + * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer + * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the + * line, not counting the terminating NUL. Return 1 if we read a whole line, + * return 0 if we don't have a whole line yet, and return -1 if the line + * length exceeds *<b>data_len</b>. */ int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) @@ -1890,6 +2222,61 @@ write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state, return 0; } +#ifdef USE_BUFFEREVENTS +int +write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, + const char *data, size_t data_len, + int done) +{ + char *next; + size_t old_avail, avail; + int over = 0, n; + struct evbuffer_iovec vec[1]; + do { + int need_new_chunk = 0; + { + size_t cap = data_len / 4; + if (cap < 128) + cap = 128; + /* XXXX NM this strategy is fragmentation-prone. We should really have + * two iovecs, and write first into the one, and then into the + * second if the first gets full. */ + n = evbuffer_reserve_space(buf, cap, vec, 1); + tor_assert(n == 1); + } + + next = vec[0].iov_base; + avail = old_avail = vec[0].iov_len; + + switch (tor_zlib_process(state, &next, &avail, &data, &data_len, done)) { + case TOR_ZLIB_DONE: + over = 1; + break; + case TOR_ZLIB_ERR: + return -1; + case TOR_ZLIB_OK: + if (data_len == 0) + over = 1; + break; + case TOR_ZLIB_BUF_FULL: + if (avail) { + /* Zlib says we need more room (ZLIB_BUF_FULL). Start a new chunk + * automatically, whether were going to or not. */ + need_new_chunk = 1; + } + break; + } + + /* XXXX possible infinite loop on BUF_FULL. */ + vec[0].iov_len = old_avail - avail; + evbuffer_commit_space(buf, vec, 1); + + } while (!over); + check(); + return 0; +} +#endif + /** Log an error and exit if <b>buf</b> is corrupted. */ void diff --git a/src/or/buffers.h b/src/or/buffers.h index e50b9ff6fb..ef64bcce5e 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -48,6 +48,23 @@ int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); int peek_buf_has_control0_command(buf_t *buf); +#ifdef USE_BUFFEREVENTS +int fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out, + int linkproto); +int fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req, + int log_sockstype, int safe_socks); +int fetch_from_evbuffer_socks_client(struct evbuffer *buf, int state, + char **reason); +int fetch_from_evbuffer_http(struct evbuffer *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete); +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); +#endif + void assert_buf_ok(buf_t *buf); #ifdef BUFFERS_PRIVATE diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index b1743847c8..d4505c29f9 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -23,6 +23,7 @@ #include "directory.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "onion.h" #include "policies.h" #include "relay.h" @@ -54,8 +55,8 @@ extern circuit_t *global_circuitlist; /** An entry_guard_t represents our information about a chosen long-term * first hop, known as a "helper" node in the literature. We can't just - * use a routerinfo_t, since we want to remember these even when we - * don't have a directory. */ + * use a node_t, since we want to remember these even when we + * don't have any directory info. */ typedef struct { char nickname[MAX_NICKNAME_LEN+1]; char identity[DIGEST_LEN]; @@ -94,7 +95,7 @@ static int circuit_deliver_create_cell(circuit_t *circ, static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); -static int count_acceptable_routers(smartlist_t *routers); +static int count_acceptable_nodes(smartlist_t *routers); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); static void entry_guards_changed(void); @@ -1401,10 +1402,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) hop = circ->cpath; do { - routerinfo_t *ri; - routerstatus_t *rs; char *elt; const char *id; + const node_t *node; if (!hop) break; if (!verbose && hop->state != CPATH_STATE_OPEN) @@ -1414,10 +1414,8 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) id = hop->extend_info->identity_digest; if (verbose_names) { elt = tor_malloc(MAX_VERBOSE_NICKNAME_LEN+1); - if ((ri = router_get_by_digest(id))) { - router_get_verbose_nickname(elt, ri); - } else if ((rs = router_get_consensus_status_by_id(id))) { - routerstatus_get_verbose_nickname(elt, rs); + if ((node = node_get_by_id(id))) { + node_get_verbose_nickname(node, elt); } else if (is_legal_nickname(hop->extend_info->nickname)) { elt[0] = '$'; base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); @@ -1429,9 +1427,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } } else { /* ! verbose_names */ - if ((ri = router_get_by_digest(id)) && - ri->is_named) { - elt = tor_strdup(hop->extend_info->nickname); + node = node_get_by_id(id); + if (node && node_is_named(node)) { + elt = tor_strdup(node_get_nickname(node)); } else { elt = tor_malloc(HEX_DIGEST_LEN+2); elt[0] = '$'; @@ -1500,31 +1498,28 @@ void circuit_rep_hist_note_result(origin_circuit_t *circ) { crypt_path_t *hop; - char *prev_digest = NULL; - routerinfo_t *router; + const char *prev_digest = NULL; hop = circ->cpath; if (!hop) /* circuit hasn't started building yet. */ return; if (server_mode(get_options())) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (!me) return; prev_digest = me->cache_info.identity_digest; } do { - router = router_get_by_digest(hop->extend_info->identity_digest); - if (router) { + const node_t *node = node_get_by_id(hop->extend_info->identity_digest); + if (node) { /* Why do we check this? We know the identity. -NM XXXX */ if (prev_digest) { if (hop->state == CPATH_STATE_OPEN) - rep_hist_note_extend_succeeded(prev_digest, - router->cache_info.identity_digest); + rep_hist_note_extend_succeeded(prev_digest, node->identity); else { - rep_hist_note_extend_failed(prev_digest, - router->cache_info.identity_digest); + rep_hist_note_extend_failed(prev_digest, node->identity); break; } } - prev_digest = router->cache_info.identity_digest; + prev_digest = node->identity; } else { prev_digest = NULL; } @@ -1794,7 +1789,7 @@ int inform_testing_reachability(void) { char dirbuf[128]; - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (!me) return 0; control_event_server_status(LOG_NOTICE, @@ -1866,7 +1861,7 @@ int circuit_send_next_onion_skin(origin_circuit_t *circ) { crypt_path_t *hop; - routerinfo_t *router; + const node_t *node; char payload[2+4+DIGEST_LEN+ONIONSKIN_CHALLENGE_LEN]; char *onionskin; size_t payload_len; @@ -1882,7 +1877,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) else control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - router = router_get_by_digest(circ->_base.n_conn->identity_digest); + node = node_get_by_id(circ->_base.n_conn->identity_digest); fast = should_use_create_fast_for_circuit(circ); if (!fast) { /* We are an OR and we know the right onion key: we should @@ -1916,7 +1911,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", fast ? "CREATE_FAST" : "CREATE", - router ? router->nickname : "<unnamed>"); + node ? node_get_nickname(node) : "<unnamed>"); } else { tor_assert(circ->cpath->state == CPATH_STATE_OPEN); tor_assert(circ->_base.state == CIRCUIT_STATE_BUILDING); @@ -1929,7 +1924,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) struct timeval end; long timediff; tor_gettimeofday(&end); - timediff = tv_mdiff(&circ->_base.highres_created, &end); + timediff = tv_mdiff(&circ->_base.timestamp_created, &end); /* * If the circuit build time is much greater than we would have cut @@ -2392,12 +2387,12 @@ onionskin_answer(or_circuit_t *circ, uint8_t cell_type, const char *payload, */ static int new_route_len(uint8_t purpose, extend_info_t *exit, - smartlist_t *routers) + smartlist_t *nodes) { int num_acceptable_routers; int routelen; - tor_assert(routers); + tor_assert(nodes); routelen = DEFAULT_ROUTE_LEN; if (exit && @@ -2405,10 +2400,10 @@ new_route_len(uint8_t purpose, extend_info_t *exit, purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) routelen++; - num_acceptable_routers = count_acceptable_routers(routers); + num_acceptable_routers = count_acceptable_nodes(nodes); log_debug(LD_CIRC,"Chosen route length %d (%d/%d routers suitable).", - routelen, num_acceptable_routers, smartlist_len(routers)); + routelen, num_acceptable_routers, smartlist_len(nodes)); if (num_acceptable_routers < 2) { log_info(LD_CIRC, @@ -2426,24 +2421,12 @@ new_route_len(uint8_t purpose, extend_info_t *exit, return routelen; } -/** Fetch the list of predicted ports, dup it into a smartlist of - * uint16_t's, remove the ones that are already handled by an - * existing circuit, and return it. - */ +/** Return a newly allocated list of uint16_t * for each predicted port not + * handled by a current circuit. */ static smartlist_t * circuit_get_unhandled_ports(time_t now) { - smartlist_t *source = rep_hist_get_predicted_ports(now); - smartlist_t *dest = smartlist_create(); - uint16_t *tmp; - int i; - - for (i = 0; i < smartlist_len(source); ++i) { - tmp = tor_malloc(sizeof(uint16_t)); - memcpy(tmp, smartlist_get(source, i), sizeof(uint16_t)); - smartlist_add(dest, tmp); - } - + smartlist_t *dest = rep_hist_get_predicted_ports(now); circuit_remove_handled_ports(dest); return dest; } @@ -2477,12 +2460,12 @@ circuit_all_predicted_ports_handled(time_t now, int *need_uptime, return enough; } -/** Return 1 if <b>router</b> can handle one or more of the ports in +/** Return 1 if <b>node</b> can handle one or more of the ports in * <b>needed_ports</b>, else return 0. */ static int -router_handles_some_port(routerinfo_t *router, smartlist_t *needed_ports) -{ +node_handles_some_port(const node_t *node, smartlist_t *needed_ports) +{ /* XXXX MOVE */ int i; uint16_t port; @@ -2492,7 +2475,10 @@ router_handles_some_port(routerinfo_t *router, smartlist_t *needed_ports) needed_ports is explicitly a smartlist of uint16_t's */ port = *(uint16_t *)smartlist_get(needed_ports, i); tor_assert(port); - r = compare_addr_to_addr_policy(0, port, router->exit_policy); + if (node) + r = compare_addr_to_node_policy(0, port, node); + else + continue; if (r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED) return 1; } @@ -2525,18 +2511,17 @@ ap_stream_wants_exit_attention(connection_t *conn) * * Return NULL if we can't find any suitable routers. */ -static routerinfo_t * -choose_good_exit_server_general(routerlist_t *dir, int need_uptime, - int need_capacity) +static const node_t * +choose_good_exit_server_general(int need_uptime, int need_capacity) { int *n_supported; - int i; int n_pending_connections = 0; smartlist_t *connections; int best_support = -1; int n_best_support=0; - routerinfo_t *router; or_options_t *options = get_options(); + const smartlist_t *the_nodes; + const node_t *node=NULL; connections = get_connection_array(); @@ -2557,10 +2542,11 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, * * -1 means "Don't use this router at all." */ - n_supported = tor_malloc(sizeof(int)*smartlist_len(dir->routers)); - for (i = 0; i < smartlist_len(dir->routers); ++i) {/* iterate over routers */ - router = smartlist_get(dir->routers, i); - if (router_is_me(router)) { + the_nodes = nodelist_get_list(); + n_supported = tor_malloc(sizeof(int)*smartlist_len(the_nodes)); + SMARTLIST_FOREACH_BEGIN(the_nodes, const node_t *, node) { + const int i = node_sl_idx; + if (router_digest_is_me(node->identity)) { n_supported[i] = -1; // log_fn(LOG_DEBUG,"Skipping node %s -- it's me.", router->nickname); /* XXX there's probably a reverse predecessor attack here, but @@ -2568,13 +2554,15 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, */ continue; } - if (!router->is_running || router->is_bad_exit) { + if (!node_has_descriptor(node)) + continue; + if (!node->is_running || node->is_bad_exit) { n_supported[i] = -1; continue; /* skip routers that are known to be down or bad exits */ } - if (router_is_unreliable(router, need_uptime, need_capacity, 0) && + if (node_is_unreliable(node, need_uptime, need_capacity, 0) && (!options->ExitNodes || - !routerset_contains_router(options->ExitNodes, router))) { + !routerset_contains_node(options->ExitNodes, node))) { /* FFFF Someday, differentiate between a routerset that names * routers, and a routerset that names countries, and only do this * check if they've asked for specific exit relays. Or if the country @@ -2583,18 +2571,19 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, continue; /* skip routers that are not suitable, unless we have * ExitNodes set, in which case we asked for it */ } - if (!(router->is_valid || options->_AllowInvalid & ALLOW_INVALID_EXIT)) { + if (!(node->is_valid || options->_AllowInvalid & ALLOW_INVALID_EXIT)) { /* if it's invalid and we don't want it */ n_supported[i] = -1; // log_fn(LOG_DEBUG,"Skipping node %s (index %d) -- invalid router.", // router->nickname, i); continue; /* skip invalid routers */ } - if (options->ExcludeSingleHopRelays && router->allow_single_hop_exits) { + if (options->ExcludeSingleHopRelays && + node_allows_single_hop_exits(node)) { n_supported[i] = -1; continue; } - if (router_exit_policy_rejects_all(router)) { + if (node_exit_policy_rejects_all(node)) { n_supported[i] = -1; // log_fn(LOG_DEBUG,"Skipping node %s (index %d) -- it rejects all.", // router->nickname, i); @@ -2602,11 +2591,10 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, } n_supported[i] = 0; /* iterate over connections */ - SMARTLIST_FOREACH(connections, connection_t *, conn, - { + SMARTLIST_FOREACH_BEGIN(connections, connection_t *, conn) { if (!ap_stream_wants_exit_attention(conn)) continue; /* Skip everything but APs in CIRCUIT_WAIT */ - if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), router, 1)) { + if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), node, 1)) { ++n_supported[i]; // log_fn(LOG_DEBUG,"%s is supported. n_supported[%d] now %d.", // router->nickname, i, n_supported[i]); @@ -2614,7 +2602,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, // log_fn(LOG_DEBUG,"%s (index %d) would reject this stream.", // router->nickname, i); } - }); /* End looping over connections. */ + } SMARTLIST_FOREACH_END(conn); if (n_pending_connections > 0 && n_supported[i] == 0) { /* Leave best_support at -1 if that's where it is, so we can * distinguish it later. */ @@ -2631,7 +2619,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, * count of equally good routers.*/ ++n_best_support; } - } + } SMARTLIST_FOREACH_END(node); log_info(LD_CIRC, "Found %d servers that might support %d/%d pending connections.", n_best_support, best_support >= 0 ? best_support : 0, @@ -2642,18 +2630,19 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, if (best_support > 0) { smartlist_t *supporting = smartlist_create(), *use = smartlist_create(); - for (i = 0; i < smartlist_len(dir->routers); i++) - if (n_supported[i] == best_support) - smartlist_add(supporting, smartlist_get(dir->routers, i)); + SMARTLIST_FOREACH(the_nodes, const node_t *, node, { + if (n_supported[node_sl_idx] == best_support) + smartlist_add(supporting, (void*)node); + }); - routersets_get_disjunction(use, supporting, options->ExitNodes, + routersets_get_node_disjunction(use, supporting, options->ExitNodes, options->_ExcludeExitNodesUnion, 1); if (smartlist_len(use) == 0 && options->ExitNodes && !options->StrictNodes) { /* give up on exitnodes and try again */ - routersets_get_disjunction(use, supporting, NULL, + routersets_get_node_disjunction(use, supporting, NULL, options->_ExcludeExitNodesUnion, 1); } - router = routerlist_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT); + node = node_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT); smartlist_free(use); smartlist_free(supporting); } else { @@ -2672,7 +2661,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, need_capacity?", fast":"", need_uptime?", stable":""); tor_free(n_supported); - return choose_good_exit_server_general(dir, 0, 0); + return choose_good_exit_server_general(0, 0); } log_notice(LD_CIRC, "All routers are down or won't exit%s -- " "choosing a doomed exit at random.", @@ -2684,28 +2673,29 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, for (attempt = 0; attempt < 2; attempt++) { /* try once to pick only from routers that satisfy a needed port, * then if there are none, pick from any that support exiting. */ - for (i = 0; i < smartlist_len(dir->routers); i++) { - router = smartlist_get(dir->routers, i); - if (n_supported[i] != -1 && - (attempt || router_handles_some_port(router, needed_ports))) { + SMARTLIST_FOREACH_BEGIN(the_nodes, const node_t *, node) { + if (!node_has_descriptor(node)) + continue; + if (n_supported[node_sl_idx] != -1 && + (attempt || node_handles_some_port(node, needed_ports))) { // log_fn(LOG_DEBUG,"Try %d: '%s' is a possibility.", // try, router->nickname); - smartlist_add(supporting, router); + smartlist_add(supporting, (void*)node); } - } + } SMARTLIST_FOREACH_END(node); - routersets_get_disjunction(use, supporting, options->ExitNodes, + routersets_get_node_disjunction(use, supporting, options->ExitNodes, options->_ExcludeExitNodesUnion, 1); if (smartlist_len(use) == 0 && options->ExitNodes && !options->StrictNodes) { /* give up on exitnodes and try again */ - routersets_get_disjunction(use, supporting, NULL, + routersets_get_node_disjunction(use, supporting, NULL, options->_ExcludeExitNodesUnion, 1); } /* FFF sometimes the above results in null, when the requested * exit node is considered down by the consensus. we should pick * it anyway, since the user asked for it. */ - router = routerlist_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT); - if (router) + node = node_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT); + if (node) break; smartlist_clear(supporting); smartlist_clear(use); @@ -2717,9 +2707,9 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, } tor_free(n_supported); - if (router) { - log_info(LD_CIRC, "Chose exit server '%s'", router->nickname); - return router; + if (node) { + log_info(LD_CIRC, "Chose exit server '%s'", node_get_nickname(node)); + return node; } if (options->ExitNodes && options->StrictNodes) { log_warn(LD_CIRC, @@ -2739,12 +2729,12 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, * For client-side rendezvous circuits, choose a random node, weighted * toward the preferences in 'options'. */ -static routerinfo_t * -choose_good_exit_server(uint8_t purpose, routerlist_t *dir, +static const node_t * +choose_good_exit_server(uint8_t purpose, int need_uptime, int need_capacity, int is_internal) { or_options_t *options = get_options(); - router_crn_flags_t flags = 0; + router_crn_flags_t flags = CRN_NEED_DESC; if (need_uptime) flags |= CRN_NEED_UPTIME; if (need_capacity) @@ -2757,7 +2747,7 @@ choose_good_exit_server(uint8_t purpose, routerlist_t *dir, if (is_internal) /* pick it like a middle hop */ return router_choose_random_node(NULL, options->ExcludeNodes, flags); else - return choose_good_exit_server_general(dir,need_uptime,need_capacity); + return choose_good_exit_server_general(need_uptime,need_capacity); case CIRCUIT_PURPOSE_C_ESTABLISH_REND: if (options->_AllowInvalid & ALLOW_INVALID_RENDEZVOUS) flags |= CRN_ALLOW_INVALID; @@ -2838,13 +2828,12 @@ static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit) { cpath_build_state_t *state = circ->build_state; - routerlist_t *rl = router_get_routerlist(); if (state->onehop_tunnel) { log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel."); state->desired_path_len = 1; } else { - int r = new_route_len(circ->_base.purpose, exit, rl->routers); + int r = new_route_len(circ->_base.purpose, exit, nodelist_get_list()); if (r < 1) /* must be at least 1 */ return -1; state->desired_path_len = r; @@ -2855,14 +2844,15 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit) log_info(LD_CIRC,"Using requested exit node '%s'", exit->nickname); exit = extend_info_dup(exit); } else { /* we have to decide one */ - routerinfo_t *router = - choose_good_exit_server(circ->_base.purpose, rl, state->need_uptime, + const node_t *node = + choose_good_exit_server(circ->_base.purpose, state->need_uptime, state->need_capacity, state->is_internal); - if (!router) { + if (!node) { log_warn(LD_CIRC,"failed to choose an exit server"); return -1; } - exit = extend_info_from_router(router); + exit = extend_info_from_node(node); + tor_assert(exit); } state->chosen_exit = exit; return 0; @@ -2913,35 +2903,30 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit) * and available for building circuits through. */ static int -count_acceptable_routers(smartlist_t *routers) +count_acceptable_nodes(smartlist_t *nodes) { - int i, n; int num=0; - routerinfo_t *r; - n = smartlist_len(routers); - for (i=0;i<n;i++) { - r = smartlist_get(routers, i); -// log_debug(LD_CIRC, + SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) { + // log_debug(LD_CIRC, // "Contemplating whether router %d (%s) is a new option.", // i, r->nickname); - if (r->is_running == 0) { + if (! node->is_running) // log_debug(LD_CIRC,"Nope, the directory says %d is not running.",i); - goto next_i_loop; - } - if (r->is_valid == 0) { + continue; + if (! node->is_valid) // log_debug(LD_CIRC,"Nope, the directory says %d is not valid.",i); - goto next_i_loop; + continue; + if (! node_has_descriptor(node)) + continue; /* XXX This clause makes us count incorrectly: if AllowInvalidRouters * allows this node in some places, then we're getting an inaccurate * count. For now, be conservative and don't count it. But later we * should try to be smarter. */ - } - num++; + ++num; + } SMARTLIST_FOREACH_END(node); + // log_debug(LD_CIRC,"I like %d. num_acceptable_routers now %d.",i, num); - next_i_loop: - ; /* C requires an explicit statement after the label */ - } return num; } @@ -2969,31 +2954,31 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop) * circuit. In particular, make sure we don't pick the exit node or its * family, and make sure we don't duplicate any previous nodes or their * families. */ -static routerinfo_t * +static const node_t * choose_good_middle_server(uint8_t purpose, cpath_build_state_t *state, crypt_path_t *head, int cur_len) { int i; - routerinfo_t *r, *choice; + const node_t *r, *choice; crypt_path_t *cpath; smartlist_t *excluded; or_options_t *options = get_options(); - router_crn_flags_t flags = 0; + router_crn_flags_t flags = CRN_NEED_DESC; tor_assert(_CIRCUIT_PURPOSE_MIN <= purpose && purpose <= _CIRCUIT_PURPOSE_MAX); log_debug(LD_CIRC, "Contemplating intermediate hop: random choice."); excluded = smartlist_create(); - if ((r = build_state_get_exit_router(state))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if ((r = build_state_get_exit_node(state))) { + smartlist_add(excluded, (void*) r); + nodelist_add_node_family(excluded, r); } for (i = 0, cpath = head; i < cur_len; ++i, cpath=cpath->next) { - if ((r = router_get_by_digest(cpath->extend_info->identity_digest))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if ((r = node_get_by_id(cpath->extend_info->identity_digest))) { + smartlist_add(excluded, (void*)r); + nodelist_add_node_family(excluded, r); } } @@ -3016,44 +3001,45 @@ choose_good_middle_server(uint8_t purpose, * If <b>state</b> is NULL, we're choosing a router to serve as an entry * guard, not for any particular circuit. */ -static routerinfo_t * +static const node_t * choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) { - routerinfo_t *r, *choice; + const node_t *choice; smartlist_t *excluded; or_options_t *options = get_options(); - router_crn_flags_t flags = CRN_NEED_GUARD; + router_crn_flags_t flags = CRN_NEED_GUARD|CRN_NEED_DESC; + const node_t *node; if (state && options->UseEntryGuards && (purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) { + /* This is request for an entry server to use for a regular circuit, + * and we use entry guard nodes. Just return one of the guard nodes. */ return choose_random_entry(state); } excluded = smartlist_create(); - if (state && (r = build_state_get_exit_router(state))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if (state && (node = build_state_get_exit_node(state))) { + /* Exclude the exit node from the state, if we have one. Also exclude its + * family. */ + smartlist_add(excluded, (void*)node); + nodelist_add_node_family(excluded, node); } if (firewall_is_fascist_or()) { - /*XXXX This could slow things down a lot; use a smarter implementation */ - /* exclude all ORs that listen on the wrong port, if anybody notices. */ - routerlist_t *rl = router_get_routerlist(); - int i; - - for (i=0; i < smartlist_len(rl->routers); i++) { - r = smartlist_get(rl->routers, i); - if (!fascist_firewall_allows_or(r)) - smartlist_add(excluded, r); - } + /* Exclude all ORs that we can't reach through our firewall */ + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, const node_t *, node, { + if (!fascist_firewall_allows_node(node)) + smartlist_add(excluded, (void*)node); + }); } - /* and exclude current entry guards, if applicable */ + /* and exclude current entry guards and their families, if applicable */ if (options->UseEntryGuards && entry_guards) { SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, { - if ((r = router_get_by_digest(entry->identity))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if ((node = node_get_by_id(entry->identity))) { + smartlist_add(excluded, (void*)node); + nodelist_add_node_family(excluded, node); } }); } @@ -3109,14 +3095,18 @@ onion_extend_cpath(origin_circuit_t *circ) if (cur_len == state->desired_path_len - 1) { /* Picking last node */ info = extend_info_dup(state->chosen_exit); } else if (cur_len == 0) { /* picking first node */ - routerinfo_t *r = choose_good_entry_server(purpose, state); - if (r) - info = extend_info_from_router(r); + const node_t *r = choose_good_entry_server(purpose, state); + if (r) { + info = extend_info_from_node(r); + tor_assert(info); + } } else { - routerinfo_t *r = + const node_t *r = choose_good_middle_server(purpose, state, circ->cpath, cur_len); - if (r) - info = extend_info_from_router(r); + if (r) { + info = extend_info_from_node(r); + tor_assert(info); + } } if (!info) { @@ -3175,7 +3165,7 @@ extend_info_alloc(const char *nickname, const char *digest, /** Allocate and return a new extend_info_t that can be used to build a * circuit to or through the router <b>r</b>. */ extend_info_t * -extend_info_from_router(routerinfo_t *r) +extend_info_from_router(const routerinfo_t *r) { tor_addr_t addr; tor_assert(r); @@ -3184,6 +3174,29 @@ extend_info_from_router(routerinfo_t *r) r->onion_pkey, &addr, r->or_port); } +/** Allocate and return a new extend_info that can be used to build a ircuit + * to or through the node <b>node</b>. May return NULL if there is not + * enough info about <b>node</b> to extend to it--for example, if there + * is no routerinfo_t or microdesc_t. + **/ +extend_info_t * +extend_info_from_node(const node_t *node) +{ + if (node->ri) { + return extend_info_from_router(node->ri); + } else if (node->rs && node->md) { + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, node->rs->addr); + return extend_info_alloc(node->rs->nickname, + node->identity, + node->md->onion_pkey, + &addr, + node->rs->or_port); + } else { + return NULL; + } +} + /** Release storage held by an extend_info_t struct. */ void extend_info_free(extend_info_t *info) @@ -3214,12 +3227,12 @@ extend_info_dup(extend_info_t *info) * If there is no chosen exit, or if we don't know the routerinfo_t for * the chosen exit, return NULL. */ -routerinfo_t * -build_state_get_exit_router(cpath_build_state_t *state) +const node_t * +build_state_get_exit_node(cpath_build_state_t *state) { if (!state || !state->chosen_exit) return NULL; - return router_get_by_digest(state->chosen_exit->identity_digest); + return node_get_by_id(state->chosen_exit->identity_digest); } /** Return the nickname for the chosen exit router in <b>state</b>. If @@ -3241,9 +3254,8 @@ build_state_get_exit_nickname(cpath_build_state_t *state) * * If it's not usable, set *<b>reason</b> to a static string explaining why. */ -/*XXXX take a routerstatus, not a routerinfo. */ static int -entry_guard_set_status(entry_guard_t *e, routerinfo_t *ri, +entry_guard_set_status(entry_guard_t *e, const node_t *node, time_t now, or_options_t *options, const char **reason) { char buf[HEX_DIGEST_LEN+1]; @@ -3252,16 +3264,17 @@ entry_guard_set_status(entry_guard_t *e, routerinfo_t *ri, *reason = NULL; /* Do we want to mark this guard as bad? */ - if (!ri) + if (!node) *reason = "unlisted"; - else if (!ri->is_running) + else if (!node->is_running) *reason = "down"; - else if (options->UseBridges && ri->purpose != ROUTER_PURPOSE_BRIDGE) + else if (options->UseBridges && (!node->ri || + node->ri->purpose != ROUTER_PURPOSE_BRIDGE)) *reason = "not a bridge"; - else if (!options->UseBridges && !ri->is_possible_guard && - !routerset_contains_router(options->EntryNodes,ri)) + else if (!options->UseBridges && !node->is_possible_guard && + !routerset_contains_node(options->EntryNodes,node)) *reason = "not recommended as a guard"; - else if (routerset_contains_router(options->ExcludeNodes, ri)) + else if (routerset_contains_node(options->ExcludeNodes, node)) *reason = "excluded"; if (*reason && ! e->bad_since) { @@ -3305,7 +3318,7 @@ entry_is_time_to_retry(entry_guard_t *e, time_t now) return now > (e->last_attempted + 36*60*60); } -/** Return the router corresponding to <b>e</b>, if <b>e</b> is +/** Return the node corresponding to <b>e</b>, if <b>e</b> is * working well enough that we are willing to use it as an entry * right now. (Else return NULL.) In particular, it must be * - Listed as either up or never yet contacted; @@ -3319,11 +3332,11 @@ entry_is_time_to_retry(entry_guard_t *e, time_t now) * * If the answer is no, set *<b>msg</b> to an explanation of why. */ -static INLINE routerinfo_t * +static INLINE const node_t * entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, int assume_reachable, const char **msg) { - routerinfo_t *r; + const node_t *node; or_options_t *options = get_options(); tor_assert(msg); @@ -3337,33 +3350,36 @@ entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, *msg = "unreachable"; return NULL; } - r = router_get_by_digest(e->identity); - if (!r) { + node = node_get_by_id(e->identity); + if (!node || !node_has_descriptor(node)) { *msg = "no descriptor"; return NULL; } - if (get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_BRIDGE) { - *msg = "not a bridge"; - return NULL; - } - if (!get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_GENERAL) { - *msg = "not general-purpose"; - return NULL; + if (get_options()->UseBridges) { + if (node_get_purpose(node) != ROUTER_PURPOSE_BRIDGE) { + *msg = "not a bridge"; + return NULL; + } + } else { /* !get_options()->UseBridges */ + if (node_get_purpose(node) != ROUTER_PURPOSE_GENERAL) { + *msg = "not general-purpose"; + return NULL; + } } if (options->EntryNodes && - routerset_contains_router(options->EntryNodes, r)) { + routerset_contains_node(options->EntryNodes, node)) { /* they asked for it, they get it */ need_uptime = need_capacity = 0; } - if (router_is_unreliable(r, need_uptime, need_capacity, 0)) { + if (node_is_unreliable(node, need_uptime, need_capacity, 0)) { *msg = "not fast/stable"; return NULL; } - if (!fascist_firewall_allows_or(r)) { + if (!fascist_firewall_allows_node(node)) { *msg = "unreachable by config"; return NULL; } - return r; + return node; } /** Return the number of entry guards that we think are usable. */ @@ -3461,15 +3477,15 @@ control_event_guard_deferred(void) * If <b>chosen</b> is defined, use that one, and if it's not * already in our entry_guards list, put it at the *beginning*. * Else, put the one we pick at the end of the list. */ -static routerinfo_t * -add_an_entry_guard(routerinfo_t *chosen, int reset_status) +static const node_t * +add_an_entry_guard(const node_t *chosen, int reset_status) { - routerinfo_t *router; + const node_t *node; entry_guard_t *entry; if (chosen) { - router = chosen; - entry = is_an_entry_guard(router->cache_info.identity_digest); + node = chosen; + entry = is_an_entry_guard(node->identity); if (entry) { if (reset_status) { entry->bad_since = 0; @@ -3478,14 +3494,15 @@ add_an_entry_guard(routerinfo_t *chosen, int reset_status) return NULL; } } else { - router = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); - if (!router) + node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); + if (!node) return NULL; } entry = tor_malloc_zero(sizeof(entry_guard_t)); - log_info(LD_CIRC, "Chose '%s' as new entry guard.", router->nickname); - strlcpy(entry->nickname, router->nickname, sizeof(entry->nickname)); - memcpy(entry->identity, router->cache_info.identity_digest, DIGEST_LEN); + log_info(LD_CIRC, "Chose '%s' as new entry guard.", + node_get_nickname(node)); + strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); + memcpy(entry->identity, node->identity, DIGEST_LEN); /* Choose expiry time smudged over the past month. The goal here * is to a) spread out when Tor clients rotate their guards, so they * don't all select them on the same day, and b) avoid leaving a @@ -3500,7 +3517,7 @@ add_an_entry_guard(routerinfo_t *chosen, int reset_status) control_event_guard(entry->nickname, entry->identity, "NEW"); control_event_guard_deferred(); log_entry_guards(LOG_INFO); - return router; + return node; } /** If the use of entry guards is configured, choose more entry guards @@ -3654,7 +3671,7 @@ entry_guards_compute_status(or_options_t *options, time_t now) reasons = digestmap_new(); SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { - routerinfo_t *r = router_get_by_digest(entry->identity); + const node_t *r = node_get_by_id(entry->identity); const char *reason = NULL; if (entry_guard_set_status(entry, r, now, options, &reason)) changed = 1; @@ -3675,7 +3692,7 @@ entry_guards_compute_status(or_options_t *options, time_t now) SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { const char *reason = digestmap_get(reasons, entry->identity); const char *live_msg = ""; - routerinfo_t *r = entry_is_live(entry, 0, 1, 0, &live_msg); + const node_t *r = entry_is_live(entry, 0, 1, 0, &live_msg); log_info(LD_CIRC, "Summary: Entry '%s' is %s, %s%s%s, and %s%s.", entry->nickname, entry->unreachable_since ? "unreachable" : "reachable", @@ -3792,7 +3809,7 @@ entry_guard_register_connect_status(const char *digest, int succeeded, break; if (e->made_contact) { const char *msg; - routerinfo_t *r = entry_is_live(e, 0, 1, 1, &msg); + const node_t *r = entry_is_live(e, 0, 1, 1, &msg); if (r && e->unreachable_since) { refuse_conn = 1; e->can_retry = 1; @@ -3833,7 +3850,7 @@ entry_nodes_should_be_added(void) static void entry_guards_prepend_from_config(or_options_t *options) { - smartlist_t *entry_routers, *entry_fps; + smartlist_t *entry_nodes, *entry_fps; smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list; tor_assert(entry_guards); @@ -3853,22 +3870,22 @@ entry_guards_prepend_from_config(or_options_t *options) tor_free(string); } - entry_routers = smartlist_create(); + entry_nodes = smartlist_create(); entry_fps = smartlist_create(); old_entry_guards_on_list = smartlist_create(); old_entry_guards_not_on_list = smartlist_create(); /* Split entry guards into those on the list and those not. */ - /* XXXX022 Now that we allow countries and IP ranges in EntryNodes, this is - * potentially an enormous list. For now, we disable such values for - * EntryNodes in options_validate(); really, this wants a better solution. - * Perhaps we should do this calculation once whenever the list of routers - * changes or the entrynodes setting changes. - */ - routerset_get_all_routers(entry_routers, options->EntryNodes, 0); - SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, - smartlist_add(entry_fps,ri->cache_info.identity_digest)); + /* Now that we allow countries and IP ranges in EntryNodes, this is + * potentially an enormous list. It's not so bad though because we + * only call this function when a) we're making a new circuit, and b) + * we've called directory_info_has_arrived() or changed our EntryNodes + * since the last time we made a circuit. */ + routerset_get_all_nodes(entry_nodes, options->EntryNodes, 0); + SMARTLIST_FOREACH(entry_nodes, const node_t *,node, + smartlist_add(entry_fps, (void*)node->identity)); + SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, { if (smartlist_digest_isin(entry_fps, e->identity)) smartlist_add(old_entry_guards_on_list, e); @@ -3877,9 +3894,9 @@ entry_guards_prepend_from_config(or_options_t *options) }); /* Remove all currently configured entry guards from entry_routers. */ - SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, { - if (is_an_entry_guard(ri->cache_info.identity_digest)) { - SMARTLIST_DEL_CURRENT(entry_routers, ri); + SMARTLIST_FOREACH(entry_nodes, const node_t *, node, { + if (is_an_entry_guard(node->identity)) { + SMARTLIST_DEL_CURRENT(entry_nodes, node); } }); @@ -3888,8 +3905,8 @@ entry_guards_prepend_from_config(or_options_t *options) /* First, the previously configured guards that are in EntryNodes. */ smartlist_add_all(entry_guards, old_entry_guards_on_list); /* Next, the rest of EntryNodes */ - SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, { - add_an_entry_guard(ri, 0); + SMARTLIST_FOREACH(entry_nodes, const node_t *, node, { + add_an_entry_guard(node, 0); }); /* Finally, the remaining previously configured guards that are not in * EntryNodes, unless we're strict in which case we drop them */ @@ -3900,7 +3917,7 @@ entry_guards_prepend_from_config(or_options_t *options) smartlist_add_all(entry_guards, old_entry_guards_not_on_list); } - smartlist_free(entry_routers); + smartlist_free(entry_nodes); smartlist_free(entry_fps); smartlist_free(old_entry_guards_on_list); smartlist_free(old_entry_guards_not_on_list); @@ -3938,21 +3955,22 @@ entry_list_is_totally_static(or_options_t *options) * make sure not to pick this circuit's exit or any node in the * exit's family. If <b>state</b> is NULL, we're looking for a random * guard (likely a bridge). */ -routerinfo_t * +const node_t * choose_random_entry(cpath_build_state_t *state) { or_options_t *options = get_options(); smartlist_t *live_entry_guards = smartlist_create(); smartlist_t *exit_family = smartlist_create(); - routerinfo_t *chosen_exit = state?build_state_get_exit_router(state) : NULL; - routerinfo_t *r = NULL; + const node_t *chosen_exit = + state?build_state_get_exit_node(state) : NULL; + const node_t *node = NULL; int need_uptime = state ? state->need_uptime : 0; int need_capacity = state ? state->need_capacity : 0; int preferred_min, consider_exit_family = 0; if (chosen_exit) { - smartlist_add(exit_family, chosen_exit); - routerlist_add_family(exit_family, chosen_exit); + smartlist_add(exit_family, (void*) chosen_exit); + nodelist_add_node_family(exit_family, chosen_exit); consider_exit_family = 1; } @@ -3968,16 +3986,15 @@ choose_random_entry(cpath_build_state_t *state) retry: smartlist_clear(live_entry_guards); - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, - { + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { const char *msg; - r = entry_is_live(entry, need_uptime, need_capacity, 0, &msg); - if (!r) + node = entry_is_live(entry, need_uptime, need_capacity, 0, &msg); + if (!node) continue; /* down, no point */ - if (consider_exit_family && smartlist_isin(exit_family, r)) + if (consider_exit_family && smartlist_isin(exit_family, node)) continue; /* avoid relays that are family members of our exit */ if (options->EntryNodes && - !routerset_contains_router(options->EntryNodes, r)) { + !routerset_contains_node(options->EntryNodes, node)) { /* We've come to the end of our preferred entry nodes. */ if (smartlist_len(live_entry_guards)) goto choose_and_finish; /* only choose from the ones we like */ @@ -3990,7 +4007,7 @@ choose_random_entry(cpath_build_state_t *state) "No relays from EntryNodes available. Using others."); } } - smartlist_add(live_entry_guards, r); + smartlist_add(live_entry_guards, (void*)node); if (!entry->made_contact) { /* Always start with the first not-yet-contacted entry * guard. Otherwise we might add several new ones, pick @@ -4000,7 +4017,7 @@ choose_random_entry(cpath_build_state_t *state) } if (smartlist_len(live_entry_guards) >= options->NumEntryGuards) break; /* we have enough */ - }); + } SMARTLIST_FOREACH_END(entry); if (entry_list_is_constrained(options)) { /* If we prefer the entry nodes we've got, and we have at least @@ -4020,8 +4037,8 @@ choose_random_entry(cpath_build_state_t *state) /* XXX if guard doesn't imply fast and stable, then we need * to tell add_an_entry_guard below what we want, or it might * be a long time til we get it. -RD */ - r = add_an_entry_guard(NULL, 0); - if (r) { + node = add_an_entry_guard(NULL, 0); + if (node) { entry_guards_changed(); /* XXX we start over here in case the new node we added shares * a family with our exit node. There's a chance that we'll just @@ -4031,16 +4048,16 @@ choose_random_entry(cpath_build_state_t *state) goto retry; } } - if (!r && need_uptime) { + if (!node && need_uptime) { need_uptime = 0; /* try without that requirement */ goto retry; } - if (!r && need_capacity) { + if (!node && need_capacity) { /* still no? last attempt, try without requiring capacity */ need_capacity = 0; goto retry; } - if (!r && entry_list_is_constrained(options) && consider_exit_family) { + if (!node && entry_list_is_constrained(options) && consider_exit_family) { /* still no? if we're using bridges or have strictentrynodes * set, and our chosen exit is in the same family as all our * bridges/entry guards, then be flexible about families. */ @@ -4054,16 +4071,16 @@ choose_random_entry(cpath_build_state_t *state) if (entry_list_is_constrained(options)) { /* We need to weight by bandwidth, because our bridges or entryguards * were not already selected proportional to their bandwidth. */ - r = routerlist_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD); + node = node_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD); } else { /* We choose uniformly at random here, because choose_good_entry_server() * already weights its choices by bandwidth, so we don't want to * *double*-weight our guard selection. */ - r = smartlist_choose(live_entry_guards); + node = smartlist_choose(live_entry_guards); } smartlist_free(live_entry_guards); smartlist_free(exit_family); - return r; + return node; } /** Parse <b>state</b> and learn about the entry guards it describes. @@ -4310,7 +4327,7 @@ getinfo_helper_entry_guards(control_connection_t *conn, char *c = tor_malloc(len); const char *status = NULL; time_t when = 0; - routerinfo_t *ri; + const node_t *node; if (!e->made_contact) { status = "never-connected"; @@ -4321,9 +4338,9 @@ getinfo_helper_entry_guards(control_connection_t *conn, status = "up"; } - ri = router_get_by_digest(e->identity); - if (ri) { - router_get_verbose_nickname(nbuf, ri); + node = node_get_by_id(e->identity); + if (node) { + node_get_verbose_nickname(node, nbuf); } else { nbuf[0] = '$'; base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN); @@ -4400,7 +4417,7 @@ get_configured_bridge_by_addr_port_digest(tor_addr_t *addr, uint16_t port, /** Wrapper around get_configured_bridge_by_addr_port_digest() to look * it up via router descriptor <b>ri</b>. */ static bridge_info_t * -get_configured_bridge_by_routerinfo(routerinfo_t *ri) +get_configured_bridge_by_routerinfo(const routerinfo_t *ri) { tor_addr_t addr; tor_addr_from_ipv4h(&addr, ri->addr); @@ -4410,7 +4427,7 @@ get_configured_bridge_by_routerinfo(routerinfo_t *ri) /** Return 1 if <b>ri</b> is one of our known bridges, else 0. */ int -routerinfo_is_a_configured_bridge(routerinfo_t *ri) +routerinfo_is_a_configured_bridge(const routerinfo_t *ri) { return get_configured_bridge_by_routerinfo(ri) ? 1 : 0; } @@ -4569,14 +4586,17 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) int first = !any_bridge_descriptors_known(); bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); time_t now = time(NULL); - ri->is_running = 1; + router_set_status(ri->cache_info.identity_digest, 1); if (bridge) { /* if we actually want to use this one */ + const node_t *node; /* it's here; schedule its re-fetch for a long time from now. */ if (!from_cache) download_status_reset(&bridge->fetch_status); - add_an_entry_guard(ri, 1); + node = node_get_by_id(ri->cache_info.identity_digest); + tor_assert(node); + add_an_entry_guard(node, 1); log_notice(LD_DIR, "new bridge descriptor '%s' (%s)", ri->nickname, from_cache ? "cached" : "fresh"); /* set entry->made_contact so if it goes down we don't drop it from @@ -4630,19 +4650,18 @@ any_pending_bridge_descriptor_fetches(void) static int entries_retry_helper(or_options_t *options, int act) { - routerinfo_t *ri; + const node_t *node; int any_known = 0; int any_running = 0; - int purpose = options->UseBridges ? - ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL; + int need_bridges = options->UseBridges != 0; if (!entry_guards) entry_guards = smartlist_create(); - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, - { - ri = router_get_by_digest(e->identity); - if (ri && ri->purpose == purpose) { + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + node = node_get_by_id(e->identity); + if (node && node_has_descriptor(node) && + node_is_bridge(node) == need_bridges) { any_known = 1; - if (ri->is_running) + if (node->is_running) any_running = 1; /* some entry is both known and running */ else if (act) { /* Mark all current connections to this OR as unhealthy, since @@ -4651,15 +4670,15 @@ entries_retry_helper(or_options_t *options, int act) * the node down and undermine the retry attempt. We mark even * the established conns, since if the network just came back * we'll want to attach circuits to fresh conns. */ - connection_or_set_bad_connections(ri->cache_info.identity_digest, 1); + connection_or_set_bad_connections(node->identity, 1); /* mark this entry node for retry */ - router_set_status(ri->cache_info.identity_digest, 1); + router_set_status(node->identity, 1); e->can_retry = 1; e->bad_since = 0; } } - }); + } SMARTLIST_FOREACH_END(e); log_debug(LD_DIR, "%d: any_known %d, any_running %d", act, any_known, any_running); return any_known && !any_running; diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 74bbd4f22a..c664440eb1 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -44,10 +44,11 @@ void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop); extend_info_t *extend_info_alloc(const char *nickname, const char *digest, crypto_pk_env_t *onion_key, const tor_addr_t *addr, uint16_t port); -extend_info_t *extend_info_from_router(routerinfo_t *r); +extend_info_t *extend_info_from_router(const routerinfo_t *r); +extend_info_t *extend_info_from_node(const node_t *node); extend_info_t *extend_info_dup(extend_info_t *info); void extend_info_free(extend_info_t *info); -routerinfo_t *build_state_get_exit_router(cpath_build_state_t *state); +const node_t *build_state_get_exit_node(cpath_build_state_t *state); const char *build_state_get_exit_nickname(cpath_build_state_t *state); void entry_guards_compute_status(or_options_t *options, time_t now); @@ -55,7 +56,7 @@ int entry_guard_register_connect_status(const char *digest, int succeeded, int mark_relay_status, time_t now); void entry_nodes_should_be_added(void); int entry_list_is_constrained(or_options_t *options); -routerinfo_t *choose_random_entry(cpath_build_state_t *state); +const node_t *choose_random_entry(cpath_build_state_t *state); int entry_guards_parse_state(or_state_t *state, int set, char **msg); void entry_guards_update_state(or_state_t *state); int getinfo_helper_entry_guards(control_connection_t *conn, @@ -63,9 +64,9 @@ int getinfo_helper_entry_guards(control_connection_t *conn, const char **errmsg); void clear_bridge_list(void); -int routerinfo_is_a_configured_bridge(routerinfo_t *ri); -void -learned_router_identity(tor_addr_t *addr, uint16_t port, const char *digest); +int routerinfo_is_a_configured_bridge(const routerinfo_t *ri); +void learned_router_identity(tor_addr_t *addr, uint16_t port, + const char *digest); void bridge_add_from_config(const tor_addr_t *addr, uint16_t port, char *digest); void retry_bridge_descriptor_fetch_directly(const char *digest); diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 58ff27e5e1..d033cc6321 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -19,6 +19,7 @@ #include "connection_or.h" #include "control.h" #include "networkstatus.h" +#include "nodelist.h" #include "onion.h" #include "relay.h" #include "rendclient.h" @@ -86,10 +87,7 @@ orconn_circid_circuit_map_t *_last_circid_orconn_ent = NULL; /** Implementation helper for circuit_set_{p,n}_circid_orconn: A circuit ID * and/or or_connection for circ has just changed from <b>old_conn, old_id</b> * to <b>conn, id</b>. Adjust the conn,circid map as appropriate, removing - * the old entry (if any) and adding a new one. If <b>active</b> is true, - * remove the circuit from the list of active circuits on old_conn and add it - * to the list of active circuits on conn. - * XXX "active" isn't an arg anymore */ + * the old entry (if any) and adding a new one. */ static void circuit_set_circid_orconn_helper(circuit_t *circ, int direction, circid_t id, @@ -396,8 +394,7 @@ circuit_initial_package_window(void) static void init_circuit_base(circuit_t *circ) { - circ->timestamp_created = time(NULL); - tor_gettimeofday(&circ->highres_created); + tor_gettimeofday(&circ->timestamp_created); circ->package_window = circuit_initial_package_window(); circ->deliver_window = CIRCWINDOW_START; @@ -609,7 +606,8 @@ circuit_dump_details(int severity, circuit_t *circ, int conn_array_index, log(severity, LD_CIRC, "Conn %d has %s circuit: circID %d (other side %d), " "state %d (%s), born %d:", conn_array_index, type, this_circid, other_circid, circ->state, - circuit_state_to_string(circ->state), (int)circ->timestamp_created); + circuit_state_to_string(circ->state), + (int)circ->timestamp_created.tv_sec); if (CIRCUIT_IS_ORIGIN(circ)) { /* circ starts at this node */ circuit_log_path(severity, LD_CIRC, TO_ORIGIN_CIRCUIT(circ)); } @@ -946,15 +944,15 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, if (info) { /* need to make sure we don't duplicate hops */ crypt_path_t *hop = circ->cpath; - routerinfo_t *ri1 = router_get_by_digest(info->identity_digest); + const node_t *ri1 = node_get_by_id(info->identity_digest); do { - routerinfo_t *ri2; + const node_t *ri2; if (!memcmp(hop->extend_info->identity_digest, info->identity_digest, DIGEST_LEN)) goto next; if (ri1 && - (ri2 = router_get_by_digest(hop->extend_info->identity_digest)) - && routers_in_same_family(ri1, ri2)) + (ri2 = node_get_by_id(hop->extend_info->identity_digest)) + && nodes_in_same_family(ri1, ri2)) goto next; hop=hop->next; } while (hop!=circ->cpath); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 8d9d115863..3beb267ea7 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -17,6 +17,7 @@ #include "connection.h" #include "connection_edge.h" #include "control.h" +#include "nodelist.h" #include "policies.h" #include "rendclient.h" #include "rendcommon.h" @@ -43,7 +44,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, int need_uptime, int need_internal, time_t now) { - routerinfo_t *exitrouter; + const node_t *exitnode; cpath_build_state_t *build_state; tor_assert(circ); tor_assert(conn); @@ -85,7 +86,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, * of the one we meant to finish at. */ build_state = TO_ORIGIN_CIRCUIT(circ)->build_state; - exitrouter = build_state_get_exit_router(build_state); + exitnode = build_state_get_exit_node(build_state); if (need_uptime && !build_state->need_uptime) return 0; @@ -93,7 +94,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, return 0; if (purpose == CIRCUIT_PURPOSE_C_GENERAL) { - if (!exitrouter && !build_state->onehop_tunnel) { + if (!exitnode && !build_state->onehop_tunnel) { log_debug(LD_CIRC,"Not considering circuit with unknown router."); return 0; /* this circuit is screwed and doesn't know it yet, * or is a rendezvous circuit. */ @@ -127,7 +128,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, return 0; } } - if (exitrouter && !connection_ap_can_use_exit(conn, exitrouter, 0)) { + if (exitnode && !connection_ap_can_use_exit(conn, exitnode, 0)) { /* can't exit from this router */ return 0; } @@ -162,7 +163,7 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose) return 1; } else { if (a->timestamp_dirty || - a->timestamp_created > b->timestamp_created) + tor_timercmp(&a->timestamp_created, &b->timestamp_created, >)) return 1; if (CIRCUIT_IS_ORIGIN(b) && TO_ORIGIN_CIRCUIT(b)->build_state->is_internal) @@ -222,8 +223,8 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose, * seen lately, a la Fallon Chen's GSoC work -RD */ #define REND_PARALLEL_INTRO_DELAY 15 if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && - !must_be_open && circ->state != CIRCUIT_STATE_OPEN && - circ->timestamp_created + REND_PARALLEL_INTRO_DELAY < now) { + !must_be_open && circ->state != CIRCUIT_STATE_OPEN && + circ->timestamp_created.tv_sec + REND_PARALLEL_INTRO_DELAY < now) { intro_going_on_but_too_old = 1; continue; } @@ -312,14 +313,14 @@ circuit_expire_building(time_t now) else cutoff = general_cutoff; - if (victim->timestamp_created > cutoff) + if (victim->timestamp_created.tv_sec > cutoff) continue; /* it's still young, leave it alone */ #if 0 /* some debug logs, to help track bugs */ if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING && - victim->timestamp_created <= introcirc_cutoff && - victim->timestamp_created > general_cutoff) + victim->timestamp_created.tv_sec <= introcirc_cutoff && + victim->timestamp_created.tv_sec > general_cutoff) log_info(LD_REND|LD_CIRC, "Timing out introduction circuit which we " "would not have done if it had been a general circuit."); @@ -407,15 +408,16 @@ circuit_expire_building(time_t now) * it off at, we probably had a suspend event along this codepath, * and we should discard the value. */ - if (now - victim->timestamp_created > 2*circ_times.close_ms/1000+1) { + if (now - victim->timestamp_created.tv_sec > + 2*circ_times.close_ms/1000+1) { log_notice(LD_CIRC, "Extremely large value for circuit build timeout: %lds. " "Assuming clock jump. Purpose %d", - (long)(now - victim->timestamp_created), + (long)(now - victim->timestamp_created.tv_sec), victim->purpose); } else if (circuit_build_times_count_close(&circ_times, - first_hop_succeeded, - victim->timestamp_created)) { + first_hop_succeeded, + victim->timestamp_created.tv_sec)) { circuit_build_times_set_timeout(&circ_times); } } @@ -473,7 +475,7 @@ circuit_stream_is_being_handled(edge_connection_t *conn, uint16_t port, int min) { circuit_t *circ; - routerinfo_t *exitrouter; + const node_t *exitnode; int num=0; time_t now = time(NULL); int need_uptime = smartlist_string_num_isin(get_options()->LongLivedPorts, @@ -489,14 +491,14 @@ circuit_stream_is_being_handled(edge_connection_t *conn, if (build_state->is_internal || build_state->onehop_tunnel) continue; - exitrouter = build_state_get_exit_router(build_state); - if (exitrouter && (!need_uptime || build_state->need_uptime)) { + exitnode = build_state_get_exit_node(build_state); + if (exitnode && (!need_uptime || build_state->need_uptime)) { int ok; if (conn) { - ok = connection_ap_can_use_exit(conn, exitrouter, 0); + ok = connection_ap_can_use_exit(conn, exitnode, 0); } else { - addr_policy_result_t r = compare_addr_to_addr_policy( - 0, port, exitrouter->exit_policy); + addr_policy_result_t r; + r = compare_addr_to_node_policy(0, port, exitnode); ok = r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED; } if (ok) { @@ -563,7 +565,7 @@ circuit_predict_and_launch_new(void) log_info(LD_CIRC, "Have %d clean circs (%d internal), need another exit circ.", num, num_internal); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } @@ -575,7 +577,7 @@ circuit_predict_and_launch_new(void) "Have %d clean circs (%d internal), need another internal " "circ for my hidden service.", num, num_internal); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } @@ -593,7 +595,7 @@ circuit_predict_and_launch_new(void) "Have %d clean circs (%d uptime-internal, %d internal), need" " another hidden service circ.", num, num_uptime_internal, num_internal); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } @@ -606,7 +608,7 @@ circuit_predict_and_launch_new(void) flags = CIRCLAUNCH_NEED_CAPACITY; log_info(LD_CIRC, "Have %d clean circs need another buildtime test circ.", num); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } } @@ -642,9 +644,9 @@ circuit_build_needed_circs(time_t now) circ = circuit_get_youngest_clean_open(CIRCUIT_PURPOSE_C_GENERAL); if (get_options()->RunTesting && circ && - circ->timestamp_created + TESTING_CIRCUIT_INTERVAL < now) { + circ->timestamp_created.tv_sec + TESTING_CIRCUIT_INTERVAL < now) { log_fn(LOG_INFO,"Creating a new testing circuit."); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, 0); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, 0); } #endif } @@ -753,7 +755,7 @@ circuit_expire_old_circuits_clientside(time_t now) circ->purpose); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } else if (!circ->timestamp_dirty && circ->state == CIRCUIT_STATE_OPEN) { - if (circ->timestamp_created < cutoff) { + if (circ->timestamp_created.tv_sec < cutoff) { if (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL || circ->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT || circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -763,7 +765,7 @@ circuit_expire_old_circuits_clientside(time_t now) circ->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) { log_debug(LD_CIRC, "Closing circuit that has been unused for %ld seconds.", - (long)(now - circ->timestamp_created)); + (long)(now - circ->timestamp_created.tv_sec)); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } else if (!TO_ORIGIN_CIRCUIT(circ)->is_ancient) { /* Server-side rend joined circuits can end up really old, because @@ -777,7 +779,7 @@ circuit_expire_old_circuits_clientside(time_t now) "Ancient non-dirty circuit %d is still around after " "%ld seconds. Purpose: %d", TO_ORIGIN_CIRCUIT(circ)->global_identifier, - (long)(now - circ->timestamp_created), + (long)(now - circ->timestamp_created.tv_sec), circ->purpose); /* FFFF implement a new circuit_purpose_to_string() so we don't * just print out a number for circ->purpose */ @@ -1076,17 +1078,9 @@ static int did_circs_fail_last_period = 0; /** Launch a new circuit; see circuit_launch_by_extend_info() for * details on arguments. */ origin_circuit_t * -circuit_launch_by_router(uint8_t purpose, - routerinfo_t *exit, int flags) +circuit_launch(uint8_t purpose, int flags) { - origin_circuit_t *circ; - extend_info_t *info = NULL; - if (exit) - info = extend_info_from_router(exit); - circ = circuit_launch_by_extend_info(purpose, info, flags); - - extend_info_free(info); - return circ; + return circuit_launch_by_extend_info(purpose, NULL, flags); } /** Launch a new circuit with purpose <b>purpose</b> and exit node @@ -1123,7 +1117,7 @@ circuit_launch_by_extend_info(uint8_t purpose, /* reset the birth date of this circ, else expire_building * will see it and think it's been trying to build since it * began. */ - circ->_base.timestamp_created = time(NULL); + tor_gettimeofday(&circ->_base.timestamp_created); switch (purpose) { case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: @@ -1256,9 +1250,9 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, uint32_t addr = 0; if (tor_inet_aton(conn->socks_request->address, &in)) addr = ntohl(in.s_addr); - if (router_exit_policy_all_routers_reject(addr, - conn->socks_request->port, - need_uptime)) { + if (router_exit_policy_all_nodes_reject(addr, + conn->socks_request->port, + need_uptime)) { log_notice(LD_APP, "No Tor server allows exit to %s:%d. Rejecting.", safe_str_client(conn->socks_request->address), @@ -1267,9 +1261,9 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, } } else { /* XXXX022 Duplicates checks in connection_ap_handshake_attach_circuit */ - routerinfo_t *router = router_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); int opt = conn->chosen_exit_optional; - if (router && !connection_ap_can_use_exit(conn, router, 0)) { + if (node && !connection_ap_can_use_exit(conn, node, 0)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, "Requested exit point '%s' would refuse request. %s.", conn->chosen_exit_name, opt ? "Trying others" : "Closing"); @@ -1317,11 +1311,11 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, */ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL) { if (conn->chosen_exit_name) { - routerinfo_t *r; + const node_t *r; int opt = conn->chosen_exit_optional; - r = router_get_by_nickname(conn->chosen_exit_name, 1); - if (r) { - extend_info = extend_info_from_router(r); + r = node_get_by_nickname(conn->chosen_exit_name, 1); + if (r && node_has_descriptor(r)) { + extend_info = extend_info_from_node(r); } else { log_debug(LD_DIR, "considering %d, %s", want_onehop, conn->chosen_exit_name); @@ -1571,9 +1565,9 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn) origin_circuit_t *circ=NULL; if (conn->chosen_exit_name) { - routerinfo_t *router = router_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); int opt = conn->chosen_exit_optional; - if (!router && !want_onehop) { + if (!node && !want_onehop) { /* We ran into this warning when trying to extend a circuit to a * hidden service directory for which we didn't have a router * descriptor. See flyspray task 767 for more details. We should @@ -1589,7 +1583,7 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn) } return -1; } - if (router && !connection_ap_can_use_exit(conn, router, 0)) { + if (node && !connection_ap_can_use_exit(conn, node, 0)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, "Requested exit point '%s' would refuse request. %s.", conn->chosen_exit_name, opt ? "Trying others" : "Closing"); diff --git a/src/or/circuituse.h b/src/or/circuituse.h index 1a604b415f..9f0e9d575f 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -41,8 +41,7 @@ void circuit_build_failed(origin_circuit_t *circ); origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose, extend_info_t *info, int flags); -origin_circuit_t *circuit_launch_by_router(uint8_t purpose, - routerinfo_t *exit, int flags); +origin_circuit_t *circuit_launch(uint8_t purpose, int flags); void circuit_reset_failure_count(int timeout); int connection_ap_handshake_attach_chosen_circuit(edge_connection_t *conn, origin_circuit_t *circ, diff --git a/src/or/command.c b/src/or/command.c index 00d9af33fa..4b70deeef4 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -25,6 +25,7 @@ #include "control.h" #include "cpuworker.h" #include "hibernate.h" +#include "nodelist.h" #include "onion.h" #include "relay.h" #include "router.h" @@ -267,15 +268,18 @@ command_process_create_cell(cell_t *cell, or_connection_t *conn) } if (circuit_id_in_use_on_orconn(cell->circ_id, conn)) { - routerinfo_t *router = router_get_by_digest(conn->identity_digest); + const node_t *node = node_get_by_id(conn->identity_digest); log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Received CREATE cell (circID %d) for known circ. " "Dropping (age %d).", cell->circ_id, (int)(time(NULL) - conn->_base.timestamp_created)); - if (router) + if (node) { + char *p = esc_for_log(node_get_platform(node)); log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Details: nickname \"%s\", platform %s.", - router->nickname, escaped(router->platform)); + node_get_nickname(node), p); + tor_free(p); + } return; } @@ -620,7 +624,7 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) /** Warn when we get a netinfo skew with at least this value. */ #define NETINFO_NOTICE_SKEW 3600 if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && - router_get_by_digest(conn->identity_digest)) { + router_get_by_id_digest(conn->identity_digest)) { char dbuf[64]; int severity; /*XXXX be smarter about when everybody says we are skewed. */ diff --git a/src/or/config.c b/src/or/config.c index a27fd22b24..00a01f5fb8 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -44,6 +44,8 @@ typedef enum config_type_t { CONFIG_TYPE_FILENAME, /**< A filename: some prefixes get expanded. */ CONFIG_TYPE_UINT, /**< A non-negative integer less than MAX_INT */ CONFIG_TYPE_INTERVAL, /**< A number of seconds, with optional units*/ + CONFIG_TYPE_MSEC_INTERVAL,/**< A number of milliseconds, with optional + * units */ CONFIG_TYPE_MEMUNIT, /**< A number of bytes, with optional units*/ CONFIG_TYPE_DOUBLE, /**< A floating-point value */ CONFIG_TYPE_BOOL, /**< A boolean value, expressed as 0 or 1. */ @@ -198,6 +200,7 @@ static config_var_t _option_vars[] = { V(ClientOnly, BOOL, "0"), V(ConsensusParams, STRING, NULL), V(ConnLimit, UINT, "1000"), + V(ConnDirectionStatistics, BOOL, "0"), V(ConstrainedSockets, BOOL, "0"), V(ConstrainedSockSize, MEMUNIT, "8192"), V(ContactInfo, STRING, NULL), @@ -221,9 +224,10 @@ static config_var_t _option_vars[] = { OBSOLETE("DirRecordUsageGranularity"), OBSOLETE("DirRecordUsageRetainIPs"), OBSOLETE("DirRecordUsageSaveInterval"), - V(DirReqStatistics, BOOL, "0"), + V(DirReqStatistics, BOOL, "1"), VAR("DirServer", LINELIST, DirServers, NULL), V(DisableAllSwap, BOOL, "0"), + V(DisableIOCP, BOOL, "1"), V(DNSPort, UINT, "0"), V(DNSListenAddress, LINELIST, NULL), V(DownloadExtraInfo, BOOL, "0"), @@ -238,7 +242,7 @@ static config_var_t _option_vars[] = { V(ExitPolicy, LINELIST, NULL), V(ExitPolicyRejectPrivate, BOOL, "1"), V(ExitPortStatistics, BOOL, "0"), - V(ExtraInfoStatistics, BOOL, "0"), + V(ExtraInfoStatistics, BOOL, "1"), #if defined (WINCE) V(FallbackNetworkstatusFile, FILENAME, "fallback-consensus"), @@ -290,6 +294,7 @@ static config_var_t _option_vars[] = { OBSOLETE("LinkPadding"), OBSOLETE("LogLevel"), OBSOLETE("LogFile"), + V(LogTimeGranularity, MSEC_INTERVAL, "1 second"), V(LongLivedPorts, CSV, "21,22,706,1863,5050,5190,5222,5223,6667,6697,8300"), VAR("MapAddress", LINELIST, AddressMap, NULL), @@ -306,7 +311,7 @@ static config_var_t _option_vars[] = { V(WarnUnsafeSocks, BOOL, "1"), OBSOLETE("NoPublish"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), - V(NumCPUs, UINT, "1"), + V(NumCPUs, UINT, "0"), V(NumEntryGuards, UINT, "3"), V(ORListenAddress, LINELIST, NULL), V(ORPort, UINT, "0"), @@ -316,6 +321,8 @@ static config_var_t _option_vars[] = { V(PerConnBWRate, MEMUNIT, "0"), V(PidFile, STRING, NULL), V(TestingTorNetwork, BOOL, "0"), + V(PortForwarding, BOOL, "0"), + V(PortForwardingHelper, FILENAME, "tor-fw-helper"), V(PreferTunneledDirConns, BOOL, "1"), V(ProtocolWarnings, BOOL, "0"), V(PublishServerDescriptor, CSV, "1"), @@ -384,6 +391,7 @@ static config_var_t _option_vars[] = { VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"), V(VirtualAddrNetwork, STRING, "127.192.0.0/10"), V(WarnPlaintextPorts, CSV, "23,109,110,143"), + V(_UseFilteringSSLBufferevents, BOOL, "0"), VAR("__ReloadTorrcOnSIGHUP", BOOL, ReloadTorrcOnSIGHUP, "1"), VAR("__AllDirActionsPrivate", BOOL, AllDirActionsPrivate, "0"), VAR("__DisablePredictedCircuits",BOOL,DisablePredictedCircuits, "0"), @@ -555,8 +563,9 @@ static int is_listening_on_low_port(uint16_t port_option, const config_line_t *listen_options); static uint64_t config_parse_memunit(const char *s, int *ok); +static int config_parse_msec_interval(const char *s, int *ok); static int config_parse_interval(const char *s, int *ok); -static void init_libevent(void); +static void init_libevent(const or_options_t *options); static int opt_streq(const char *s1, const char *s2); /** Magic value for or_options_t. */ @@ -690,6 +699,11 @@ or_options_free(or_options_t *options) return; routerset_free(options->_ExcludeExitNodesUnion); + if (options->NodeFamilySets) { + SMARTLIST_FOREACH(options->NodeFamilySets, routerset_t *, + rs, routerset_free(rs)); + smartlist_free(options->NodeFamilySets); + } config_free(&options_format, options); } @@ -957,7 +971,7 @@ options_act_reversible(or_options_t *old_options, char **msg) /* Set up libevent. (We need to do this before we can register the * listeners as listeners.) */ if (running_tor && !libevent_initialized) { - init_libevent(); + init_libevent(options); libevent_initialized = 1; } @@ -1233,6 +1247,17 @@ options_act(or_options_t *old_options) if (accounting_is_enabled(options)) configure_accounting(time(NULL)); +#ifdef USE_BUFFEREVENTS + /* If we're using the bufferevents implementation and our rate limits + * changed, we need to tell the rate-limiting system about it. */ + if (!old_options || + old_options->BandwidthRate != options->BandwidthRate || + old_options->BandwidthBurst != options->BandwidthBurst || + old_options->RelayBandwidthRate != options->RelayBandwidthRate || + old_options->RelayBandwidthBurst != options->RelayBandwidthBurst) + connection_bucket_init(); +#endif + /* parse RefuseUnknownExits tristate */ if (!strcmp(options->RefuseUnknownExits, "0")) options->RefuseUnknownExits_ = 0; @@ -1344,44 +1369,52 @@ options_act(or_options_t *old_options) tor_free(actual_fname); } - if (options->DirReqStatistics && !geoip_is_loaded()) { - /* Check if GeoIP database could be loaded. */ - log_warn(LD_CONFIG, "Configured to measure directory request " - "statistics, but no GeoIP database found!"); - return -1; - } - - if (options->EntryStatistics) { - if (should_record_bridge_info(options)) { - /* Don't allow measuring statistics on entry guards when configured - * as bridge. */ - log_warn(LD_CONFIG, "Bridges cannot be configured to measure " - "additional GeoIP statistics as entry guards."); - return -1; - } else if (!geoip_is_loaded()) { - /* Check if GeoIP database could be loaded. */ - log_warn(LD_CONFIG, "Configured to measure entry node statistics, " - "but no GeoIP database found!"); - return -1; - } - } - if (options->CellStatistics || options->DirReqStatistics || - options->EntryStatistics || options->ExitPortStatistics) { + options->EntryStatistics || options->ExitPortStatistics || + options->ConnDirectionStatistics) { time_t now = time(NULL); + int print_notice = 0; if ((!old_options || !old_options->CellStatistics) && - options->CellStatistics) + options->CellStatistics) { rep_hist_buffer_stats_init(now); + print_notice = 1; + } if ((!old_options || !old_options->DirReqStatistics) && - options->DirReqStatistics) - geoip_dirreq_stats_init(now); + options->DirReqStatistics) { + if (geoip_is_loaded()) { + geoip_dirreq_stats_init(now); + print_notice = 1; + } else { + options->DirReqStatistics = 0; + log_notice(LD_CONFIG, "Configured to measure directory request " + "statistics, but no GeoIP database found! " + "Please specify a GeoIP database using the " + "GeoIPFile option!"); + } + } if ((!old_options || !old_options->EntryStatistics) && - options->EntryStatistics) - geoip_entry_stats_init(now); + options->EntryStatistics && !should_record_bridge_info(options)) { + if (geoip_is_loaded()) { + geoip_entry_stats_init(now); + print_notice = 1; + } else { + options->EntryStatistics = 0; + log_notice(LD_CONFIG, "Configured to measure entry node " + "statistics, but no GeoIP database found! " + "Please specify a GeoIP database using the " + "GeoIPFile option!"); + } + } if ((!old_options || !old_options->ExitPortStatistics) && - options->ExitPortStatistics) + options->ExitPortStatistics) { rep_hist_exit_stats_init(now); - if (!old_options) + print_notice = 1; + } + if ((!old_options || !old_options->ConnDirectionStatistics) && + options->ConnDirectionStatistics) { + rep_hist_conn_stats_init(now); + } + if (print_notice) log_notice(LD_CONFIG, "Configured to measure statistics. Look for " "the *-stats files that will first be written to the " "data directory in 24 hours from now."); @@ -1399,6 +1432,9 @@ options_act(or_options_t *old_options) if (old_options && old_options->ExitPortStatistics && !options->ExitPortStatistics) rep_hist_exit_stats_term(); + if (old_options && old_options->ConnDirectionStatistics && + !options->ConnDirectionStatistics) + rep_hist_conn_stats_term(); /* Check if we need to parse and add the EntryNodes config option. */ if (options->EntryNodes && @@ -1704,6 +1740,18 @@ config_assign_value(config_format_t *fmt, or_options_t *options, break; } + case CONFIG_TYPE_MSEC_INTERVAL: { + i = config_parse_msec_interval(c->value, &ok); + if (!ok) { + tor_asprintf(msg, + "Msec interval '%s %s' is malformed or out of bounds.", + c->key, c->value); + return -1; + } + *(int *)lvalue = i; + break; + } + case CONFIG_TYPE_MEMUNIT: { uint64_t u64 = config_parse_memunit(c->value, &ok); if (!ok) { @@ -1991,6 +2039,7 @@ get_assigned_option(config_format_t *fmt, void *options, escape_val = 0; /* Can't need escape. */ break; case CONFIG_TYPE_INTERVAL: + case CONFIG_TYPE_MSEC_INTERVAL: case CONFIG_TYPE_UINT: /* This means every or_options_t uint or bool element * needs to be an int. Not, say, a uint16_t or char. */ @@ -2218,6 +2267,7 @@ option_clear(config_format_t *fmt, or_options_t *options, config_var_t *var) *(time_t*)lvalue = 0; break; case CONFIG_TYPE_INTERVAL: + case CONFIG_TYPE_MSEC_INTERVAL: case CONFIG_TYPE_UINT: case CONFIG_TYPE_BOOL: *(int*)lvalue = 0; @@ -2324,7 +2374,7 @@ resolve_my_address(int warn_severity, or_options_t *options, int explicit_ip=1; int explicit_hostname=1; int from_interface=0; - char tmpbuf[INET_NTOA_BUF_LEN]; + char *addr_string = NULL; const char *address = options->Address; int notice_severity = warn_severity <= LOG_NOTICE ? LOG_NOTICE : warn_severity; @@ -2366,48 +2416,43 @@ resolve_my_address(int warn_severity, or_options_t *options, return -1; } from_interface = 1; - in.s_addr = htonl(interface_ip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + addr = interface_ip; log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for " - "local interface. Using that.", tmpbuf); + "local interface. Using that.", fmt_addr32(addr)); strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname)); } else { /* resolved hostname into addr */ - in.s_addr = htonl(addr); - if (!explicit_hostname && - is_internal_IP(ntohl(in.s_addr), 0)) { + is_internal_IP(addr, 0)) { uint32_t interface_ip; - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); log_fn(notice_severity, LD_CONFIG, "Guessed local hostname '%s' " - "resolves to a private IP address (%s). Trying something " - "else.", hostname, tmpbuf); + "resolves to a private IP address (%s). Trying something " + "else.", hostname, fmt_addr32(addr)); if (get_interface_address(warn_severity, &interface_ip)) { log_fn(warn_severity, LD_CONFIG, "Could not get local interface IP address. Too bad."); } else if (is_internal_IP(interface_ip, 0)) { - struct in_addr in2; - in2.s_addr = htonl(interface_ip); - tor_inet_ntoa(&in2,tmpbuf,sizeof(tmpbuf)); log_fn(notice_severity, LD_CONFIG, "Interface IP address '%s' is a private address too. " - "Ignoring.", tmpbuf); + "Ignoring.", fmt_addr32(interface_ip)); } else { from_interface = 1; - in.s_addr = htonl(interface_ip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + addr = interface_ip; log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for local interface." - " Using that.", tmpbuf); + " Using that.", fmt_addr32(addr)); strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname)); } } } + } else { + addr = ntohl(in.s_addr); /* set addr so that addr_string is not + * illformed */ } - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); - if (is_internal_IP(ntohl(in.s_addr), 0)) { + addr_string = tor_dup_ip(addr); + if (is_internal_IP(addr, 0)) { /* make sure we're ok with publishing an internal IP */ if (!options->DirServers && !options->AlternateDirAuthority) { /* if they are using the default dirservers, disallow internal IPs @@ -2415,7 +2460,8 @@ resolve_my_address(int warn_severity, or_options_t *options, log_fn(warn_severity, LD_CONFIG, "Address '%s' resolves to private IP address '%s'. " "Tor servers that use the default DirServers must have public " - "IP addresses.", hostname, tmpbuf); + "IP addresses.", hostname, addr_string); + tor_free(addr_string); return -1; } if (!explicit_ip) { @@ -2423,19 +2469,20 @@ resolve_my_address(int warn_severity, or_options_t *options, * they're using an internal address. */ log_fn(warn_severity, LD_CONFIG, "Address '%s' resolves to private " "IP address '%s'. Please set the Address config option to be " - "the IP address you want to use.", hostname, tmpbuf); + "the IP address you want to use.", hostname, addr_string); + tor_free(addr_string); return -1; } } - log_debug(LD_CONFIG, "Resolved Address to '%s'.", tmpbuf); - *addr_out = ntohl(in.s_addr); + log_debug(LD_CONFIG, "Resolved Address to '%s'.", fmt_addr32(addr)); + *addr_out = addr; if (last_resolved_addr && last_resolved_addr != *addr_out) { /* Leave this as a notice, regardless of the requested severity, * at least until dynamic IP address support becomes bulletproof. */ log_notice(LD_NET, "Your IP address seems to have changed to %s. Updating.", - tmpbuf); + addr_string); ip_address_changed(0); } if (last_resolved_addr != *addr_out) { @@ -2454,11 +2501,12 @@ resolve_my_address(int warn_severity, or_options_t *options, } control_event_server_status(LOG_NOTICE, "EXTERNAL_ADDRESS ADDRESS=%s METHOD=%s %s%s", - tmpbuf, method, h?"HOSTNAME=":"", h); + addr_string, method, h?"HOSTNAME=":"", h); } last_resolved_addr = *addr_out; if (hostname_out) *hostname_out = tor_strdup(hostname); + tor_free(addr_string); return 0; } @@ -3063,17 +3111,24 @@ options_validate(or_options_t *old_options, or_options_t *options, routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeNodes); } + if (options->NodeFamilies) { + options->NodeFamilySets = smartlist_create(); + for (cl = options->NodeFamilies; cl; cl = cl->next) { + routerset_t *rs = routerset_new(); + if (routerset_parse(rs, cl->value, cl->key) == 0) { + smartlist_add(options->NodeFamilySets, rs); + } else { + routerset_free(rs); + } + } + } + if (options->ExcludeNodes && options->StrictNodes) { COMPLAIN("You have asked to exclude certain relays from all positions " "in your circuits. Expect hidden services and other Tor " "features to be broken in unpredictable ways."); } - if (options->EntryNodes && !routerset_is_list(options->EntryNodes)) { - /* XXXX fix this; see entry_guards_prepend_from_config(). */ - REJECT("IPs or countries are not yet supported in EntryNodes."); - } - if (options->AuthoritativeDir) { if (!options->ContactInfo && !options->TestingTorNetwork) REJECT("Authoritative directory servers must set ContactInfo"); @@ -3534,8 +3589,12 @@ options_validate(or_options_t *old_options, or_options_t *options, if (check_nickname_list(options->MyFamily, "MyFamily", msg)) return -1; for (cl = options->NodeFamilies; cl; cl = cl->next) { - if (check_nickname_list(cl->value, "NodeFamily", msg)) + routerset_t *rs = routerset_new(); + if (routerset_parse(rs, cl->value, cl->key)) { + routerset_free(rs); return -1; + } + routerset_free(rs); } if (validate_addr_policies(options, msg) < 0) @@ -4315,6 +4374,35 @@ options_init_logs(or_options_t *options, int validate_only) options->RunAsDaemon; #endif + if (options->LogTimeGranularity <= 0) { + log_warn(LD_CONFIG, "Log time granularity '%d' has to be positive.", + options->LogTimeGranularity); + return -1; + } else if (1000 % options->LogTimeGranularity != 0 && + options->LogTimeGranularity % 1000 != 0) { + int granularity = options->LogTimeGranularity; + if (granularity < 40) { + do granularity++; + while (1000 % granularity != 0); + } else if (granularity < 1000) { + granularity = 1000 / granularity; + while (1000 % granularity != 0) + granularity--; + granularity = 1000 / granularity; + } else { + granularity = 1000 * ((granularity / 1000) + 1); + } + log_warn(LD_CONFIG, "Log time granularity '%d' has to be either a " + "divisor or a multiple of 1 second. Changing to " + "'%d'.", + options->LogTimeGranularity, granularity); + if (!validate_only) + set_log_time_granularity(granularity); + } else { + if (!validate_only) + set_log_time_granularity(options->LogTimeGranularity); + } + ok = 1; elts = smartlist_create(); @@ -4803,6 +4891,26 @@ static struct unit_table_t time_units[] = { { NULL, 0 }, }; +/** Table to map the names of time units to the number of milliseconds + * they contain. */ +static struct unit_table_t time_msec_units[] = { + { "", 1 }, + { "msec", 1 }, + { "millisecond", 1 }, + { "milliseconds", 1 }, + { "second", 1000 }, + { "seconds", 1000 }, + { "minute", 60*1000 }, + { "minutes", 60*1000 }, + { "hour", 60*60*1000 }, + { "hours", 60*60*1000 }, + { "day", 24*60*60*1000 }, + { "days", 24*60*60*1000 }, + { "week", 7*24*60*60*1000 }, + { "weeks", 7*24*60*60*1000 }, + { NULL, 0 }, +}; + /** Parse a string <b>val</b> containing a number, zero or more * spaces, and an optional unit string. If the unit appears in the * table <b>u</b>, then multiply the number by the unit multiplier. @@ -4866,6 +4974,25 @@ config_parse_memunit(const char *s, int *ok) return u; } +/** Parse a string in the format "number unit", where unit is a unit of + * time in milliseconds. On success, set *<b>ok</b> to true and return + * the number of milliseconds in the provided interval. Otherwise, set + * *<b>ok</b> to 0 and return -1. */ +static int +config_parse_msec_interval(const char *s, int *ok) +{ + uint64_t r; + r = config_parse_units(s, time_msec_units, ok); + if (!ok) + return -1; + if (r > INT_MAX) { + log_warn(LD_CONFIG, "Msec interval '%s' is too long", s); + *ok = 0; + return -1; + } + return (int)r; +} + /** Parse a string in the format "number unit", where unit is a unit of time. * On success, set *<b>ok</b> to true and return the number of seconds in * the provided interval. Otherwise, set *<b>ok</b> to 0 and return -1. @@ -4885,13 +5012,29 @@ config_parse_interval(const char *s, int *ok) return (int)r; } +/** Return the number of cpus configured in <b>options</b>. If we are + * told to auto-detect the number of cpus, return the auto-detected number. */ +int +get_num_cpus(const or_options_t *options) +{ + if (options->NumCPUs == 0) { + int n = compute_num_cpus(); + return (n >= 1) ? n : 1; + } else { + return options->NumCPUs; + } +} + /** * Initialize the libevent library. */ static void -init_libevent(void) +init_libevent(const or_options_t *options) { const char *badness=NULL; + tor_libevent_cfg cfg; + + tor_assert(options); configure_libevent_logging(); /* If the kernel complains that some method (say, epoll) doesn't @@ -4901,7 +5044,11 @@ init_libevent(void) tor_check_libevent_header_compatibility(); - tor_libevent_initialize(); + memset(&cfg, 0, sizeof(cfg)); + cfg.disable_iocp = options->DisableIOCP; + cfg.num_cpus = get_num_cpus(options); + + tor_libevent_initialize(&cfg); suppress_libevent_log_msg(NULL); @@ -5242,6 +5389,7 @@ getinfo_helper_config(control_connection_t *conn, case CONFIG_TYPE_FILENAME: type = "Filename"; break; case CONFIG_TYPE_UINT: type = "Integer"; break; case CONFIG_TYPE_INTERVAL: type = "TimeInterval"; break; + case CONFIG_TYPE_MSEC_INTERVAL: type = "TimeMsecInterval"; break; case CONFIG_TYPE_MEMUNIT: type = "DataSize"; break; case CONFIG_TYPE_DOUBLE: type = "Float"; break; case CONFIG_TYPE_BOOL: type = "Boolean"; break; diff --git a/src/or/config.h b/src/or/config.h index defda35e0b..7f0a3d10da 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -57,6 +57,8 @@ char *options_get_datadir_fname2_suffix(or_options_t *options, #define get_datadir_fname_suffix(sub1, suffix) \ get_datadir_fname2_suffix((sub1), NULL, (suffix)) +int get_num_cpus(const or_options_t *options); + or_state_t *get_or_state(void); int or_state_save(time_t now); diff --git a/src/or/connection.c b/src/or/connection.c index 8a21d81c58..6f2eeb278f 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -36,6 +36,10 @@ #include "router.h" #include "routerparse.h" +#ifdef USE_BUFFEREVENTS +#include <event2/event.h> +#endif + static connection_t *connection_create_listener( struct sockaddr *listensockaddr, socklen_t listensocklen, int type, @@ -45,8 +49,10 @@ static void connection_init(time_t now, connection_t *conn, int type, static int connection_init_accepted_conn(connection_t *conn, uint8_t listener_type); static int connection_handle_listener_read(connection_t *conn, int new_type); +#ifndef USE_BUFFEREVENTS static int connection_bucket_should_increase(int bucket, or_connection_t *conn); +#endif static int connection_finished_flushing(connection_t *conn); static int connection_flushed_some(connection_t *conn); static int connection_finished_connecting(connection_t *conn); @@ -183,6 +189,26 @@ conn_state_to_string(int type, int state) return buf; } +#ifdef USE_BUFFEREVENTS +/** Return true iff the connection's type is one that can use a + bufferevent-based implementation. */ +int +connection_type_uses_bufferevent(connection_t *conn) +{ + switch (conn->type) { + case CONN_TYPE_AP: + case CONN_TYPE_EXIT: + case CONN_TYPE_DIR: + case CONN_TYPE_CONTROL: + case CONN_TYPE_OR: + case CONN_TYPE_CPUWORKER: + return 1; + default: + return 0; + } +} +#endif + /** Allocate and return a new dir_connection_t, initialized as by * connection_init(). */ dir_connection_t * @@ -308,10 +334,13 @@ connection_init(time_t now, connection_t *conn, int type, int socket_family) conn->type = type; conn->socket_family = socket_family; - if (!connection_is_listener(conn)) { /* listeners never use their buf */ +#ifndef USE_BUFFEREVENTS + if (!connection_is_listener(conn)) { + /* listeners never use their buf */ conn->inbuf = buf_new(); conn->outbuf = buf_new(); } +#endif conn->timestamp_created = now; conn->timestamp_lastread = now; @@ -377,7 +406,8 @@ _connection_free(connection_t *conn) "bytes on inbuf, %d on outbuf.", conn_type_to_string(conn->type), conn_state_to_string(conn->type, conn->state), - (int)buf_datalen(conn->inbuf), (int)buf_datalen(conn->outbuf)); + (int)connection_get_inbuf_len(conn), + (int)connection_get_outbuf_len(conn)); } if (!connection_is_listener(conn)) { @@ -424,6 +454,15 @@ _connection_free(connection_t *conn) tor_free(conn->read_event); /* Probably already freed by connection_free. */ tor_free(conn->write_event); /* Probably already freed by connection_free. */ + IF_HAS_BUFFEREVENT(conn, { + /* This was a workaround to handle bugs in some old versions of libevent + * where callbacks can occur after calling bufferevent_free(). Setting + * the callbacks to NULL prevented this. It shouldn't be necessary any + * more, but let's not tempt fate for now. */ + bufferevent_setcb(conn->bufev, NULL, NULL, NULL, NULL); + bufferevent_free(conn->bufev); + conn->bufev = NULL; + }); if (conn->type == CONN_TYPE_DIR) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); @@ -450,6 +489,11 @@ _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)); } +#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); + } +#endif memset(mem, 0xCC, memlen); /* poison memory */ tor_free(mem); @@ -675,10 +719,9 @@ connection_close_immediate(connection_t *conn) conn->s = -1; if (conn->linked) conn->linked_conn_is_closed = 1; - if (!connection_is_listener(conn)) { + if (conn->outbuf) buf_clear(conn->outbuf); - conn->outbuf_flushlen = 0; - } + conn->outbuf_flushlen = 0; } /** Mark <b>conn</b> to be closed next time we loop through @@ -1238,7 +1281,7 @@ connection_connect(connection_t *conn, const char *address, int s, inprogress = 0; char addrbuf[256]; struct sockaddr *dest_addr = (struct sockaddr*) addrbuf; - socklen_t dest_addr_len; + int dest_addr_len; or_options_t *options = get_options(); int protocol_family; @@ -1294,7 +1337,7 @@ connection_connect(connection_t *conn, const char *address, log_debug(LD_NET, "Connecting to %s:%u.", escaped_safe_str_client(address), port); - if (connect(s, dest_addr, dest_addr_len) < 0) { + if (connect(s, dest_addr, (socklen_t)dest_addr_len) < 0) { int e = tor_socket_errno(s); if (!ERRNO_IS_CONN_EINPROGRESS(e)) { /* yuck. kill it. */ @@ -1319,7 +1362,7 @@ connection_connect(connection_t *conn, const char *address, escaped_safe_str_client(address), port, inprogress?"in progress":"established", s); conn->s = s; - if (connection_add(conn) < 0) /* no space, forget it */ + if (connection_add_connecting(conn) < 0) /* no space, forget it */ return -1; return inprogress ? 0 : 1; } @@ -1542,6 +1585,19 @@ connection_send_socks5_connect(connection_t *conn) conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK; } +/** DOCDOC */ +static int +connection_fetch_from_buf_socks_client(connection_t *conn, + int state, char **reason) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_from_evbuffer_socks_client(input, state, reason); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf_socks_client(conn->inbuf, state, reason); + } +} + /** Call this from connection_*_process_inbuf() to advance the proxy * handshake. * @@ -1569,17 +1625,17 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS4_WANT_CONNECT_OK: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); if (ret == 1) conn->proxy_state = PROXY_CONNECTED; break; case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); /* no auth needed, do connect */ if (ret == 1) { connection_send_socks5_connect(conn); @@ -1588,9 +1644,9 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); /* send auth if needed, otherwise do connect */ if (ret == 1) { @@ -1625,9 +1681,9 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); /* send the connect request */ if (ret == 1) { connection_send_socks5_connect(conn); @@ -1636,9 +1692,9 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS5_WANT_CONNECT_OK: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); if (ret == 1) conn->proxy_state = PROXY_CONNECTED; break; @@ -1902,6 +1958,9 @@ connection_is_rate_limited(connection_t *conn) return 1; } +#ifdef USE_BUFFEREVENTS +static struct bufferevent_rate_limit_group *global_rate_limit = NULL; +#else extern int global_read_bucket, global_write_bucket; extern int global_relayed_read_bucket, global_relayed_write_bucket; @@ -1909,11 +1968,13 @@ extern int global_relayed_read_bucket, global_relayed_write_bucket; * we are likely to run dry again this second, so be stingy with the * tokens we just put in. */ static int write_buckets_empty_last_second = 0; +#endif /** How many seconds of no active local circuits will make the * connection revert to the "relayed" bandwidth class? */ #define CLIENT_IDLE_TIME_FOR_PRIORITY 30 +#ifndef USE_BUFFEREVENTS /** Return 1 if <b>conn</b> should use tokens from the "relayed" * bandwidth rates, else 0. Currently, only OR conns with bandwidth * class 1, and directory conns that are serving data out, count. @@ -2024,6 +2085,20 @@ connection_bucket_write_limit(connection_t *conn, time_t now) return connection_bucket_round_robin(base, priority, global_bucket, conn_bucket); } +#else +static ssize_t +connection_bucket_read_limit(connection_t *conn, time_t now) +{ + (void) now; + return bufferevent_get_max_to_read(conn->bufev); +} +ssize_t +connection_bucket_write_limit(connection_t *conn, time_t now) +{ + (void) now; + return bufferevent_get_max_to_write(conn->bufev); +} +#endif /** Return 1 if the global write buckets are low enough that we * shouldn't send <b>attempt</b> bytes of low-priority directory stuff @@ -2048,8 +2123,12 @@ connection_bucket_write_limit(connection_t *conn, time_t now) int global_write_bucket_low(connection_t *conn, size_t attempt, int priority) { +#ifdef USE_BUFFEREVENTS + ssize_t smaller_bucket = bufferevent_get_max_to_write(conn->bufev); +#else int smaller_bucket = global_write_bucket < global_relayed_write_bucket ? global_write_bucket : global_relayed_write_bucket; +#endif if (authdir_mode(get_options()) && priority>1) return 0; /* there's always room to answer v2 if we're an auth dir */ @@ -2059,8 +2138,10 @@ global_write_bucket_low(connection_t *conn, size_t attempt, int priority) if (smaller_bucket < (int)attempt) return 1; /* not enough space no matter the priority */ +#ifndef USE_BUFFEREVENTS if (write_buckets_empty_last_second) return 1; /* we're already hitting our limits, no more please */ +#endif if (priority == 1) { /* old-style v1 query */ /* Could we handle *two* of these requests within the next two seconds? */ @@ -2076,6 +2157,7 @@ global_write_bucket_low(connection_t *conn, size_t attempt, int priority) return 0; } +#ifndef USE_BUFFEREVENTS /** We just read <b>num_read</b> and wrote <b>num_written</b> bytes * onto <b>conn</b>. Decrement buckets appropriately. */ static void @@ -2103,6 +2185,11 @@ connection_buckets_decrement(connection_t *conn, time_t now, if (!connection_is_rate_limited(conn)) return; /* local IPs are free */ + + if (conn->type == CONN_TYPE_OR) + rep_hist_note_or_conn_bytes(conn->global_identifier, num_read, + num_written, now); + if (num_read > 0) { rep_hist_note_bytes_read(num_read, now); } @@ -2319,6 +2406,88 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn) return 1; } +#else + +static void +connection_buckets_decrement(connection_t *conn, time_t now, + size_t num_read, size_t num_written) +{ + (void) conn; + (void) now; + (void) num_read; + (void) num_written; + /* Libevent does this for us. */ +} +void +connection_bucket_refill(int seconds_elapsed, time_t now) +{ + (void) seconds_elapsed; + (void) now; + /* Libevent does this for us. */ +} +void +connection_bucket_init(void) +{ + or_options_t *options = get_options(); + const struct timeval *tick = tor_libevent_get_one_tick_timeout(); + struct ev_token_bucket_cfg *bucket_cfg; + + uint64_t rate, burst; + if (options->RelayBandwidthRate) { + rate = options->RelayBandwidthRate; + burst = options->RelayBandwidthBurst; + } else { + rate = options->BandwidthRate; + burst = options->BandwidthBurst; + } + + rate /= TOR_LIBEVENT_TICKS_PER_SECOND; + bucket_cfg = ev_token_bucket_cfg_new((uint32_t)rate, (uint32_t)burst, + (uint32_t)rate, (uint32_t)burst, + tick); + + if (!global_rate_limit) { + global_rate_limit = + bufferevent_rate_limit_group_new(tor_libevent_get_base(), bucket_cfg); + } else { + bufferevent_rate_limit_group_set_cfg(global_rate_limit, bucket_cfg); + } + ev_token_bucket_cfg_free(bucket_cfg); +} + +void +connection_get_rate_limit_totals(uint64_t *read_out, uint64_t *written_out) +{ + if (global_rate_limit == NULL) { + *read_out = *written_out = 0; + } else { + bufferevent_rate_limit_group_get_totals( + global_rate_limit, read_out, written_out); + } +} + +/** DOCDOC */ +void +connection_enable_rate_limiting(connection_t *conn) +{ + if (conn->bufev) { + if (!global_rate_limit) + connection_bucket_init(); + bufferevent_add_to_rate_limit_group(conn->bufev, global_rate_limit); + } +} + +static void +connection_consider_empty_write_buckets(connection_t *conn) +{ + (void) conn; +} +static void +connection_consider_empty_read_buckets(connection_t *conn) +{ + (void) conn; +} +#endif /** Read bytes from conn-\>s and process them. * @@ -2572,13 +2741,13 @@ connection_read_to_buf(connection_t *conn, int *max_to_read, int *socket_error) } if (n_read > 0) { /* change *max_to_read */ - /*XXXX021 check for overflow*/ + /*XXXX022 check for overflow*/ *max_to_read = (int)(at_most - n_read); } if (conn->type == CONN_TYPE_AP) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); - /*XXXX021 check for overflow*/ + /*XXXX022 check for overflow*/ edge_conn->n_read += (int)n_read; } @@ -2600,11 +2769,202 @@ connection_read_to_buf(connection_t *conn, int *max_to_read, int *socket_error) return 0; } +#ifdef USE_BUFFEREVENTS +/* XXXX These generic versions could be simplified by making them + type-specific */ + +/** Callback: Invoked whenever bytes are added to or drained from an input + * evbuffer. Used to track the number of bytes read. */ +static void +evbuffer_inbuf_callback(struct evbuffer *buf, + const struct evbuffer_cb_info *info, void *arg) +{ + connection_t *conn = arg; + (void) buf; + /* XXXX These need to get real counts on the non-nested TLS case. - NM */ + if (info->n_added) { + time_t now = approx_time(); + conn->timestamp_lastread = now; + connection_buckets_decrement(conn, now, info->n_added, 0); + connection_consider_empty_read_buckets(conn); + if (conn->type == CONN_TYPE_AP) { + edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + /*XXXX022 check for overflow*/ + edge_conn->n_read += (int)info->n_added; + } + } +} + +/** Callback: Invoked whenever bytes are added to or drained from an output + * evbuffer. Used to track the number of bytes written. */ +static void +evbuffer_outbuf_callback(struct evbuffer *buf, + const struct evbuffer_cb_info *info, void *arg) +{ + connection_t *conn = arg; + (void)buf; + if (info->n_deleted) { + time_t now = approx_time(); + conn->timestamp_lastwritten = now; + connection_buckets_decrement(conn, now, 0, info->n_deleted); + connection_consider_empty_write_buckets(conn); + if (conn->type == CONN_TYPE_AP) { + edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + /*XXXX022 check for overflow*/ + edge_conn->n_written += (int)info->n_deleted; + } + } +} + +/** Callback: invoked whenever a bufferevent has read data. */ +void +connection_handle_read_cb(struct bufferevent *bufev, void *arg) +{ + connection_t *conn = arg; + (void) bufev; + if (!conn->marked_for_close) + if (connection_process_inbuf(conn, 1)<0) /* XXXX Always 1? */ + connection_mark_for_close(conn); +} + +/** Callback: invoked whenever a bufferevent has written data. */ +void +connection_handle_write_cb(struct bufferevent *bufev, void *arg) +{ + connection_t *conn = arg; + struct evbuffer *output; + if (connection_flushed_some(conn)<0) { + connection_mark_for_close(conn); + return; + } + + output = bufferevent_get_output(bufev); + if (!evbuffer_get_length(output)) { + connection_finished_flushing(conn); + if (conn->marked_for_close && conn->hold_open_until_flushed) { + conn->hold_open_until_flushed = 0; + if (conn->linked) { + /* send eof */ + bufferevent_flush(conn->bufev, EV_WRITE, BEV_FINISHED); + } + } + } +} + +/** Callback: invoked whenever a bufferevent has had an event (like a + * connection, or an eof, or an error) occur. */ +void +connection_handle_event_cb(struct bufferevent *bufev, short event, void *arg) +{ + connection_t *conn = arg; + (void) bufev; + if (event & BEV_EVENT_CONNECTED) { + tor_assert(connection_state_is_connecting(conn)); + if (connection_finished_connecting(conn)<0) + return; + } + if (event & BEV_EVENT_EOF) { + if (!conn->marked_for_close) { + conn->inbuf_reached_eof = 1; + if (connection_reached_eof(conn)<0) + return; + } + } + if (event & BEV_EVENT_ERROR) { + int socket_error = evutil_socket_geterror(conn->s); + if (conn->type == CONN_TYPE_OR && + conn->state == OR_CONN_STATE_CONNECTING) { + connection_or_connect_failed(TO_OR_CONN(conn), + errno_to_orconn_end_reason(socket_error), + tor_socket_strerror(socket_error)); + } else if (CONN_IS_EDGE(conn)) { + edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + if (!edge_conn->edge_has_sent_end) + connection_edge_end_errno(edge_conn); + if (edge_conn->socks_request) /* broken, don't send a socks reply back */ + edge_conn->socks_request->has_finished = 1; + } + connection_close_immediate(conn); /* Connection is dead. */ + if (!conn->marked_for_close) + connection_mark_for_close(conn); + } +} + +/** Set up the generic callbacks for the bufferevent on <b>conn</b>. */ +void +connection_configure_bufferevent_callbacks(connection_t *conn) +{ + struct bufferevent *bufev; + struct evbuffer *input, *output; + tor_assert(conn->bufev); + bufev = conn->bufev; + bufferevent_setcb(bufev, + connection_handle_read_cb, + connection_handle_write_cb, + connection_handle_event_cb, + conn); + + input = bufferevent_get_input(bufev); + output = bufferevent_get_output(bufev); + evbuffer_add_cb(input, evbuffer_inbuf_callback, conn); + evbuffer_add_cb(output, evbuffer_outbuf_callback, conn); +} +#endif + /** A pass-through to fetch_from_buf. */ int connection_fetch_from_buf(char *string, size_t len, connection_t *conn) { - return fetch_from_buf(string, len, conn->inbuf); + IF_HAS_BUFFEREVENT(conn, { + /* XXX overflow -seb */ + return (int)bufferevent_read(conn->bufev, string, len); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf(string, len, conn->inbuf); + } +} + +/** As fetch_from_buf_line(), but read from a connection's input buffer. */ +int +connection_fetch_from_buf_line(connection_t *conn, char *data, + size_t *data_len) +{ + IF_HAS_BUFFEREVENT(conn, { + int r; + size_t eol_len=0; + struct evbuffer *input = bufferevent_get_input(conn->bufev); + struct evbuffer_ptr ptr = + evbuffer_search_eol(input, NULL, &eol_len, EVBUFFER_EOL_LF); + if (ptr.pos == -1) + return 0; /* No EOL found. */ + if ((size_t)ptr.pos+eol_len >= *data_len) { + return -1; /* Too long */ + } + *data_len = ptr.pos+eol_len; + r = evbuffer_remove(input, data, ptr.pos+eol_len); + tor_assert(r >= 0); + data[ptr.pos+eol_len] = '\0'; + return 1; + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf_line(conn->inbuf, data, data_len); + } +} + +/** As fetch_from_buf_http, but fetches from a conncetion's input buffer_t or + * its bufferevent as appropriate. */ +int +connection_fetch_from_buf_http(connection_t *conn, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, + size_t max_bodylen, int force_complete) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_from_evbuffer_http(input, headers_out, max_headerlen, + body_out, body_used, max_bodylen, force_complete); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf_http(conn->inbuf, headers_out, max_headerlen, + body_out, body_used, max_bodylen, force_complete); + } } /** Return conn-\>outbuf_flushlen: how many bytes conn wants to flush @@ -2725,6 +3085,7 @@ connection_handle_write_impl(connection_t *conn, int force) /* If we just flushed the last bytes, check if this tunneled dir * request is done. */ + /* XXXX move this to flushed_some or finished_flushing -NM */ if (buf_datalen(conn->outbuf) == 0 && conn->dirreq_id) geoip_change_dirreq_state(conn->dirreq_id, DIRREQ_TUNNELED, DIRREQ_OR_CONN_BUFFER_FLUSHED); @@ -2780,7 +3141,7 @@ connection_handle_write_impl(connection_t *conn, int force) if (conn->type == CONN_TYPE_AP) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); - /*XXXX021 check for overflow.*/ + /*XXXX022 check for overflow.*/ edge_conn->n_written += (int)n_written; } @@ -2853,6 +3214,22 @@ _connection_write_to_buf_impl(const char *string, size_t len, if (conn->marked_for_close && !conn->hold_open_until_flushed) return; + IF_HAS_BUFFEREVENT(conn, { + if (zlib) { + int done = zlib < 0; + r = write_to_evbuffer_zlib(bufferevent_get_output(conn->bufev), + TO_DIR_CONN(conn)->zlib_state, + string, len, done); + } else { + r = bufferevent_write(conn->bufev, string, len); + } + if (r < 0) { + /* XXXX mark for close? */ + log_warn(LD_NET, "bufferevent_write failed! That shouldn't happen."); + } + return; + }); + old_datalen = buf_datalen(conn->outbuf); if (zlib) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); @@ -3025,6 +3402,32 @@ connection_get_by_type_state_rendquery(int type, int state, return NULL; } +/** Return a directory connection (if any one exists) that is fetching + * the item described by <b>state</b>/<b>resource</b> */ +dir_connection_t * +connection_dir_get_by_purpose_and_resource(int purpose, + const char *resource) +{ + smartlist_t *conns = get_connection_array(); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + dir_connection_t *dirconn; + if (conn->type != CONN_TYPE_DIR || conn->marked_for_close || + conn->purpose != purpose) + continue; + dirconn = TO_DIR_CONN(conn); + if (dirconn->requested_resource == NULL) { + if (resource == NULL) + return dirconn; + } else if (resource) { + if (0 == strcmp(resource, dirconn->requested_resource)) + return dirconn; + } + } SMARTLIST_FOREACH_END(conn); + + return NULL; +} + /** Return an open, non-marked connection of a given type and purpose, or NULL * if no such connection exists. */ connection_t * @@ -3264,6 +3667,9 @@ connection_finished_flushing(connection_t *conn) // log_fn(LOG_DEBUG,"entered. Socket %u.", conn->s); + IF_HAS_NO_BUFFEREVENT(conn) + connection_stop_writing(conn); + switch (conn->type) { case CONN_TYPE_OR: return connection_or_finished_flushing(TO_OR_CONN(conn)); @@ -3389,6 +3795,16 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(conn); tor_assert(conn->type >= _CONN_TYPE_MIN); tor_assert(conn->type <= _CONN_TYPE_MAX); + +#ifdef USE_BUFFEREVENTS + if (conn->bufev) { + tor_assert(conn->read_event == NULL); + tor_assert(conn->write_event == NULL); + tor_assert(conn->inbuf == NULL); + tor_assert(conn->outbuf == NULL); + } +#endif + switch (conn->type) { case CONN_TYPE_OR: tor_assert(conn->magic == OR_CONNECTION_MAGIC); @@ -3427,10 +3843,10 @@ assert_connection_ok(connection_t *conn, time_t now) * marked_for_close. */ /* buffers */ - if (!connection_is_listener(conn)) { + if (conn->inbuf) assert_buf_ok(conn->inbuf); + if (conn->outbuf) assert_buf_ok(conn->outbuf); - } if (conn->type == CONN_TYPE_OR) { or_connection_t *or_conn = TO_OR_CONN(conn); diff --git a/src/or/connection.h b/src/or/connection.h index 576d3a63e1..94ae64591f 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -12,6 +12,9 @@ #ifndef _TOR_CONNECTION_H #define _TOR_CONNECTION_H +/* XXXX For buf_datalen in inline function */ +#include "buffers.h" + const char *conn_type_to_string(int type); const char *conn_state_to_string(int type, int state); @@ -31,6 +34,21 @@ void _connection_mark_for_close(connection_t *conn,int line, const char *file); #define connection_mark_for_close(c) \ _connection_mark_for_close((c), __LINE__, _SHORT_FILE_) +/** + * Mark 'c' for close, but try to hold it open until all the data is written. + */ +#define _connection_mark_and_flush(c,line,file) \ + do { \ + connection_t *tmp_conn_ = (c); \ + _connection_mark_for_close(tmp_conn_, (line), (file)); \ + tmp_conn_->hold_open_until_flushed = 1; \ + IF_HAS_BUFFEREVENT(tmp_conn_, \ + connection_start_writing(tmp_conn_)); \ + } while (0) + +#define connection_mark_and_flush(c) \ + _connection_mark_and_flush((c), __LINE__, _SHORT_FILE_) + void connection_expire_held_open(void); int connection_connect(connection_t *conn, const char *address, @@ -51,6 +69,12 @@ void connection_bucket_refill(int seconds_elapsed, time_t now); int connection_handle_read(connection_t *conn); int connection_fetch_from_buf(char *string, size_t len, connection_t *conn); +int connection_fetch_from_buf_line(connection_t *conn, char *data, + size_t *data_len); +int connection_fetch_from_buf_http(connection_t *conn, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, + size_t max_bodylen, int force_complete); int connection_wants_to_flush(connection_t *conn); int connection_outbuf_too_full(connection_t *conn); @@ -73,6 +97,29 @@ connection_write_to_buf_zlib(const char *string, size_t len, _connection_write_to_buf_impl(string, len, TO_CONN(conn), done ? -1 : 1); } +static size_t connection_get_inbuf_len(connection_t *conn); +static size_t connection_get_outbuf_len(connection_t *conn); + +static INLINE size_t +connection_get_inbuf_len(connection_t *conn) +{ + IF_HAS_BUFFEREVENT(conn, { + return evbuffer_get_length(bufferevent_get_input(conn->bufev)); + }) ELSE_IF_NO_BUFFEREVENT { + return conn->inbuf ? buf_datalen(conn->inbuf) : 0; + } +} + +static INLINE size_t +connection_get_outbuf_len(connection_t *conn) +{ + IF_HAS_BUFFEREVENT(conn, { + return evbuffer_get_length(bufferevent_get_output(conn->bufev)); + }) ELSE_IF_NO_BUFFEREVENT { + return conn->outbuf ? buf_datalen(conn->outbuf) : 0; + } +} + connection_t *connection_get_by_global_id(uint64_t id); connection_t *connection_get_by_type(int type); @@ -83,6 +130,8 @@ connection_t *connection_get_by_type_addr_port_purpose(int type, connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); +dir_connection_t *connection_dir_get_by_purpose_and_resource( + int state, const char *resource); #define connection_speaks_cells(conn) ((conn)->type == CONN_TYPE_OR) int connection_is_listener(connection_t *conn); @@ -96,5 +145,19 @@ int connection_or_nonopen_was_started_here(or_connection_t *conn); void connection_dump_buffer_mem_stats(int severity); void remove_file_if_very_old(const char *fname, time_t now); +#ifdef USE_BUFFEREVENTS +int connection_type_uses_bufferevent(connection_t *conn); +void connection_configure_bufferevent_callbacks(connection_t *conn); +void connection_handle_read_cb(struct bufferevent *bufev, void *arg); +void connection_handle_write_cb(struct bufferevent *bufev, void *arg); +void connection_handle_event_cb(struct bufferevent *bufev, short event, + void *arg); +void connection_get_rate_limit_totals(uint64_t *read_out, + uint64_t *written_out); +void connection_enable_rate_limiting(connection_t *conn); +#else +#define connection_type_uses_bufferevent(c) (0) +#endif + #endif diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 73ed9fb5c3..c0b177d6e2 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -23,6 +23,7 @@ #include "dirserv.h" #include "hibernate.h" #include "main.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -90,8 +91,8 @@ _connection_mark_unattached_ap(edge_connection_t *conn, int endreason, conn->socks_request->has_finished = 1; } - _connection_mark_for_close(TO_CONN(conn), line, file); - conn->_base.hold_open_until_flushed = 1; + _connection_mark_and_flush(TO_CONN(conn), line, file); + conn->end_reason = endreason; } @@ -100,7 +101,7 @@ _connection_mark_unattached_ap(edge_connection_t *conn, int endreason, int connection_edge_reached_eof(edge_connection_t *conn) { - if (buf_datalen(conn->_base.inbuf) && + if (connection_get_inbuf_len(TO_CONN(conn)) && connection_state_is_open(TO_CONN(conn))) { /* it still has stuff to process. don't let it die yet. */ return 0; @@ -191,8 +192,7 @@ connection_edge_destroy(circid_t circ_id, edge_connection_t *conn) conn->edge_has_sent_end = 1; conn->end_reason = END_STREAM_REASON_DESTROY; conn->end_reason |= END_STREAM_REASON_FLAG_ALREADY_SENT_CLOSED; - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); } } conn->cpath_layer = NULL; @@ -319,7 +319,6 @@ connection_edge_finished_flushing(edge_connection_t *conn) switch (conn->_base.state) { case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: - connection_stop_writing(TO_CONN(conn)); connection_edge_consider_sending_sendme(conn); return 0; case AP_CONN_STATE_SOCKS_WAIT: @@ -328,7 +327,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) case AP_CONN_STATE_CIRCUIT_WAIT: case AP_CONN_STATE_CONNECT_WAIT: case AP_CONN_STATE_CONTROLLER_WAIT: - connection_stop_writing(TO_CONN(conn)); + case AP_CONN_STATE_RESOLVE_WAIT: return 0; default: log_warn(LD_BUG, "Called in unexpected state %d.",conn->_base.state); @@ -358,8 +357,9 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn) rep_hist_note_exit_stream_opened(conn->port); conn->state = EXIT_CONN_STATE_OPEN; - connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */ - if (connection_wants_to_flush(conn)) /* in case there are any queued relay + IF_HAS_NO_BUFFEREVENT(conn) + connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */ + if (connection_get_outbuf_len(conn)) /* in case there are any queued relay * cells */ connection_start_writing(conn); /* deliver a 'connected' relay cell back through the circuit. */ @@ -587,7 +587,7 @@ void circuit_discard_optional_exit_enclaves(extend_info_t *info) { edge_connection_t *edge_conn; - routerinfo_t *r1, *r2; + const node_t *r1, *r2; smartlist_t *conns = get_connection_array(); SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { @@ -599,8 +599,8 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) if (!edge_conn->chosen_exit_optional && !edge_conn->chosen_exit_retries) continue; - r1 = router_get_by_nickname(edge_conn->chosen_exit_name, 0); - r2 = router_get_by_digest(info->identity_digest); + r1 = node_get_by_nickname(edge_conn->chosen_exit_name, 0); + r2 = node_get_by_id(info->identity_digest); if (!r1 || !r2 || r1 != r2) continue; tor_assert(edge_conn->socks_request); @@ -1199,7 +1199,6 @@ static char * addressmap_get_virtual_address(int type) { char buf[64]; - struct in_addr in; tor_assert(addressmap); if (type == RESOLVED_TYPE_HOSTNAME) { @@ -1213,6 +1212,7 @@ addressmap_get_virtual_address(int type) } else if (type == RESOLVED_TYPE_IPV4) { // This is an imperfect estimate of how many addresses are available, but // that's ok. + struct in_addr in; uint32_t available = 1u << (32-virtual_addr_netmask_bits); while (available) { /* Don't hand out any .0 or .255 address. */ @@ -1605,12 +1605,12 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, return -1; } } else { - routerinfo_t *r; + const node_t *r; conn->chosen_exit_name = tor_strdup(socks->address); - r = router_get_by_nickname(conn->chosen_exit_name, 1); + r = node_get_by_nickname(conn->chosen_exit_name, 1); *socks->address = 0; if (r) { - strlcpy(socks->address, r->address, sizeof(socks->address)); + node_get_address_string(r, socks->address, sizeof(socks->address)); } else { log_warn(LD_APP, "Unrecognized server in exit address '%s.exit'. Refusing.", @@ -1662,16 +1662,16 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, if (!conn->use_begindir && !conn->chosen_exit_name && !circ) { /* see if we can find a suitable enclave exit */ - routerinfo_t *r = + const node_t *r = router_find_exact_exit_enclave(socks->address, socks->port); if (r) { log_info(LD_APP, "Redirecting address %s to exit at enclave router %s", - safe_str_client(socks->address), r->nickname); + safe_str_client(socks->address), node_get_nickname(r)); /* use the hex digest, not nickname, in case there are two routers with this nickname */ conn->chosen_exit_name = - tor_strdup(hex_str(r->cache_info.identity_digest, DIGEST_LEN)); + tor_strdup(hex_str(r->identity, DIGEST_LEN)); conn->chosen_exit_optional = 1; } } @@ -1786,10 +1786,10 @@ get_pf_socket(void) #ifdef OPENBSD /* only works on OpenBSD */ - pf = open("/dev/pf", O_RDONLY); + pf = tor_open_cloexec("/dev/pf", O_RDONLY, 0); #else /* works on NetBSD and FreeBSD */ - pf = open("/dev/pf", O_RDWR); + pf = tor_open_cloexec("/dev/pf", O_RDWR, 0); #endif if (pf < 0) { @@ -1926,8 +1926,14 @@ connection_ap_handshake_process_socks(edge_connection_t *conn) log_debug(LD_APP,"entered."); - sockshere = fetch_from_buf_socks(conn->_base.inbuf, socks, - options->TestSocks, options->SafeSocks); + IF_HAS_BUFFEREVENT(TO_CONN(conn), { + struct evbuffer *input = bufferevent_get_input(conn->_base.bufev); + sockshere = fetch_from_evbuffer_socks(input, socks, + options->TestSocks, options->SafeSocks); + }) ELSE_IF_NO_BUFFEREVENT { + sockshere = fetch_from_buf_socks(conn->_base.inbuf, socks, + options->TestSocks, options->SafeSocks); + }; if (sockshere == 0) { if (socks->replylen) { connection_write_to_buf(socks->reply, socks->replylen, TO_CONN(conn)); @@ -2028,7 +2034,7 @@ connection_ap_process_natd(edge_connection_t *conn) /* look for LF-terminated "[DEST ip_addr port]" * where ip_addr is a dotted-quad and port is in string form */ - err = fetch_from_buf_line(conn->_base.inbuf, tmp_buf, &tlen); + err = connection_fetch_from_buf_line(TO_CONN(conn), tmp_buf, &tlen); if (err == 0) return 0; if (err < 0) { @@ -2135,8 +2141,10 @@ connection_ap_handshake_send_begin(edge_connection_t *ap_conn) ap_conn->socks_request->port); payload_len = (int)strlen(payload)+1; - log_debug(LD_APP, - "Sending relay cell to begin stream %d.", ap_conn->stream_id); + log_info(LD_APP, + "Sending relay cell %d to begin stream %d.", + (int)ap_conn->use_begindir, + ap_conn->stream_id); begin_type = ap_conn->use_begindir ? RELAY_COMMAND_BEGIN_DIR : RELAY_COMMAND_BEGIN; @@ -2244,9 +2252,11 @@ connection_ap_handshake_send_resolve(edge_connection_t *ap_conn) * and call connection_ap_handshake_attach_circuit(conn) on it. * * Return the other end of the linked connection pair, or -1 if error. + * DOCDOC partner. */ edge_connection_t * -connection_ap_make_link(char *address, uint16_t port, +connection_ap_make_link(connection_t *partner, + char *address, uint16_t port, const char *digest, int use_begindir, int want_onehop) { edge_connection_t *conn; @@ -2281,6 +2291,8 @@ connection_ap_make_link(char *address, uint16_t port, tor_addr_make_unspec(&conn->_base.addr); conn->_base.port = 0; + connection_link_connections(partner, TO_CONN(conn)); + if (connection_add(TO_CONN(conn)) < 0) { /* no space, forget it */ connection_free(TO_CONN(conn)); return NULL; @@ -2317,13 +2329,11 @@ tell_controller_about_resolved_result(edge_connection_t *conn, answer_type == RESOLVED_TYPE_HOSTNAME)) { return; /* we already told the controller. */ } else if (answer_type == RESOLVED_TYPE_IPV4 && answer_len >= 4) { - struct in_addr in; - char buf[INET_NTOA_BUF_LEN]; - in.s_addr = get_uint32(answer); - tor_inet_ntoa(&in, buf, sizeof(buf)); + char *cp = tor_dup_ip(get_uint32(answer)); control_event_address_mapped(conn->socks_request->address, - buf, expires, NULL); - } else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len <256) { + cp, expires, NULL); + tor_free(cp); + } else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len < 256) { char *cp = tor_strndup(answer, answer_len); control_event_address_mapped(conn->socks_request->address, cp, expires, NULL); @@ -2802,12 +2812,13 @@ connection_exit_connect(edge_connection_t *edge_conn) } conn->state = EXIT_CONN_STATE_OPEN; - if (connection_wants_to_flush(conn)) { + if (connection_get_outbuf_len(conn)) { /* in case there are any queued data cells */ log_warn(LD_BUG,"newly connected conn had data waiting!"); // connection_start_writing(conn); } - connection_watch_events(conn, READ_EVENT); + IF_HAS_NO_BUFFEREVENT(conn) + connection_watch_events(conn, READ_EVENT); /* also, deliver a 'connected' cell back through the circuit. */ if (connection_edge_is_rendezvous_stream(edge_conn)) { @@ -2920,7 +2931,7 @@ connection_edge_is_rendezvous_stream(edge_connection_t *conn) * this relay, return 0. */ int -connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, +connection_ap_can_use_exit(edge_connection_t *conn, const node_t *exit, int excluded_means_no) { or_options_t *options = get_options(); @@ -2934,10 +2945,10 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, * make sure the exit node of the existing circuit matches exactly. */ if (conn->chosen_exit_name) { - routerinfo_t *chosen_exit = - router_get_by_nickname(conn->chosen_exit_name, 1); - if (!chosen_exit || memcmp(chosen_exit->cache_info.identity_digest, - exit->cache_info.identity_digest, DIGEST_LEN)) { + const node_t *chosen_exit = + node_get_by_nickname(conn->chosen_exit_name, 1); + if (!chosen_exit || memcmp(chosen_exit->identity, + exit->identity, DIGEST_LEN)) { /* doesn't match */ // log_debug(LD_APP,"Requested node '%s', considering node '%s'. No.", // conn->chosen_exit_name, exit->nickname); @@ -2952,8 +2963,7 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, addr_policy_result_t r; if (tor_inet_aton(conn->socks_request->address, &in)) addr = ntohl(in.s_addr); - r = compare_addr_to_addr_policy(addr, conn->socks_request->port, - exit->exit_policy); + r = compare_addr_to_node_policy(addr, conn->socks_request->port, exit); if (r == ADDR_POLICY_REJECTED) return 0; /* We know the address, and the exit policy rejects it. */ if (r == ADDR_POLICY_PROBABLY_REJECTED && !conn->chosen_exit_name) @@ -2961,18 +2971,13 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, * addresses with this port. Since the user didn't ask for * this node, err on the side of caution. */ } else if (SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command)) { - /* Can't support reverse lookups without eventdns. */ - if (conn->socks_request->command == SOCKS_COMMAND_RESOLVE_PTR && - exit->has_old_dnsworkers) - return 0; - /* Don't send DNS requests to non-exit servers by default. */ - if (!conn->chosen_exit_name && policy_is_reject_star(exit->exit_policy)) + if (!conn->chosen_exit_name && node_exit_policy_rejects_all(exit)) return 0; } if (options->_ExcludeExitNodesUnion && (options->StrictNodes || excluded_means_no) && - routerset_contains_router(options->_ExcludeExitNodesUnion, exit)) { + routerset_contains_node(options->_ExcludeExitNodesUnion, exit)) { /* If we are trying to avoid this node as exit, and we have StrictNodes * set, then this is not a suitable exit. Refuse it. * diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index fa5f91cf8d..ec7c7f5546 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -29,7 +29,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn); int connection_ap_handshake_send_begin(edge_connection_t *ap_conn); int connection_ap_handshake_send_resolve(edge_connection_t *ap_conn); -edge_connection_t *connection_ap_make_link(char *address, uint16_t port, +edge_connection_t *connection_ap_make_link(connection_t *partner, + char *address, uint16_t port, const char *digest, int use_begindir, int want_onehop); void connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, @@ -46,7 +47,8 @@ int connection_exit_begin_conn(cell_t *cell, circuit_t *circ); int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ); void connection_exit_connect(edge_connection_t *conn); int connection_edge_is_rendezvous_stream(edge_connection_t *conn); -int connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, +int connection_ap_can_use_exit(edge_connection_t *conn, + const node_t *exit, int excluded_means_no); void connection_ap_expire_beginning(void); void connection_ap_attach_pending(void); diff --git a/src/or/connection_or.c b/src/or/connection_or.c index cf7c09a3cd..5b440bc7ce 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -22,12 +22,17 @@ #include "geoip.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "reasons.h" #include "relay.h" #include "rephist.h" #include "router.h" #include "routerlist.h" +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent_ssl.h> +#endif + static int connection_tls_finish_handshake(or_connection_t *conn); static int connection_or_process_cells_from_inbuf(or_connection_t *conn); static int connection_or_send_versions(or_connection_t *conn); @@ -37,6 +42,14 @@ static int connection_or_check_valid_tls_handshake(or_connection_t *conn, int started_here, char *digest_rcvd_out); +static void connection_or_tls_renegotiated_cb(tor_tls_t *tls, void *_conn); + +#ifdef USE_BUFFEREVENTS +static void connection_or_handle_event_cb(struct bufferevent *bufev, + short event, void *arg); +#include <event2/buffer.h>/*XXXX REMOVE */ +#endif + /**************************************************************/ /** Map from identity digest of connected OR or desired OR to a connection_t @@ -178,7 +191,8 @@ var_cell_pack_header(const var_cell_t *cell, char *hdr_out) var_cell_t * var_cell_new(uint16_t payload_len) { - var_cell_t *cell = tor_malloc(sizeof(var_cell_t)+payload_len-1); + size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len; + var_cell_t *cell = tor_malloc(size); cell->payload_len = payload_len; cell->command = 0; cell->circ_id = 0; @@ -227,6 +241,14 @@ connection_or_process_inbuf(or_connection_t *conn) } return ret; +#ifdef USE_BUFFEREVENTS + case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: + if (tor_tls_server_got_renegotiate(conn->tls)) + connection_or_tls_renegotiated_cb(conn->tls, conn); + if (conn->_base.marked_for_close) + return 0; + /* fall through. */ +#endif case OR_CONN_STATE_OPEN: case OR_CONN_STATE_OR_HANDSHAKING: return connection_or_process_cells_from_inbuf(conn); @@ -248,7 +270,7 @@ connection_or_process_inbuf(or_connection_t *conn) int connection_or_flushed_some(or_connection_t *conn) { - size_t datalen = buf_datalen(conn->_base.outbuf); + size_t datalen = connection_get_outbuf_len(TO_CONN(conn)); /* If we're under the low water mark, add cells until we're just over the * high water mark. */ if (datalen < OR_CONN_LOWWATER) { @@ -281,7 +303,6 @@ connection_or_finished_flushing(or_connection_t *conn) case OR_CONN_STATE_PROXY_HANDSHAKING: case OR_CONN_STATE_OPEN: case OR_CONN_STATE_OR_HANDSHAKING: - connection_stop_writing(TO_CONN(conn)); break; default: log_err(LD_BUG,"Called in unexpected state %d.", conn->_base.state); @@ -342,7 +363,7 @@ connection_or_digest_is_known_relay(const char *id_digest) { if (router_get_consensus_status_by_id(id_digest)) return 1; /* It's in the consensus: "yes" */ - if (router_get_by_digest(id_digest)) + if (router_get_by_id_digest(id_digest)) return 1; /* Not in the consensus, but we have a descriptor for * it. Probably it was in a recent consensus. "Yes". */ return 0; @@ -379,6 +400,22 @@ connection_or_update_token_buckets_helper(or_connection_t *conn, int reset, conn->bandwidthrate = rate; conn->bandwidthburst = burst; +#ifdef USE_BUFFEREVENTS + { + const struct timeval *tick = tor_libevent_get_one_tick_timeout(); + struct ev_token_bucket_cfg *cfg, *old_cfg; + int rate_per_tick = rate / TOR_LIBEVENT_TICKS_PER_SECOND; + cfg = ev_token_bucket_cfg_new(rate_per_tick, burst, rate_per_tick, + burst, tick); + old_cfg = conn->bucket_cfg; + if (conn->_base.bufev) + bufferevent_set_rate_limit(conn->_base.bufev, cfg); + if (old_cfg) + ev_token_bucket_cfg_free(old_cfg); + conn->bucket_cfg = cfg; + (void) reset; /* No way to do this with libevent yet. */ + } +#else if (reset) { /* set up the token buckets to be full */ conn->read_bucket = conn->write_bucket = burst; return; @@ -389,6 +426,7 @@ connection_or_update_token_buckets_helper(or_connection_t *conn, int reset, conn->read_bucket = burst; if (conn->write_bucket > burst) conn->write_bucket = burst; +#endif } /** Either our set of relays or our per-conn rate limits have changed. @@ -412,7 +450,7 @@ connection_or_init_conn_from_address(or_connection_t *conn, const char *id_digest, int started_here) { - routerinfo_t *r = router_get_by_digest(id_digest); + const node_t *r = node_get_by_id(id_digest); connection_or_set_identity_digest(conn, id_digest); connection_or_update_token_buckets_helper(conn, 1, get_options()); @@ -420,8 +458,10 @@ connection_or_init_conn_from_address(or_connection_t *conn, tor_addr_copy(&conn->_base.addr, addr); tor_addr_copy(&conn->real_addr, addr); if (r) { + tor_addr_t node_addr; + node_get_addr(r, &node_addr); /* XXXX proposal 118 will make this more complex. */ - if (tor_addr_eq_ipv4h(&conn->_base.addr, r->addr)) + if (tor_addr_eq(&conn->_base.addr, &node_addr)) conn->is_canonical = 1; if (!started_here) { /* Override the addr/port, so our log messages will make sense. @@ -434,12 +474,12 @@ connection_or_init_conn_from_address(or_connection_t *conn, * right IP address and port 56244, that wouldn't be as helpful. now we * log the "right" port too, so we know if it's moria1 or moria2. */ - tor_addr_from_ipv4h(&conn->_base.addr, r->addr); - conn->_base.port = r->or_port; + tor_addr_copy(&conn->_base.addr, &node_addr); + conn->_base.port = node_get_orport(r); } - conn->nickname = tor_strdup(r->nickname); + conn->nickname = tor_strdup(node_get_nickname(r)); tor_free(conn->_base.address); - conn->_base.address = tor_strdup(r->address); + conn->_base.address = tor_dup_addr(&node_addr); } else { const char *n; /* If we're an authoritative directory server, we may know a @@ -859,6 +899,7 @@ int connection_tls_start_handshake(or_connection_t *conn, int receiving) { conn->_base.state = OR_CONN_STATE_TLS_HANDSHAKING; + tor_assert(!conn->tls); conn->tls = tor_tls_new(conn->_base.s, receiving); tor_tls_set_logged_address(conn->tls, // XXX client and relay? escaped_safe_str(conn->_base.address)); @@ -866,12 +907,38 @@ connection_tls_start_handshake(or_connection_t *conn, int receiving) log_warn(LD_BUG,"tor_tls_new failed. Closing."); return -1; } +#ifdef USE_BUFFEREVENTS + if (connection_type_uses_bufferevent(TO_CONN(conn))) { + const int filtering = get_options()->_UseFilteringSSLBufferevents; + struct bufferevent *b = + tor_tls_init_bufferevent(conn->tls, conn->_base.bufev, conn->_base.s, + receiving, filtering); + if (!b) { + log_warn(LD_BUG,"tor_tls_init_bufferevent failed. Closing."); + return -1; + } + conn->_base.bufev = b; + if (conn->bucket_cfg) + bufferevent_set_rate_limit(conn->_base.bufev, conn->bucket_cfg); + connection_enable_rate_limiting(TO_CONN(conn)); + + connection_configure_bufferevent_callbacks(TO_CONN(conn)); + bufferevent_setcb(b, + connection_handle_read_cb, + connection_handle_write_cb, + connection_or_handle_event_cb,/* overriding this one*/ + TO_CONN(conn)); + } +#endif connection_start_reading(TO_CONN(conn)); log_debug(LD_HANDSHAKE,"starting TLS handshake on fd %d", conn->_base.s); note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C); - if (connection_tls_continue_handshake(conn) < 0) { - return -1; + IF_HAS_BUFFEREVENT(TO_CONN(conn), { + /* ???? */; + }) ELSE_IF_NO_BUFFEREVENT { + if (connection_tls_continue_handshake(conn) < 0) + return -1; } return 0; } @@ -956,6 +1023,78 @@ connection_tls_continue_handshake(or_connection_t *conn) return 0; } +#ifdef USE_BUFFEREVENTS +static void +connection_or_handle_event_cb(struct bufferevent *bufev, short event, + void *arg) +{ + struct or_connection_t *conn = TO_OR_CONN(arg); + + /* XXXX cut-and-paste code; should become a function. */ + if (event & BEV_EVENT_CONNECTED) { + if (conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING) { + if (tor_tls_finish_handshake(conn->tls) < 0) { + log_warn(LD_OR, "Problem finishing handshake"); + connection_mark_for_close(TO_CONN(conn)); + return; + } + } + + if (! tor_tls_used_v1_handshake(conn->tls)) { + if (!tor_tls_is_server(conn->tls)) { + if (conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING) { + conn->_base.state = OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING; + tor_tls_unblock_renegotiation(conn->tls); + if (bufferevent_ssl_renegotiate(conn->_base.bufev)<0) { + log_warn(LD_OR, "Start_renegotiating went badly."); + connection_mark_for_close(TO_CONN(conn)); + } + tor_tls_unblock_renegotiation(conn->tls); + return; /* ???? */ + } + } else if (tor_tls_get_num_server_handshakes(conn->tls) == 1) { + /* improved handshake, as a server. Only got one handshake, so + * wait for the next one. */ + tor_tls_set_renegotiate_callback(conn->tls, + connection_or_tls_renegotiated_cb, + conn); + conn->_base.state = OR_CONN_STATE_TLS_SERVER_RENEGOTIATING; + /* return 0; */ + return; /* ???? */ + } else { + const int handshakes = tor_tls_get_num_server_handshakes(conn->tls); + tor_assert(handshakes >= 2); + if (handshakes == 2) { + /* improved handshake, as a server. Two handshakes happened already, + * so we treat renegotiation as done. + */ + connection_or_tls_renegotiated_cb(conn->tls, conn); + } else { + log_warn(LD_OR, "More than two handshakes done on connection. " + "Closing."); + connection_mark_for_close(TO_CONN(conn)); + } + return; + } + } + connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); + if (connection_tls_finish_handshake(conn) < 0) + connection_mark_for_close(TO_CONN(conn)); /* ???? */ + return; + } + + if (event & BEV_EVENT_ERROR) { + unsigned long err; + while ((err = bufferevent_get_openssl_error(bufev))) { + tor_tls_log_one_error(conn->tls, err, LOG_WARN, LD_OR, + "handshaking (with bufferevent)"); + } + } + + connection_handle_event_cb(bufev, event, arg); +} +#endif + /** Return 1 if we initiated this connection, or 0 if it started * out as an incoming connection. */ @@ -1127,7 +1266,9 @@ connection_tls_finish_handshake(or_connection_t *conn) char digest_rcvd[DIGEST_LEN]; int started_here = connection_or_nonopen_was_started_here(conn); - log_debug(LD_HANDSHAKE,"tls handshake with %s done. verifying.", + log_debug(LD_HANDSHAKE,"%s tls handshake on %p with %s done. verifying.", + started_here?"outgoing":"incoming", + conn, safe_str_client(conn->_base.address)); directory_set_dirty(); @@ -1208,7 +1349,7 @@ connection_or_set_state_open(or_connection_t *conn) router_set_status(conn->identity_digest, 1); } else { /* only report it to the geoip module if it's not a known router */ - if (!router_get_by_digest(conn->identity_digest)) { + if (!router_get_by_id_digest(conn->identity_digest)) { if (tor_addr_family(&TO_CONN(conn)->addr) == AF_INET) { /*XXXX IP6 support ipv6 geoip.*/ uint32_t a = tor_addr_to_ipv4h(&TO_CONN(conn)->addr); @@ -1219,8 +1360,12 @@ connection_or_set_state_open(or_connection_t *conn) or_handshake_state_free(conn->handshake_state); conn->handshake_state = NULL; + IF_HAS_BUFFEREVENT(TO_CONN(conn), { + connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); + }) ELSE_IF_NO_BUFFEREVENT { + connection_start_reading(TO_CONN(conn)); + } - connection_start_reading(TO_CONN(conn)); circuit_n_conn_done(conn, 1); /* send the pending creates, if any. */ return 0; @@ -1265,12 +1410,18 @@ connection_or_write_var_cell_to_buf(const var_cell_t *cell, conn->timestamp_last_added_nonpadding = approx_time(); } -/** See whether there's a variable-length cell waiting on <b>conn</b>'s +/** See whether there's a variable-length cell waiting on <b>or_conn</b>'s * inbuf. Return values as for fetch_var_cell_from_buf(). */ static int -connection_fetch_var_cell_from_buf(or_connection_t *conn, var_cell_t **out) +connection_fetch_var_cell_from_buf(or_connection_t *or_conn, var_cell_t **out) { - return fetch_var_cell_from_buf(conn->_base.inbuf, out, conn->link_proto); + connection_t *conn = TO_CONN(or_conn); + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_var_cell_from_evbuffer(input, out, or_conn->link_proto); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_var_cell_from_buf(conn->inbuf, out, or_conn->link_proto); + } } /** Process cells from <b>conn</b>'s inbuf. @@ -1288,7 +1439,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) while (1) { log_debug(LD_OR, "%d: starting, inbuf_datalen %d (%d pending in tls object).", - conn->_base.s,(int)buf_datalen(conn->_base.inbuf), + conn->_base.s,(int)connection_get_inbuf_len(TO_CONN(conn)), tor_tls_get_pending_bytes(conn->tls)); if (connection_fetch_var_cell_from_buf(conn, &var_cell)) { if (!var_cell) @@ -1299,8 +1450,8 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) } else { char buf[CELL_NETWORK_SIZE]; cell_t cell; - if (buf_datalen(conn->_base.inbuf) < CELL_NETWORK_SIZE) /* whole response - available? */ + if (connection_get_inbuf_len(TO_CONN(conn)) + < CELL_NETWORK_SIZE) /* whole response available? */ return 0; /* not yet */ circuit_build_times_network_is_live(&circ_times); @@ -1387,7 +1538,7 @@ connection_or_send_netinfo(or_connection_t *conn) { cell_t cell; time_t now = time(NULL); - routerinfo_t *me; + const routerinfo_t *me; int len; uint8_t *out; diff --git a/src/or/control.c b/src/or/control.c index fb9c1aeaf6..d9d1dfeb5e 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -26,12 +26,18 @@ #include "hibernate.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "router.h" #include "routerlist.h" #include "routerparse.h" +#ifndef MS_WINDOWS +#include <pwd.h> +#include <sys/resource.h> +#endif + /** Yield true iff <b>s</b> is the state of a control_connection_t that has * finished authentication and is accepting commands. */ #define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) @@ -64,7 +70,8 @@ #define EVENT_CLIENTS_SEEN 0x0015 #define EVENT_NEWCONSENSUS 0x0016 #define EVENT_BUILDTIMEOUT_SET 0x0017 -#define _EVENT_MAX 0x0017 +#define EVENT_SIGNAL 0x0018 +#define _EVENT_MAX 0x0018 /* 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 @@ -945,6 +952,8 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, event_code = EVENT_NEWCONSENSUS; else if (!strcasecmp(ev, "BUILDTIMEOUT_SET")) event_code = EVENT_BUILDTIMEOUT_SET; + else if (!strcasecmp(ev, "SIGNAL")) + event_code = EVENT_SIGNAL; else { connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n", ev); @@ -1341,10 +1350,65 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, return -1; } *answer = tor_dup_ip(addr); + } else if (!strcmp(question, "traffic/read")) { + tor_asprintf(answer, U64_FORMAT, U64_PRINTF_ARG(get_bytes_read())); + } else if (!strcmp(question, "traffic/written")) { + tor_asprintf(answer, U64_FORMAT, U64_PRINTF_ARG(get_bytes_written())); + } else if (!strcmp(question, "process/pid")) { + int myPid = -1; + + #ifdef MS_WINDOWS + myPid = _getpid(); + #else + myPid = getpid(); + #endif + + tor_asprintf(answer, "%d", myPid); + } else if (!strcmp(question, "process/uid")) { + #ifdef MS_WINDOWS + *answer = tor_strdup("-1"); + #else + int myUid = geteuid(); + tor_asprintf(answer, "%d", myUid); + #endif + } else if (!strcmp(question, "process/user")) { + #ifdef MS_WINDOWS + *answer = tor_strdup(""); + #else + int myUid = geteuid(); + struct passwd *myPwEntry = getpwuid(myUid); + + if (myPwEntry) { + *answer = tor_strdup(myPwEntry->pw_name); + } else { + *answer = tor_strdup(""); + } + #endif + } else if (!strcmp(question, "process/descriptor-limit")) { + /** platform specifc limits are from the set_max_file_descriptors function + * of src/common/compat.c */ + /* XXXX023 This is duplicated code from compat.c; it should turn into a + * function. */ + #ifdef HAVE_GETRLIMIT + struct rlimit descriptorLimit; + + if (getrlimit(RLIMIT_NOFILE, &descriptorLimit) == 0) { + tor_asprintf(answer, U64_FORMAT, + U64_PRINTF_ARG(descriptorLimit.rlim_max)); + } else { + *answer = tor_strdup("-1"); + } + #elif defined(CYGWIN) || defined(__CYGWIN__) + *answer = tor_strdup("3200"); + #elif defined(MS_WINDOWS) + *answer = tor_strdup("15000"); + #else + *answer = tor_strdup("15000"); + #endif } else if (!strcmp(question, "dir-usage")) { *answer = directory_dump_request_log(); } else if (!strcmp(question, "fingerprint")) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (!me) { *errmsg = "No routerdesc known; am I really a server?"; return -1; @@ -1366,8 +1430,9 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, * NOTE: <b>ri_body</b> is as returned by signed_descriptor_get_body: it might * not be NUL-terminated. */ static char * -munge_extrainfo_into_routerinfo(const char *ri_body, signed_descriptor_t *ri, - signed_descriptor_t *ei) +munge_extrainfo_into_routerinfo(const char *ri_body, + const signed_descriptor_t *ri, + const signed_descriptor_t *ei) { char *out = NULL, *outp; int i; @@ -1412,16 +1477,17 @@ getinfo_helper_dir(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg) { + const routerinfo_t *ri; (void) control_conn; if (!strcmpstart(question, "desc/id/")) { - routerinfo_t *ri = router_get_by_hexdigest(question+strlen("desc/id/")); + ri = router_get_by_hexdigest(question+strlen("desc/id/")); if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); } } else if (!strcmpstart(question, "desc/name/")) { - routerinfo_t *ri = router_get_by_nickname(question+strlen("desc/name/"),1); + ri = router_get_by_nickname(question+strlen("desc/name/"),1); if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) @@ -1431,7 +1497,7 @@ getinfo_helper_dir(control_connection_t *control_conn, routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_create(); if (routerlist && routerlist->routers) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, ri, + SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri, { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) @@ -1447,7 +1513,7 @@ getinfo_helper_dir(control_connection_t *control_conn, routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_create(); if (routerlist && routerlist->routers) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, ri, + SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri, { const char *body = signed_descriptor_get_body(&ri->cache_info); signed_descriptor_t *ei = extrainfo_get_by_descriptor_digest( @@ -1465,8 +1531,8 @@ getinfo_helper_dir(control_connection_t *control_conn, SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); } else if (!strcmpstart(question, "desc-annotations/id/")) { - routerinfo_t *ri = router_get_by_hexdigest(question+ - strlen("desc-annotations/id/")); + ri = router_get_by_hexdigest(question+ + strlen("desc-annotations/id/")); if (ri) { const char *annotations = signed_descriptor_get_annotations(&ri->cache_info); @@ -1896,6 +1962,14 @@ static const getinfo_item_t getinfo_items[] = { "Number of versioning authorities agreeing on the status of the " "current version"), ITEM("address", misc, "IP address of this Tor host, if we can guess it."), + ITEM("traffic/read", misc,"Bytes read since the process was started."), + ITEM("traffic/written", misc, + "Bytes written since the process was started."), + ITEM("process/pid", misc, "Process id belonging to the main tor process."), + ITEM("process/uid", misc, "User id running the tor process."), + ITEM("process/user", misc, + "Username under which the tor process is running."), + ITEM("process/descriptor-limit", misc, "File descriptor limit."), ITEM("dir-usage", misc, "Breakdown of bytes transferred over DirPort."), PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."), PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."), @@ -2105,7 +2179,7 @@ static int handle_control_extendcircuit(control_connection_t *conn, uint32_t len, const char *body) { - smartlist_t *router_nicknames=NULL, *routers=NULL; + smartlist_t *router_nicknames=NULL, *nodes=NULL; origin_circuit_t *circ = NULL; int zero_circ; uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL; @@ -2136,8 +2210,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, if ((smartlist_len(args) == 1) || (smartlist_len(args) >= 2 && is_keyval_pair(smartlist_get(args, 1)))) { // "EXTENDCIRCUIT 0" || EXTENDCIRCUIT 0 foo=bar" - circ = circuit_launch_by_router(intended_purpose, NULL, - CIRCLAUNCH_NEED_CAPACITY); + circ = circuit_launch(intended_purpose, CIRCLAUNCH_NEED_CAPACITY); if (!circ) { connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); } else { @@ -2165,17 +2238,21 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); smartlist_free(args); - routers = smartlist_create(); + nodes = smartlist_create(); SMARTLIST_FOREACH(router_nicknames, const char *, n, { - routerinfo_t *r = router_get_by_nickname(n, 1); - if (!r) { + const node_t *node = node_get_by_nickname(n, 1); + if (!node) { connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); goto done; } - smartlist_add(routers, r); + if (!node_has_descriptor(node)) { + connection_printf_to_buf(conn, "552 descriptor for \"%s\"\r\n", n); + goto done; + } + smartlist_add(nodes, (void*)node); }); - if (!smartlist_len(routers)) { + if (!smartlist_len(nodes)) { connection_write_str_to_buf("512 No router names provided\r\n", conn); goto done; } @@ -2186,9 +2263,10 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, } /* now circ refers to something that is ready to be extended */ - SMARTLIST_FOREACH(routers, routerinfo_t *, r, + SMARTLIST_FOREACH(nodes, const node_t *, node, { - extend_info_t *info = extend_info_from_router(r); + extend_info_t *info = extend_info_from_node(node); + tor_assert(info); /* True, since node_has_descriptor(node) == true */ circuit_append_new_exit(circ, info); extend_info_free(info); }); @@ -2222,7 +2300,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, done: SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n)); smartlist_free(router_nicknames); - smartlist_free(routers); + smartlist_free(nodes); return 0; } @@ -2338,16 +2416,17 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, } /* Is this a single hop circuit? */ if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { - routerinfo_t *r = NULL; - char* exit_digest; + const node_t *node = NULL; + char *exit_digest; if (circ->build_state && circ->build_state->chosen_exit && !tor_digest_is_zero(circ->build_state->chosen_exit->identity_digest)) { exit_digest = circ->build_state->chosen_exit->identity_digest; - r = router_get_by_digest(exit_digest); + node = node_get_by_id(exit_digest); } /* Do both the client and relay allow one-hop exit circuits? */ - if (!r || !r->allow_single_hop_exits || + if (!node || + !node_allows_single_hop_exits(node) || !get_options()->AllowSingleHopCircuits) { connection_write_str_to_buf( "551 Can't attach stream to this one-hop circuit.\r\n", conn); @@ -2719,8 +2798,6 @@ int connection_control_finished_flushing(control_connection_t *conn) { tor_assert(conn); - - connection_stop_writing(TO_CONN(conn)); return 0; } @@ -2755,6 +2832,17 @@ is_valid_initial_command(control_connection_t *conn, const char *cmd) * interfaces is broken. */ #define MAX_COMMAND_LINE_LENGTH (1024*1024) +static int +peek_connection_has_control0_command(connection_t *conn) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return peek_evbuffer_has_control0_command(input); + }) ELSE_IF_NO_BUFFEREVENT { + return peek_buf_has_control0_command(conn->inbuf); + } +} + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -2777,7 +2865,7 @@ connection_control_process_inbuf(control_connection_t *conn) } if (conn->_base.state == CONTROL_CONN_STATE_NEEDAUTH && - peek_buf_has_control0_command(conn->_base.inbuf)) { + peek_connection_has_control0_command(TO_CONN(conn))) { /* Detect v0 commands and send a "no more v0" message. */ size_t body_len; char buf[128]; @@ -2789,8 +2877,8 @@ connection_control_process_inbuf(control_connection_t *conn) body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ set_uint16(buf+0, htons(body_len)); connection_write_to_buf(buf, 4+body_len, TO_CONN(conn)); - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + + connection_mark_and_flush(TO_CONN(conn)); return 0; } @@ -2801,7 +2889,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* First, fetch a line. */ do { data_len = conn->incoming_cmd_len - conn->incoming_cmd_cur_len; - r = fetch_from_buf_line(conn->_base.inbuf, + r = connection_fetch_from_buf_line(TO_CONN(conn), conn->incoming_cmd+conn->incoming_cmd_cur_len, &data_len); if (r == 0) @@ -2811,8 +2899,7 @@ connection_control_process_inbuf(control_connection_t *conn) if (data_len + conn->incoming_cmd_cur_len > MAX_COMMAND_LINE_LENGTH) { connection_write_str_to_buf("500 Line too long.\r\n", conn); connection_stop_reading(TO_CONN(conn)); - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); } while (conn->incoming_cmd_len < data_len+conn->incoming_cmd_cur_len) conn->incoming_cmd_len *= 2; @@ -2872,8 +2959,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* Otherwise, Quit is always valid. */ if (!strcasecmp(conn->incoming_cmd, "QUIT")) { connection_write_str_to_buf("250 closing connection\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); return 0; } @@ -3167,10 +3253,10 @@ control_event_stream_status(edge_connection_t *conn, stream_status_event_t tp, static void orconn_target_get_name(char *name, size_t len, or_connection_t *conn) { - routerinfo_t *ri = router_get_by_digest(conn->identity_digest); - if (ri) { + const node_t *node = node_get_by_id(conn->identity_digest); + if (node) { tor_assert(len > MAX_VERBOSE_NICKNAME_LEN); - router_get_verbose_nickname(name, ri); + node_get_verbose_nickname(node, name); } else if (! tor_digest_is_zero(conn->identity_digest)) { name[0] = '$'; base16_encode(name+1, len-1, conn->identity_digest, @@ -3487,7 +3573,7 @@ control_event_networkstatus_changed_helper(smartlist_t *statuses, smartlist_add(strs, tor_strdup("650+")); smartlist_add(strs, tor_strdup(event_string)); smartlist_add(strs, tor_strdup("\r\n")); - SMARTLIST_FOREACH(statuses, routerstatus_t *, rs, + SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs, { s = networkstatus_getinfo_helper_single(rs); if (!s) continue; @@ -3575,10 +3661,46 @@ control_event_buildtimeout_set(const circuit_build_times_t *cbt, return 0; } +/** Called when a signal has been processed from signal_callback */ +int +control_event_signal(uintptr_t signal) +{ + const char *signal_string = NULL; + + if (!control_event_is_interesting(EVENT_SIGNAL)) + return 0; + + switch (signal) { + case SIGHUP: + signal_string = "RELOAD"; + break; + case SIGUSR1: + signal_string = "DUMP"; + break; + case SIGUSR2: + signal_string = "DEBUG"; + break; + case SIGNEWNYM: + signal_string = "NEWNYM"; + break; + case SIGCLEARDNSCACHE: + signal_string = "CLEARDNSCACHE"; + break; + default: + log_warn(LD_BUG, "Unrecognized signal %lu in control_event_signal", + (unsigned long)signal); + return -1; + } + + send_control_event(EVENT_SIGNAL, ALL_FORMATS, "650 SIGNAL %s\r\n", + signal_string); + return 0; +} + /** Called when a single local_routerstatus_t has changed: Sends an NS event * to any controller that cares. */ int -control_event_networkstatus_changed_single(routerstatus_t *rs) +control_event_networkstatus_changed_single(const routerstatus_t *rs) { smartlist_t *statuses; int r; @@ -3587,7 +3709,7 @@ control_event_networkstatus_changed_single(routerstatus_t *rs) return 0; statuses = smartlist_create(); - smartlist_add(statuses, rs); + smartlist_add(statuses, (void*)rs); r = control_event_networkstatus_changed(statuses); smartlist_free(statuses); return r; @@ -3712,9 +3834,9 @@ control_event_guard(const char *nickname, const char *digest, { char buf[MAX_VERBOSE_NICKNAME_LEN+1]; - routerinfo_t *ri = router_get_by_digest(digest); - if (ri) { - router_get_verbose_nickname(buf, ri); + const node_t *node = node_get_by_id(digest); + if (node) { + node_get_verbose_nickname(node, buf); } else { tor_snprintf(buf, sizeof(buf), "$%s~%s", hbuf, nickname); } diff --git a/src/or/control.h b/src/or/control.h index 2ae96b4b8a..bec067792e 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -53,7 +53,7 @@ int control_event_my_descriptor_changed(void); int control_event_networkstatus_changed(smartlist_t *statuses); int control_event_newconsensus(const networkstatus_t *consensus); -int control_event_networkstatus_changed_single(routerstatus_t *rs); +int control_event_networkstatus_changed_single(const routerstatus_t *rs); int control_event_general_status(int severity, const char *format, ...) CHECK_PRINTF(2,3); int control_event_client_status(int severity, const char *format, ...) @@ -64,6 +64,7 @@ int control_event_guard(const char *nickname, const char *digest, const char *status); int control_event_buildtimeout_set(const circuit_build_times_t *cbt, buildtimeout_set_event_t type); +int control_event_signal(uintptr_t signal); int init_cookie_authentication(int enabled); smartlist_t *decode_hashed_passwords(config_line_t *passwords); diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index 7cbc191333..5597cad2a8 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -62,7 +62,6 @@ connection_cpu_finished_flushing(connection_t *conn) { tor_assert(conn); tor_assert(conn->type == CONN_TYPE_CPUWORKER); - connection_stop_writing(conn); return 0; } @@ -141,13 +140,13 @@ connection_cpu_process_inbuf(connection_t *conn) tor_assert(conn); tor_assert(conn->type == CONN_TYPE_CPUWORKER); - if (!buf_datalen(conn->inbuf)) + if (!connection_get_inbuf_len(conn)) return 0; if (conn->state == CPUWORKER_STATE_BUSY_ONION) { - if (buf_datalen(conn->inbuf) < LEN_ONION_RESPONSE) /* answer available? */ + if (connection_get_inbuf_len(conn) < LEN_ONION_RESPONSE) return 0; /* not yet */ - tor_assert(buf_datalen(conn->inbuf) == LEN_ONION_RESPONSE); + tor_assert(connection_get_inbuf_len(conn) == LEN_ONION_RESPONSE); connection_fetch_from_buf(&success,1,conn); connection_fetch_from_buf(buf,LEN_ONION_RESPONSE-1,conn); @@ -367,7 +366,7 @@ spawn_cpuworker(void) static void spawn_enough_cpuworkers(void) { - int num_cpuworkers_needed = get_options()->NumCPUs; + int num_cpuworkers_needed = get_num_cpus(get_options()); if (num_cpuworkers_needed < MIN_CPUWORKERS) num_cpuworkers_needed = MIN_CPUWORKERS; diff --git a/src/or/directory.c b/src/or/directory.c index 7360a47e46..ca612024fd 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -15,7 +15,9 @@ #include "dirvote.h" #include "geoip.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "rendclient.h" #include "rendcommon.h" @@ -78,6 +80,8 @@ static void dir_routerdesc_download_failed(smartlist_t *failed, int router_purpose, int was_extrainfo, int was_descriptor_digests); +static void dir_microdesc_download_failed(smartlist_t *failed, + int status_code); static void note_client_request(int purpose, int compressed, size_t bytes); static int client_likes_consensus(networkstatus_t *v, const char *want_url); @@ -137,7 +141,8 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose) dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS || dir_purpose == DIR_PURPOSE_FETCH_CERTIFICATE || dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC || - dir_purpose == DIR_PURPOSE_FETCH_EXTRAINFO) + dir_purpose == DIR_PURPOSE_FETCH_EXTRAINFO || + dir_purpose == DIR_PURPOSE_FETCH_MICRODESC) return 0; return 1; } @@ -201,6 +206,8 @@ dir_conn_purpose_to_string(int purpose) return "hidden-service v2 descriptor fetch"; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: return "hidden-service v2 descriptor upload"; + case DIR_PURPOSE_FETCH_MICRODESC: + return "microdescriptor fetch"; } log_warn(LD_BUG, "Called with unknown purpose %d", purpose); @@ -213,17 +220,19 @@ dir_conn_purpose_to_string(int purpose) int router_supports_extrainfo(const char *identity_digest, int is_authority) { - routerinfo_t *ri = router_get_by_digest(identity_digest); + const node_t *node = node_get_by_id(identity_digest); - if (ri) { - if (ri->caches_extra_info) + if (node && node->ri) { + if (node->ri->caches_extra_info) return 1; - if (is_authority && ri->platform && - tor_version_as_new_as(ri->platform, "Tor 0.2.0.0-alpha-dev (r10070)")) + if (is_authority && node->ri->platform && + tor_version_as_new_as(node->ri->platform, + "Tor 0.2.0.0-alpha-dev (r10070)")) return 1; } if (is_authority) { - routerstatus_t *rs = router_get_consensus_status_by_id(identity_digest); + const routerstatus_t *rs = + router_get_consensus_status_by_id(identity_digest); if (rs && rs->version_supports_extrainfo_upload) return 1; } @@ -322,7 +331,7 @@ void directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, const char *resource, int pds_flags) { - routerstatus_t *rs = NULL; + const routerstatus_t *rs = NULL; or_options_t *options = get_options(); int prefer_authority = directory_fetches_from_authorities(options); int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose); @@ -355,15 +364,33 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, case DIR_PURPOSE_FETCH_CERTIFICATE: type = V3_AUTHORITY; break; + case DIR_PURPOSE_FETCH_MICRODESC: + type = V3_AUTHORITY; + break; default: log_warn(LD_BUG, "Unexpected purpose %d", (int)dir_purpose); return; } if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS) { - networkstatus_t *v = networkstatus_get_latest_consensus(); - if (v) - if_modified_since = v->valid_after + 180; + int flav = FLAV_NS; + networkstatus_t *v; + if (resource) + flav = networkstatus_parse_flavor_name(resource); + + if (flav != -1) { + /* IF we have a parsed consensus of this type, we can do an + * if-modified-time based on it. */ + v = networkstatus_get_latest_consensus_by_flavor(flav); + if (v) + if_modified_since = v->valid_after + 180; + } else { + /* Otherwise it might be a consensus we don't parse, but which we + * do cache. Look at the cached copy, perhaps. */ + cached_dir_t *cd = dirserv_get_consensus(resource ? resource : "ns"); + if (cd) + if_modified_since = cd->published + 180; + } } if (!options->FetchServerDescriptors && type != HIDSERV_AUTHORITY) @@ -376,10 +403,12 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, * possible directory question. This won't be true forever. -RD */ /* It certainly is not true with conditional consensus downloading, * so, for now, never assume the server supports that. */ - routerinfo_t *ri = choose_random_entry(NULL); - if (ri) { + const node_t *node = choose_random_entry(NULL); + if (node && node->ri) { + /* every bridge has a routerinfo. */ tor_addr_t addr; - tor_addr_from_ipv4h(&addr, ri->addr); + routerinfo_t *ri = node->ri; + node_get_addr(node, &addr); directory_initiate_command(ri->address, &addr, ri->or_port, 0, 0, /* don't use conditional consensus url */ @@ -395,7 +424,8 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, if (prefer_authority || type == BRIDGE_AUTHORITY) { /* only ask authdirservers, and don't ask myself */ rs = router_pick_trusteddirserver(type, pds_flags); - if (rs == NULL && (pds_flags & PDS_NO_EXISTING_SERVERDESC_FETCH)) { + if (rs == NULL && (pds_flags & (PDS_NO_EXISTING_SERVERDESC_FETCH| + PDS_NO_EXISTING_MICRODESC_FETCH))) { /* We don't want to fetch from any authorities that we're currently * fetching server descriptors from, and we got no match. Did we * get no match because all the authorities have connections @@ -403,7 +433,8 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, * return,) or because all the authorities are down or on fire or * unreachable or something (in which case we should go on with * our fallback code)? */ - pds_flags &= ~PDS_NO_EXISTING_SERVERDESC_FETCH; + pds_flags &= ~(PDS_NO_EXISTING_SERVERDESC_FETCH| + PDS_NO_EXISTING_MICRODESC_FETCH); rs = router_pick_trusteddirserver(type, pds_flags); if (rs) { log_debug(LD_DIR, "Deferring serverdesc fetch: all authorities " @@ -486,7 +517,7 @@ directory_get_from_all_authorities(uint8_t dir_purpose, /** Same as directory_initiate_command_routerstatus(), but accepts * rendezvous data to fetch a hidden service descriptor. */ void -directory_initiate_command_routerstatus_rend(routerstatus_t *status, +directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, @@ -496,18 +527,19 @@ directory_initiate_command_routerstatus_rend(routerstatus_t *status, time_t if_modified_since, const rend_data_t *rend_query) { - routerinfo_t *router; + const node_t *node; char address_buf[INET_NTOA_BUF_LEN+1]; struct in_addr in; const char *address; tor_addr_t addr; - router = router_get_by_digest(status->identity_digest); - if (!router && anonymized_connection) { + node = node_get_by_id(status->identity_digest); + if (!node && anonymized_connection) { log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we " "don't have its router descriptor.", status->nickname); return; - } else if (router) { - address = router->address; + } else if (node) { + node_get_address_string(node, address_buf, sizeof(address_buf)); + address = address_buf; } else { in.s_addr = htonl(status->addr); tor_inet_ntoa(&in, address_buf, sizeof(address_buf)); @@ -540,7 +572,7 @@ directory_initiate_command_routerstatus_rend(routerstatus_t *status, * want to fetch. */ void -directory_initiate_command_routerstatus(routerstatus_t *status, +directory_initiate_command_routerstatus(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, @@ -564,7 +596,7 @@ directory_conn_is_self_reachability_test(dir_connection_t *conn) { if (conn->requested_resource && !strcmpstart(conn->requested_resource,"authority")) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (me && router_digest_is_me(conn->identity_digest) && tor_addr_eq_ipv4h(&conn->_base.addr, me->addr) && /*XXXX prop 118*/ @@ -592,15 +624,19 @@ connection_dir_request_failed(dir_connection_t *conn) connection_dir_download_networkstatus_failed(conn, -1); } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { - log_info(LD_DIR, "Giving up on directory server at '%s'; retrying", + log_info(LD_DIR, "Giving up on serverdesc/extrainfo fetch from " + "directory server at '%s'; retrying", conn->_base.address); if (conn->router_purpose == ROUTER_PURPOSE_BRIDGE) connection_dir_bridge_routerdesc_failed(conn); connection_dir_download_routerdesc_failed(conn); } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { - networkstatus_consensus_download_failed(0); + const char *flavname = + conn->requested_resource ? conn->requested_resource : "ns"; + networkstatus_consensus_download_failed(0, flavname); } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) { - log_info(LD_DIR, "Giving up on directory server at '%s'; retrying", + log_info(LD_DIR, "Giving up on certificate fetch from directory server " + "at '%s'; retrying", conn->_base.address); connection_dir_download_cert_failed(conn, 0); } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) { @@ -609,6 +645,10 @@ connection_dir_request_failed(dir_connection_t *conn) } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) { log_info(LD_DIR, "Giving up downloading votes from '%s'", 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); + connection_dir_download_routerdesc_failed(conn); } } @@ -679,7 +719,8 @@ connection_dir_download_routerdesc_failed(dir_connection_t *conn) /* No need to relaunch descriptor downloads here: we already do it * every 10 or 60 seconds (FOO_DESCRIPTOR_RETRY_INTERVAL) in main.c. */ tor_assert(conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || - conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO); + conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || + conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC); (void) conn; } @@ -892,14 +933,14 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, * hook up both sides */ linked_conn = - connection_ap_make_link(conn->_base.address, conn->_base.port, + connection_ap_make_link(TO_CONN(conn), + conn->_base.address, conn->_base.port, digest, use_begindir, conn->dirconn_direct); if (!linked_conn) { log_warn(LD_NET,"Making tunnel to dirserver failed."); connection_mark_for_close(TO_CONN(conn)); return; } - connection_link_connections(TO_CONN(conn), TO_CONN(linked_conn)); if (connection_add(TO_CONN(conn)) < 0) { log_warn(LD_NET,"Unable to add connection for link to dirserver."); @@ -912,8 +953,12 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, payload, payload_len, supports_conditional_consensus, if_modified_since); + connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); - connection_start_reading(TO_CONN(linked_conn)); + IF_HAS_BUFFEREVENT(TO_CONN(linked_conn), { + connection_watch_events(TO_CONN(linked_conn), READ_EVENT|WRITE_EVENT); + }) ELSE_IF_NO_BUFFEREVENT + connection_start_reading(TO_CONN(linked_conn)); } } @@ -951,12 +996,16 @@ _compare_strs(const void **a, const void **b) * This url depends on whether or not the server we go to * is sufficiently new to support conditional consensus downloading, * i.e. GET .../consensus/<b>fpr</b>+<b>fpr</b>+<b>fpr</b> + * + * If 'resource' is provided, it is the name of a consensus flavor to request. */ static char * -directory_get_consensus_url(int supports_conditional_consensus) +directory_get_consensus_url(int supports_conditional_consensus, + const char *resource) { - char *url; - size_t len; + char *url = NULL; + const char *hyphen = resource ? "-" : ""; + const char *flavor = resource ? resource : ""; if (supports_conditional_consensus) { char *authority_id_list; @@ -978,16 +1027,15 @@ directory_get_consensus_url(int supports_conditional_consensus) authority_id_list = smartlist_join_strings(authority_digests, "+", 0, NULL); - len = strlen(authority_id_list)+64; - url = tor_malloc(len); - tor_snprintf(url, len, "/tor/status-vote/current/consensus/%s.z", - authority_id_list); + tor_asprintf(&url, "/tor/status-vote/current/consensus%s%s/%s.z", + hyphen, flavor, authority_id_list); SMARTLIST_FOREACH(authority_digests, char *, cp, tor_free(cp)); smartlist_free(authority_digests); tor_free(authority_id_list); } else { - url = tor_strdup("/tor/status-vote/current/consensus.z"); + tor_asprintf(&url, "/tor/status-vote/current/consensus%s%s.z", + hyphen, flavor); } return url; } @@ -1068,10 +1116,11 @@ directory_send_command(dir_connection_t *conn, tor_snprintf(url, len, "/tor/status/%s", resource); break; case DIR_PURPOSE_FETCH_CONSENSUS: - tor_assert(!resource); + /* resource is optional. If present, it's a flavor name */ tor_assert(!payload); httpcommand = "GET"; - url = directory_get_consensus_url(supports_conditional_consensus); + url = directory_get_consensus_url(supports_conditional_consensus, + resource); log_info(LD_DIR, "Downloading consensus from %s using %s", hoststring, url); break; @@ -1111,6 +1160,11 @@ directory_send_command(dir_connection_t *conn, url = tor_malloc(len); tor_snprintf(url, len, "/tor/extra/%s", resource); break; + case DIR_PURPOSE_FETCH_MICRODESC: + tor_assert(resource); + httpcommand = "GET"; + tor_asprintf(&url, "/tor/micro/%s", resource); + break; case DIR_PURPOSE_UPLOAD_DIR: tor_assert(!resource); tor_assert(payload); @@ -1386,6 +1440,9 @@ body_is_plausible(const char *body, size_t len, int purpose) return 1; /* empty bodies don't need decompression */ if (len < 32) return 0; + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { + return (!strcmpstart(body,"onion-key")); + } if (purpose != DIR_PURPOSE_FETCH_RENDDESC) { if (!strcmpstart(body,"router") || !strcmpstart(body,"signed-directory") || @@ -1462,11 +1519,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn) int plausible; int skewed=0; int allow_partial = (conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || - conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO); + conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || + conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC); int was_compressed=0; time_t now = time(NULL); - switch (fetch_from_buf_http(conn->_base.inbuf, + switch (connection_fetch_from_buf_http(TO_CONN(conn), &headers, MAX_HEADERS_SIZE, &body, &body_len, MAX_DIR_DL_SIZE, allow_partial)) { @@ -1545,7 +1603,8 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "'%s:%d'. I'll try again soon.", status_code, escaped(reason), conn->_base.address, conn->_base.port); - if ((rs = router_get_consensus_status_by_id(conn->identity_digest))) + rs = router_get_mutable_consensus_status_by_id(conn->identity_digest); + if (rs) rs->last_dir_503_at = now; if ((ds = router_get_trusteddirserver_by_digest(conn->identity_digest))) ds->fake_status.last_dir_503_at = now; @@ -1686,6 +1745,8 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (conn->_base.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { int r; + const char *flavname = + conn->requested_resource ? conn->requested_resource : "ns"; if (status_code != 200) { int severity = (status_code == 304) ? LOG_INFO : LOG_WARN; log(severity, LD_DIR, @@ -1694,22 +1755,24 @@ connection_dir_client_reached_eof(dir_connection_t *conn) status_code, escaped(reason), conn->_base.address, conn->_base.port); tor_free(body); tor_free(headers); tor_free(reason); - networkstatus_consensus_download_failed(status_code); + networkstatus_consensus_download_failed(status_code, flavname); return -1; } log_info(LD_DIR,"Received consensus directory (size %d) from server " "'%s:%d'", (int)body_len, conn->_base.address, conn->_base.port); - if ((r=networkstatus_set_current_consensus(body, "ns", 0))<0) { + if ((r=networkstatus_set_current_consensus(body, flavname, 0))<0) { log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, - "Unable to load consensus directory downloaded from " + "Unable to load %s consensus directory downloaded from " "server '%s:%d'. I'll try again soon.", - conn->_base.address, conn->_base.port); + flavname, conn->_base.address, conn->_base.port); tor_free(body); tor_free(headers); tor_free(reason); - networkstatus_consensus_download_failed(0); + networkstatus_consensus_download_failed(0, flavname); return -1; } /* launches router downloads as needed */ routers_update_all_from_networkstatus(now, 3); + update_microdescs_from_networkstatus(now); + update_microdesc_downloads(now); directory_info_has_arrived(now, 0); log_info(LD_DIR, "Successfully loaded consensus."); } @@ -1859,6 +1922,42 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (directory_conn_is_self_reachability_test(conn)) router_dirport_found_reachable(); } + if (conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC) { + smartlist_t *which = NULL; + log_info(LD_DIR,"Received answer to microdescriptor request (status %d, " + "size %d) from server '%s:%d'", + status_code, (int)body_len, conn->_base.address, + conn->_base.port); + tor_assert(conn->requested_resource && + !strcmpstart(conn->requested_resource, "d/")); + which = smartlist_create(); + dir_split_resource_into_fingerprints(conn->requested_resource+2, + which, NULL, + DSR_DIGEST256|DSR_BASE64); + if (status_code != 200) { + log_info(LD_DIR, "Received status code %d (%s) from server " + "'%s:%d' while fetching \"/tor/micro/%s\". I'll try again " + "soon.", + status_code, escaped(reason), conn->_base.address, + (int)conn->_base.port, conn->requested_resource); + dir_microdesc_download_failed(which, status_code); + SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); + smartlist_free(which); + tor_free(body); tor_free(headers); tor_free(reason); + return 0; + } else { + smartlist_t *mds; + mds = microdescs_add_to_cache(get_microdesc_cache(), + body, body+body_len, SAVED_NOWHERE, 0, + now, which); + if (smartlist_len(which)) { + /* Mark remaining ones as failed. */ + dir_microdesc_download_failed(which, status_code); + } + SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); + smartlist_free(which); + } + } if (conn->_base.purpose == DIR_PURPOSE_UPLOAD_DIR) { switch (status_code) { @@ -2134,7 +2233,7 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } - if (buf_datalen(conn->_base.inbuf) > MAX_DIRECTORY_OBJECT_SIZE) { + if (connection_get_inbuf_len(TO_CONN(conn)) > MAX_DIRECTORY_OBJECT_SIZE) { log_warn(LD_HTTP, "Too much data received from directory connection: " "denial of service attempt, or you need to upgrade?"); connection_mark_for_close(TO_CONN(conn)); @@ -3299,7 +3398,7 @@ directory_handle_command(dir_connection_t *conn) tor_assert(conn); tor_assert(conn->_base.type == CONN_TYPE_DIR); - switch (fetch_from_buf_http(conn->_base.inbuf, + switch (connection_fetch_from_buf_http(TO_CONN(conn), &headers, MAX_HEADERS_SIZE, &body, &body_len, MAX_DIR_UL_SIZE, 0)) { case -1: /* overflow */ @@ -3352,10 +3451,10 @@ connection_dir_finished_flushing(dir_connection_t *conn) DIRREQ_DIRECT, DIRREQ_FLUSHING_DIR_CONN_FINISHED); switch (conn->_base.state) { + case DIR_CONN_STATE_CONNECTING: case DIR_CONN_STATE_CLIENT_SENDING: log_debug(LD_DIR,"client finished sending command."); conn->_base.state = DIR_CONN_STATE_CLIENT_READING; - connection_stop_writing(TO_CONN(conn)); return 0; case DIR_CONN_STATE_SERVER_WRITING: log_debug(LD_DIRSERV,"Finished writing server response. Closing."); @@ -3585,6 +3684,36 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code, * every 10 or 60 seconds (FOO_DESCRIPTOR_RETRY_INTERVAL) in main.c. */ } +/* DOCDOC NM */ +static void +dir_microdesc_download_failed(smartlist_t *failed, + int status_code) +{ + networkstatus_t *consensus + = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); + routerstatus_t *rs; + download_status_t *dls; + time_t now = time(NULL); + int server = directory_fetches_from_authorities(get_options()); + + if (! consensus) + return; + SMARTLIST_FOREACH_BEGIN(failed, const char *, d) { + rs = router_get_mutable_consensus_status_by_descriptor_digest(consensus,d); + if (!rs) + continue; + dls = &rs->dl_status; + if (dls->n_download_failures >= MAX_MICRODESC_DOWNLOAD_FAILURES) + continue; + { + char buf[BASE64_DIGEST256_LEN+1]; + digest256_to_base64(buf, d); + download_status_increment_failure(dls, status_code, buf, + server, now); + } + } SMARTLIST_FOREACH_END(d); +} + /** Helper. Compare two fp_pair_t objects, and return -1, 0, or 1 as * appropriate. */ static int diff --git a/src/or/directory.h b/src/or/directory.h index 94dfb17644..e078575503 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -23,7 +23,7 @@ void directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, void directory_get_from_all_authorities(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); -void directory_initiate_command_routerstatus(routerstatus_t *status, +void directory_initiate_command_routerstatus(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, @@ -31,7 +31,7 @@ void directory_initiate_command_routerstatus(routerstatus_t *status, const char *payload, size_t payload_len, time_t if_modified_since); -void directory_initiate_command_routerstatus_rend(routerstatus_t *status, +void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 876698a2dc..5d9859f28c 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -16,6 +16,7 @@ #include "hibernate.h" #include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "rephist.h" #include "router.h" @@ -65,8 +66,6 @@ static char *format_versions_list(config_line_t *ln); struct authdir_config_t; static int add_fingerprint_to_dir(const char *nickname, const char *fp, struct authdir_config_t *list); -static uint32_t dirserv_router_get_status(const routerinfo_t *router, - const char **msg); static uint32_t dirserv_get_status_impl(const char *fp, const char *nickname, const char *address, @@ -74,7 +73,8 @@ dirserv_get_status_impl(const char *fp, const char *nickname, const char *platform, const char *contact, const char **msg, int should_log); static void clear_cached_dir(cached_dir_t *d); -static signed_descriptor_t *get_signed_descriptor_by_fp(const char *fp, +static const signed_descriptor_t *get_signed_descriptor_by_fp( + const char *fp, int extrainfo, time_t publish_cutoff); static int dirserv_add_extrainfo(extrainfo_t *ei, const char **msg); @@ -303,7 +303,7 @@ dirserv_load_fingerprint_file(void) * * If the status is 'FP_REJECT' and <b>msg</b> is provided, set * *<b>msg</b> to an explanation of why. */ -static uint32_t +uint32_t dirserv_router_get_status(const routerinfo_t *router, const char **msg) { char d[DIGEST_LEN]; @@ -325,7 +325,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg) /** Return true if there is no point in downloading the router described by * <b>rs</b> because this directory would reject it. */ int -dirserv_would_reject_router(routerstatus_t *rs) +dirserv_would_reject_router(const routerstatus_t *rs) { uint32_t res; @@ -360,7 +360,7 @@ dirserv_get_name_status(const char *id_digest, const char *nickname) return 0; } -/** Helper: As dirserv_get_router_status, but takes the router fingerprint +/** Helper: As dirserv_router_get_status, but takes the router fingerprint * (hex, no spaces), nickname, address (used for logging only), IP address, OR * port, platform (logging only) and contact info (logging only) as arguments. * @@ -375,7 +375,7 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, const char **msg, int should_log) { int reject_unlisted = get_options()->AuthDirRejectUnlisted; - uint32_t result = 0; + uint32_t result; router_status_t *status_by_digest; if (!fingerprint_list) @@ -539,7 +539,7 @@ dirserv_router_has_valid_address(routerinfo_t *ri) */ int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, - int complain) + int complain, int *valid_out) { /* Okay. Now check whether the fingerprint is recognized. */ uint32_t status = dirserv_router_get_status(ri, msg); @@ -580,15 +580,24 @@ authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, *msg = "Rejected: Address is not an IP, or IP is a private address."; return -1; } - /* Okay, looks like we're willing to accept this one. */ - ri->is_named = (status & FP_NAMED) ? 1 : 0; - ri->is_valid = (status & FP_INVALID) ? 0 : 1; - ri->is_bad_directory = (status & FP_BADDIR) ? 1 : 0; - ri->is_bad_exit = (status & FP_BADEXIT) ? 1 : 0; + + *valid_out = ! (status & FP_INVALID); return 0; } +/** Update the relevant flags of <b>node</b> based on our opinion as a + * directory authority in <b>authstatus</b>, as returned by + * dirserv_router_get_status or equivalent. */ +void +dirserv_set_node_flags_from_authoritative_status(node_t *node, + uint32_t authstatus) +{ + node->is_valid = (authstatus & FP_INVALID) ? 0 : 1; + node->is_bad_directory = (authstatus & FP_BADDIR) ? 1 : 0; + node->is_bad_exit = (authstatus & FP_BADEXIT) ? 1 : 0; +} + /** True iff <b>a</b> is more severe than <b>b</b>. */ static int WRA_MORE_SEVERE(was_router_added_t a, was_router_added_t b) @@ -713,7 +722,7 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) * from this server. (We do this here and not in router_add_to_routerlist * because we want to be able to accept the newest router descriptor that * another authority has, so we all converge on the same one.) */ - ri_old = router_get_by_digest(ri->cache_info.identity_digest); + ri_old = router_get_mutable_by_digest(ri->cache_info.identity_digest); if (ri_old && ri_old->cache_info.published_on < ri->cache_info.published_on && router_differences_are_cosmetic(ri_old, ri) && !router_is_me(ri)) { @@ -757,8 +766,7 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) routerlist_descriptors_added(changed, 0); smartlist_free(changed); if (!*msg) { - *msg = ri->is_valid ? "Descriptor for valid server accepted" : - "Descriptor for invalid server accepted"; + *msg = "Descriptor accepted"; } log_info(LD_DIRSERV, "Added descriptor from '%s' (source: %s): %s.", @@ -773,12 +781,12 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei, const char **msg) { - routerinfo_t *ri; + const routerinfo_t *ri; int r; tor_assert(msg); *msg = NULL; - ri = router_get_by_digest(ei->cache_info.identity_digest); + ri = router_get_by_id_digest(ei->cache_info.identity_digest); if (!ri) { *msg = "No corresponding router descriptor for extra-info descriptor"; extrainfo_free(ei); @@ -813,54 +821,65 @@ dirserv_add_extrainfo(extrainfo_t *ei, const char **msg) static void directory_remove_invalid(void) { - int i; int changed = 0; routerlist_t *rl = router_get_routerlist(); + smartlist_t *nodes = smartlist_create(); + smartlist_add_all(nodes, nodelist_get_list()); - routerlist_assert_ok(rl); - - for (i = 0; i < smartlist_len(rl->routers); ++i) { + SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) { const char *msg; - routerinfo_t *ent = smartlist_get(rl->routers, i); - uint32_t r = dirserv_router_get_status(ent, &msg); + routerinfo_t *ent = node->ri; + uint32_t r; + if (!ent) + continue; + r = dirserv_router_get_status(ent, &msg); if (r & FP_REJECT) { log_info(LD_DIRSERV, "Router '%s' is now rejected: %s", ent->nickname, msg?msg:""); routerlist_remove(rl, ent, 0, time(NULL)); - i--; changed = 1; continue; } - if (bool_neq((r & FP_NAMED), ent->is_named)) { +#if 0 + if (bool_neq((r & FP_NAMED), ent->auth_says_is_named)) { log_info(LD_DIRSERV, "Router '%s' is now %snamed.", ent->nickname, (r&FP_NAMED)?"":"un"); ent->is_named = (r&FP_NAMED)?1:0; changed = 1; } - if (bool_neq((r & FP_INVALID), !ent->is_valid)) { + if (bool_neq((r & FP_UNNAMED), ent->auth_says_is_unnamed)) { + log_info(LD_DIRSERV, + "Router '%s' is now %snamed. (FP_UNNAMED)", ent->nickname, + (r&FP_NAMED)?"":"un"); + ent->is_named = (r&FP_NUNAMED)?0:1; + changed = 1; + } +#endif + if (bool_neq((r & FP_INVALID), !node->is_valid)) { log_info(LD_DIRSERV, "Router '%s' is now %svalid.", ent->nickname, (r&FP_INVALID) ? "in" : ""); - ent->is_valid = (r&FP_INVALID)?0:1; + node->is_valid = (r&FP_INVALID)?0:1; changed = 1; } - if (bool_neq((r & FP_BADDIR), ent->is_bad_directory)) { + if (bool_neq((r & FP_BADDIR), node->is_bad_directory)) { log_info(LD_DIRSERV, "Router '%s' is now a %s directory", ent->nickname, (r & FP_BADDIR) ? "bad" : "good"); - ent->is_bad_directory = (r&FP_BADDIR) ? 1: 0; + node->is_bad_directory = (r&FP_BADDIR) ? 1: 0; changed = 1; } - if (bool_neq((r & FP_BADEXIT), ent->is_bad_exit)) { + if (bool_neq((r & FP_BADEXIT), node->is_bad_exit)) { log_info(LD_DIRSERV, "Router '%s' is now a %s exit", ent->nickname, (r & FP_BADEXIT) ? "bad" : "good"); - ent->is_bad_exit = (r&FP_BADEXIT) ? 1: 0; + node->is_bad_exit = (r&FP_BADEXIT) ? 1: 0; changed = 1; } - } + } SMARTLIST_FOREACH_END(node); if (changed) directory_set_dirty(); routerlist_assert_ok(rl); + smartlist_free(nodes); } /** Mark the directory as <b>dirty</b> -- when we're next asked for a @@ -899,10 +918,11 @@ directory_set_dirty(void) * as running iff <b>is_live</b> is true. */ static char * -list_single_server_status(routerinfo_t *desc, int is_live) +list_single_server_status(const routerinfo_t *desc, int is_live) { char buf[MAX_NICKNAME_LEN+HEX_DIGEST_LEN+4]; /* !nickname=$hexdigest\0 */ char *cp; + const node_t *node; tor_assert(desc); @@ -910,7 +930,8 @@ list_single_server_status(routerinfo_t *desc, int is_live) if (!is_live) { *cp++ = '!'; } - if (desc->is_valid) { + node = node_get_by_id(desc->cache_info.identity_digest); + if (node && node->is_valid) { strlcpy(cp, desc->nickname, sizeof(buf)-(cp-buf)); cp += strlen(cp); *cp++ = '='; @@ -949,6 +970,8 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) unreachable. */ int answer; + node_t *node = node_get_mutable_by_id(router->cache_info.identity_digest); + tor_assert(node); if (router_is_me(router)) { /* We always know if we are down ourselves. */ @@ -973,7 +996,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) rep_hist_note_router_unreachable(router->cache_info.identity_digest, now); } - router->is_running = answer; + node->is_running = answer; } /** Based on the routerinfo_ts in <b>routers</b>, allocate the @@ -1001,6 +1024,8 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, rs_entries = smartlist_create(); SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) { + const node_t *node = node_get_by_id(ri->cache_info.identity_digest); + tor_assert(node); if (authdir) { /* Update router status in routerinfo_t. */ dirserv_set_router_is_running(ri, now); @@ -1008,12 +1033,13 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, if (for_controller) { char name_buf[MAX_VERBOSE_NICKNAME_LEN+2]; char *cp = name_buf; - if (!ri->is_running) + if (!node->is_running) *cp++ = '!'; router_get_verbose_nickname(cp, ri); smartlist_add(rs_entries, tor_strdup(name_buf)); } else if (ri->cache_info.published_on >= cutoff) { - smartlist_add(rs_entries, list_single_server_status(ri, ri->is_running)); + smartlist_add(rs_entries, list_single_server_status(ri, + node->is_running)); } } SMARTLIST_FOREACH_END(ri); @@ -1051,12 +1077,12 @@ format_versions_list(config_line_t *ln) * not hibernating, and not too old. Else return 0. */ static int -router_is_active(routerinfo_t *ri, time_t now) +router_is_active(const routerinfo_t *ri, const node_t *node, time_t now) { time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH; if (ri->cache_info.published_on < cutoff) return 0; - if (!ri->is_running || !ri->is_valid || ri->is_hibernating) + if (!node->is_running || !node->is_valid || ri->is_hibernating) return 0; return 1; } @@ -1157,7 +1183,7 @@ dirserv_dump_directory_to_string(char **dir_out, int directory_fetches_from_authorities(or_options_t *options) { - routerinfo_t *me; + const routerinfo_t *me; uint32_t addr; int refuseunknown; if (options->FetchDirInfoEarly) @@ -1269,7 +1295,8 @@ static cached_dir_t cached_runningrouters; * cached_dir_t. */ static digestmap_t *cached_v2_networkstatus = NULL; -/** Map from flavor name to the v3 consensuses that we're currently serving. */ +/** Map from flavor name to the cached_dir_t for the v3 consensuses that we're + * currently serving. */ static strmap_t *cached_consensuses = NULL; /** Possibly replace the contents of <b>d</b> with the value of @@ -1722,7 +1749,7 @@ static uint64_t total_exit_bandwidth = 0; /** Helper: estimate the uptime of a router given its stated uptime and the * amount of time since it last stated its stated uptime. */ static INLINE long -real_uptime(routerinfo_t *router, time_t now) +real_uptime(const routerinfo_t *router, time_t now) { if (now < router->cache_info.published_on) return router->uptime; @@ -1773,7 +1800,8 @@ dirserv_thinks_router_is_unreliable(time_t now, * been set. */ static int -dirserv_thinks_router_is_hs_dir(routerinfo_t *router, time_t now) +dirserv_thinks_router_is_hs_dir(const routerinfo_t *router, + const node_t *node, time_t now) { long uptime = real_uptime(router, now); @@ -1783,7 +1811,7 @@ dirserv_thinks_router_is_hs_dir(routerinfo_t *router, time_t now) * version is too old. */ return (router->wants_to_be_hs_dir && router->dir_port && uptime > get_options()->MinUptimeHidServDirectoryV2 && - router->is_running); + node->is_running); } /** Look through the routerlist, the Mean Time Between Failure history, and @@ -1831,19 +1859,22 @@ dirserv_compute_performance_thresholds(routerlist_t *rl) /* Weighted fractional uptime for each active router. */ wfus = tor_malloc(sizeof(double)*smartlist_len(rl->routers)); + nodelist_assert_ok(); + /* Now, fill in the arrays. */ - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { - if (router_is_active(ri, now)) { + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + routerinfo_t *ri = node->ri; + if (ri && router_is_active(ri, node, now)) { const char *id = ri->cache_info.identity_digest; uint32_t bw; - ri->is_exit = (!router_exit_policy_rejects_all(ri) && - exit_policy_is_general_exit(ri->exit_policy)); + node->is_exit = (!router_exit_policy_rejects_all(ri) && + exit_policy_is_general_exit(ri->exit_policy)); uptimes[n_active] = (uint32_t)real_uptime(ri, now); mtbfs[n_active] = rep_hist_get_stability(id, now); tks [n_active] = rep_hist_get_weighted_time_known(id, now); bandwidths[n_active] = bw = router_get_advertised_bandwidth(ri); total_bandwidth += bw; - if (ri->is_exit && !ri->is_bad_exit) { + if (node->is_exit && !node->is_bad_exit) { total_exit_bandwidth += bw; } else { bandwidths_excluding_exits[n_active_nonexit] = bw; @@ -1851,7 +1882,7 @@ dirserv_compute_performance_thresholds(routerlist_t *rl) } ++n_active; } - }); + } SMARTLIST_FOREACH_END(node); /* Now, compute thresholds. */ if (n_active) { @@ -1877,15 +1908,17 @@ dirserv_compute_performance_thresholds(routerlist_t *rl) /* Now that we have a time-known that 7/8 routers are known longer than, * fill wfus with the wfu of every such "familiar" router. */ n_familiar = 0; - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { - if (router_is_active(ri, now)) { + + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + routerinfo_t *ri = node->ri; + if (ri && router_is_active(ri, node, now)) { const char *id = ri->cache_info.identity_digest; long tk = rep_hist_get_weighted_time_known(id, now); if (tk < guard_tk) continue; wfus[n_familiar++] = rep_hist_get_weighted_fractional_uptime(id, now); } - }); + } SMARTLIST_FOREACH_END(node); if (n_familiar) guard_wfu = median_double(wfus, n_familiar); if (guard_wfu > WFU_TO_GUARANTEE_GUARD) @@ -1956,24 +1989,20 @@ version_from_platform(const char *platform) */ int routerstatus_format_entry(char *buf, size_t buf_len, - routerstatus_t *rs, const char *version, + const routerstatus_t *rs, const char *version, routerstatus_format_type_t format) { int r; - struct in_addr in; char *cp; char *summary; char published[ISO_TIME_LEN+1]; - char ipaddr[INET_NTOA_BUF_LEN]; char identity64[BASE64_DIGEST_LEN+1]; char digest64[BASE64_DIGEST_LEN+1]; format_iso_time(published, rs->published_on); digest_to_base64(identity64, rs->identity_digest); digest_to_base64(digest64, rs->descriptor_digest); - in.s_addr = htonl(rs->addr); - tor_inet_ntoa(&in, ipaddr, sizeof(ipaddr)); r = tor_snprintf(buf, buf_len, "r %s %s %s%s%s %s %d %d\n", @@ -1982,7 +2011,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, (format==NS_V3_CONSENSUS_MICRODESC)?"":digest64, (format==NS_V3_CONSENSUS_MICRODESC)?"":" ", published, - ipaddr, + fmt_addr32(rs->addr), (int)rs->or_port, (int)rs->dir_port); if (r<0) { @@ -2010,7 +2039,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, rs->is_possible_guard?" Guard":"", rs->is_hs_dir?" HSDir":"", rs->is_named?" Named":"", - rs->is_running?" Running":"", + rs->is_flagged_running?" Running":"", rs->is_stable?" Stable":"", rs->is_unnamed?" Unnamed":"", rs->is_v2_dir?" V2Dir":"", @@ -2032,7 +2061,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, } if (format != NS_V2) { - routerinfo_t* desc = router_get_by_digest(rs->identity_digest); + const routerinfo_t* desc = router_get_by_id_digest(rs->identity_digest); uint32_t bw; if (format != NS_CONTROL_PORT) { @@ -2128,6 +2157,8 @@ _compare_routerinfo_by_ip_and_bw(const void **a, const void **b) routerinfo_t *first = *(routerinfo_t **)a, *second = *(routerinfo_t **)b; int first_is_auth, second_is_auth; uint32_t bw_first, bw_second; + const node_t *node_first, *node_second; + int first_is_running, second_is_running; /* we return -1 if first should appear before second... that is, * if first is a better router. */ @@ -2150,9 +2181,14 @@ _compare_routerinfo_by_ip_and_bw(const void **a, const void **b) else if (!first_is_auth && second_is_auth) return 1; - else if (first->is_running && !second->is_running) + node_first = node_get_by_id(first->cache_info.identity_digest); + node_second = node_get_by_id(second->cache_info.identity_digest); + first_is_running = node_first && node_first->is_running; + second_is_running = node_second && node_second->is_running; + + if (first_is_running && !second_is_running) return -1; - else if (!first->is_running && second->is_running) + else if (!first_is_running && second_is_running) return 1; bw_first = router_get_advertised_bandwidth(first); @@ -2221,7 +2257,9 @@ get_possible_sybil_list(const smartlist_t *routers) */ void set_routerstatus_from_routerinfo(routerstatus_t *rs, - routerinfo_t *ri, time_t now, + node_t *node, + routerinfo_t *ri, + time_t now, int naming, int listbadexits, int listbaddirs) { @@ -2233,48 +2271,46 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, router_digest_is_trusted_dir(ri->cache_info.identity_digest); /* Already set by compute_performance_thresholds. */ - rs->is_exit = ri->is_exit; - rs->is_stable = ri->is_stable = - router_is_active(ri, now) && + rs->is_exit = node->is_exit; + rs->is_stable = node->is_stable = + router_is_active(ri, node, now) && !dirserv_thinks_router_is_unreliable(now, ri, 1, 0) && !unstable_version; - rs->is_fast = ri->is_fast = - router_is_active(ri, now) && + rs->is_fast = node->is_fast = + router_is_active(ri, node, now) && !dirserv_thinks_router_is_unreliable(now, ri, 0, 1); - rs->is_running = ri->is_running; /* computed above */ + rs->is_flagged_running = node->is_running; /* computed above */ if (naming) { uint32_t name_status = dirserv_get_name_status( - ri->cache_info.identity_digest, ri->nickname); + node->identity, ri->nickname); rs->is_named = (naming && (name_status & FP_NAMED)) ? 1 : 0; rs->is_unnamed = (naming && (name_status & FP_UNNAMED)) ? 1 : 0; } - rs->is_valid = ri->is_valid; + rs->is_valid = node->is_valid; - if (rs->is_fast && + if (node->is_fast && (router_get_advertised_bandwidth(ri) >= BANDWIDTH_TO_GUARANTEE_GUARD || router_get_advertised_bandwidth(ri) >= MIN(guard_bandwidth_including_exits, guard_bandwidth_excluding_exits))) { - long tk = rep_hist_get_weighted_time_known( - ri->cache_info.identity_digest, now); - double wfu = rep_hist_get_weighted_fractional_uptime( - ri->cache_info.identity_digest, now); + long tk = rep_hist_get_weighted_time_known(node->identity, now); + double wfu = rep_hist_get_weighted_fractional_uptime(node->identity, now); rs->is_possible_guard = (wfu >= guard_wfu && tk >= guard_tk) ? 1 : 0; } else { rs->is_possible_guard = 0; } - rs->is_bad_directory = listbaddirs && ri->is_bad_directory; - rs->is_bad_exit = listbadexits && ri->is_bad_exit; - ri->is_hs_dir = dirserv_thinks_router_is_hs_dir(ri, now); - rs->is_hs_dir = ri->is_hs_dir; + rs->is_bad_directory = listbaddirs && node->is_bad_directory; + rs->is_bad_exit = listbadexits && node->is_bad_exit; + node->is_hs_dir = dirserv_thinks_router_is_hs_dir(ri, node, now); + rs->is_hs_dir = node->is_hs_dir; rs->is_v2_dir = ri->dir_port != 0; if (!strcasecmp(ri->nickname, UNNAMED_ROUTER_NICKNAME)) rs->is_named = rs->is_unnamed = 0; rs->published_on = ri->cache_info.published_on; - memcpy(rs->identity_digest, ri->cache_info.identity_digest, DIGEST_LEN); + memcpy(rs->identity_digest, node->identity, DIGEST_LEN); memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest, DIGEST_LEN); rs->addr = ri->addr; @@ -2291,7 +2327,7 @@ static void clear_status_flags_on_sybil(routerstatus_t *rs) { rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = - rs->is_running = rs->is_named = rs->is_valid = rs->is_v2_dir = + rs->is_flagged_running = rs->is_named = rs->is_valid = rs->is_v2_dir = rs->is_hs_dir = rs->is_possible_guard = rs->is_bad_exit = rs->is_bad_directory = 0; /* FFFF we might want some mechanism to check later on if we @@ -2299,18 +2335,6 @@ clear_status_flags_on_sybil(routerstatus_t *rs) * forget to add it to this clause. */ } -/** Clear all the status flags in routerinfo <b>router</b>. We put this - * function here because it's eerily similar to - * clear_status_flags_on_sybil() above. One day we should merge them. */ -void -router_clear_status_flags(routerinfo_t *router) -{ - router->is_valid = router->is_running = router->is_hs_dir = - router->is_fast = router->is_stable = - router->is_possible_guard = router->is_exit = - router->is_bad_exit = router->is_bad_directory = 0; -} - /** * Helper function to parse out a line in the measured bandwidth file * into a measured_bw_line_t output structure. Returns -1 on failure @@ -2428,7 +2452,7 @@ dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses) { char line[256]; - FILE *fp = fopen(from_file, "r"); + FILE *fp = tor_fopen_cloexec(from_file, "r"); int applied_lines = 0; time_t file_time; int ok; @@ -2558,17 +2582,20 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key, routerstatus_t *rs; vote_routerstatus_t *vrs; microdesc_t *md; + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (!node) + continue; vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); rs = &vrs->status; - set_routerstatus_from_routerinfo(rs, ri, now, + set_routerstatus_from_routerinfo(rs, node, ri, now, naming, listbadexits, listbaddirs); if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest)) clear_status_flags_on_sybil(rs); if (!vote_on_reachability) - rs->is_running = 0; + rs->is_flagged_running = 0; vrs->version = version_from_platform(ri->platform); md = dirvote_create_microdescriptor(ri); @@ -2699,10 +2726,8 @@ generate_v2_networkstatus_opinion(void) char *outp, *endp; or_options_t *options = get_options(); char fingerprint[FINGERPRINT_LEN+1]; - char ipaddr[INET_NTOA_BUF_LEN]; char published[ISO_TIME_LEN+1]; char digest[DIGEST_LEN]; - struct in_addr in; uint32_t addr; crypto_pk_env_t *private_key; routerlist_t *rl = router_get_routerlist(); @@ -2723,8 +2748,6 @@ generate_v2_networkstatus_opinion(void) log_warn(LD_NET, "Couldn't resolve my hostname"); goto done; } - in.s_addr = htonl(addr); - tor_inet_ntoa(&in, ipaddr, sizeof(ipaddr)); format_iso_time(published, now); @@ -2770,7 +2793,7 @@ generate_v2_networkstatus_opinion(void) "dir-options%s%s%s%s\n" "%s" /* client version line, server version line. */ "dir-signing-key\n%s", - hostname, ipaddr, (int)options->DirPort, + hostname, fmt_addr32(addr), (int)options->DirPort, fingerprint, contact, published, @@ -2801,8 +2824,12 @@ generate_v2_networkstatus_opinion(void) if (ri->cache_info.published_on >= cutoff) { routerstatus_t rs; char *version = version_from_platform(ri->platform); - - set_routerstatus_from_routerinfo(&rs, ri, now, + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (!node) { + tor_free(version); + continue; + } + set_routerstatus_from_routerinfo(&rs, node, ri, now, naming, listbadexits, listbaddirs); if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest)) @@ -2885,7 +2912,7 @@ dirserv_get_networkstatus_v2_fingerprints(smartlist_t *result, if (!strcmp(key,"authority")) { if (authdir_mode_v2(get_options())) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (me) smartlist_add(result, tor_memdup(me->cache_info.identity_digest, DIGEST_LEN)); @@ -2971,7 +2998,7 @@ dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, smartlist_add(fps_out, tor_memdup(r->cache_info.identity_digest, DIGEST_LEN))); } else if (!strcmp(key, "authority")) { - routerinfo_t *ri = router_get_my_routerinfo(); + const routerinfo_t *ri = router_get_my_routerinfo(); if (ri) smartlist_add(fps_out, tor_memdup(ri->cache_info.identity_digest, DIGEST_LEN)); @@ -2991,8 +3018,8 @@ dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, if (for_unencrypted_conn) { /* Remove anything that insists it not be sent unencrypted. */ - SMARTLIST_FOREACH(fps_out, char *, cp, { - signed_descriptor_t *sd; + SMARTLIST_FOREACH_BEGIN(fps_out, char *, cp) { + const signed_descriptor_t *sd; if (by_id) sd = get_signed_descriptor_by_fp(cp,is_extrainfo,0); else if (is_extrainfo) @@ -3003,7 +3030,7 @@ dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, tor_free(cp); SMARTLIST_DEL_CURRENT(fps_out, cp); } - }); + } SMARTLIST_FOREACH_END(cp); } if (!smartlist_len(fps_out)) { @@ -3042,9 +3069,9 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, smartlist_add(descs_out, &(r->cache_info))); } else if (!strcmp(key, "/tor/server/authority")) { - routerinfo_t *ri = router_get_my_routerinfo(); + const routerinfo_t *ri = router_get_my_routerinfo(); if (ri) - smartlist_add(descs_out, &(ri->cache_info)); + smartlist_add(descs_out, (void*) &(ri->cache_info)); } else if (!strcmpstart(key, "/tor/server/d/")) { smartlist_t *digests = smartlist_create(); key += strlen("/tor/server/d/"); @@ -3068,17 +3095,17 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, { if (router_digest_is_me(d)) { /* make sure desc_routerinfo exists */ - routerinfo_t *ri = router_get_my_routerinfo(); + const routerinfo_t *ri = router_get_my_routerinfo(); if (ri) - smartlist_add(descs_out, &(ri->cache_info)); + smartlist_add(descs_out, (void*) &(ri->cache_info)); } else { - routerinfo_t *ri = router_get_by_digest(d); + const routerinfo_t *ri = router_get_by_id_digest(d); /* Don't actually serve a descriptor that everyone will think is * expired. This is an (ugly) workaround to keep buggy 0.1.1.10 * Tors from downloading descriptors that they will throw away. */ if (ri && ri->cache_info.published_on > cutoff) - smartlist_add(descs_out, &(ri->cache_info)); + smartlist_add(descs_out, (void*) &(ri->cache_info)); } }); SMARTLIST_FOREACH(digests, char *, d, tor_free(d)); @@ -3127,6 +3154,7 @@ dirserv_orconn_tls_done(const char *address, } } }); + /* FFFF Maybe we should reinstate the code that dumps routers with the same * addr/port but with nonmatching keys, but instead of dumping, we should * skip testing. */ @@ -3136,7 +3164,8 @@ dirserv_orconn_tls_done(const char *address, * an upload or a download. Used to decide whether to relaunch reachability * testing for the server. */ int -dirserv_should_launch_reachability_test(routerinfo_t *ri, routerinfo_t *ri_old) +dirserv_should_launch_reachability_test(const routerinfo_t *ri, + const routerinfo_t *ri_old) { if (!authdir_mode_handles_descs(get_options(), ri->purpose)) return 0; @@ -3260,7 +3289,7 @@ dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff) * its extra-info document if <b>extrainfo</b> is true. Return * NULL if not found or if the descriptor is older than * <b>publish_cutoff</b>. */ -static signed_descriptor_t * +static const signed_descriptor_t * get_signed_descriptor_by_fp(const char *fp, int extrainfo, time_t publish_cutoff) { @@ -3270,7 +3299,7 @@ get_signed_descriptor_by_fp(const char *fp, int extrainfo, else return &(router_get_my_routerinfo()->cache_info); } else { - routerinfo_t *ri = router_get_by_digest(fp); + const routerinfo_t *ri = router_get_by_id_digest(fp); if (ri && ri->cache_info.published_on > publish_cutoff) { if (extrainfo) @@ -3338,7 +3367,7 @@ dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs, tor_assert(fps); if (is_serverdescs) { int n = smartlist_len(fps); - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); result = (me?me->cache_info.signed_descriptor_len:2048) * n; if (compressed) result /= 2; /* observed compressibility is between 35 and 55%. */ @@ -3402,10 +3431,10 @@ connection_dirserv_add_servers_to_outbuf(dir_connection_t *conn) time_t publish_cutoff = time(NULL)-ROUTER_MAX_AGE_TO_PUBLISH; while (smartlist_len(conn->fingerprint_stack) && - buf_datalen(conn->_base.outbuf) < DIRSERV_BUFFER_MIN) { + connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { const char *body; char *fp = smartlist_pop_last(conn->fingerprint_stack); - signed_descriptor_t *sd = NULL; + const signed_descriptor_t *sd = NULL; if (by_fp) { sd = get_signed_descriptor_by_fp(fp, extra, publish_cutoff); } else { @@ -3463,7 +3492,7 @@ connection_dirserv_add_microdescs_to_outbuf(dir_connection_t *conn) { microdesc_cache_t *cache = get_microdesc_cache(); while (smartlist_len(conn->fingerprint_stack) && - buf_datalen(conn->_base.outbuf) < DIRSERV_BUFFER_MIN) { + connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { char *fp256 = smartlist_pop_last(conn->fingerprint_stack); microdesc_t *md = microdesc_cache_lookup_by_digest256(cache, fp256); tor_free(fp256); @@ -3502,7 +3531,7 @@ connection_dirserv_add_dir_bytes_to_outbuf(dir_connection_t *conn) ssize_t bytes; int64_t remaining; - bytes = DIRSERV_BUFFER_MIN - buf_datalen(conn->_base.outbuf); + bytes = DIRSERV_BUFFER_MIN - connection_get_outbuf_len(TO_CONN(conn)); tor_assert(bytes > 0); tor_assert(conn->cached_dir); if (bytes < 8192) @@ -3541,7 +3570,7 @@ static int connection_dirserv_add_networkstatus_bytes_to_outbuf(dir_connection_t *conn) { - while (buf_datalen(conn->_base.outbuf) < DIRSERV_BUFFER_MIN) { + while (connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { if (conn->cached_dir) { int uncompressing = (conn->zlib_state != NULL); int r = connection_dirserv_add_dir_bytes_to_outbuf(conn); @@ -3587,7 +3616,7 @@ connection_dirserv_flushed_some(dir_connection_t *conn) { tor_assert(conn->_base.state == DIR_CONN_STATE_SERVER_WRITING); - if (buf_datalen(conn->_base.outbuf) >= DIRSERV_BUFFER_MIN) + if (connection_get_outbuf_len(TO_CONN(conn)) >= DIRSERV_BUFFER_MIN) return 0; switch (conn->dir_spool_src) { diff --git a/src/or/dirserv.h b/src/or/dirserv.h index 56ad7a6a56..0489f3a6a8 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -40,8 +40,6 @@ MAX_V_LINE_LEN \ ) -#define UNNAMED_ROUTER_NICKNAME "Unnamed" - int connection_dirserv_flushed_some(dir_connection_t *conn); int dirserv_add_own_fingerprint(const char *nickname, crypto_pk_env_t *pk); @@ -99,13 +97,19 @@ void dirserv_orconn_tls_done(const char *address, uint16_t or_port, const char *digest_rcvd, int as_advertised); -int dirserv_should_launch_reachability_test(routerinfo_t *ri, - routerinfo_t *ri_old); +int dirserv_should_launch_reachability_test(const routerinfo_t *ri, + const routerinfo_t *ri_old); void dirserv_single_reachability_test(time_t now, routerinfo_t *router); void dirserv_test_reachability(time_t now); int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, - int complain); -int dirserv_would_reject_router(routerstatus_t *rs); + int complain, + int *valid_out); +uint32_t dirserv_router_get_status(const routerinfo_t *router, + const char **msg); +void dirserv_set_node_flags_from_authoritative_status(node_t *node, + uint32_t authstatus); + +int dirserv_would_reject_router(const routerstatus_t *rs); int dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff); int dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src); int dirserv_have_any_microdesc(const smartlist_t *fps); @@ -114,7 +118,7 @@ size_t dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs, size_t dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed); int routerstatus_format_entry(char *buf, size_t buf_len, - routerstatus_t *rs, const char *platform, + const routerstatus_t *rs, const char *platform, routerstatus_format_type_t format); void dirserv_free_all(void); void cached_dir_decref(cached_dir_t *d); diff --git a/src/or/dirvote.c b/src/or/dirvote.c index 41985d1485..8ef5b14a4f 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -83,9 +83,7 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, const char *client_versions = NULL, *server_versions = NULL; char *outp, *endp; char fingerprint[FINGERPRINT_LEN+1]; - char ipaddr[INET_NTOA_BUF_LEN]; char digest[DIGEST_LEN]; - struct in_addr in; uint32_t addr; routerlist_t *rl = router_get_routerlist(); char *version_lines = NULL; @@ -98,8 +96,6 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, voter = smartlist_get(v3_ns->voters, 0); addr = voter->addr; - in.s_addr = htonl(addr); - tor_inet_ntoa(&in, ipaddr, sizeof(ipaddr)); base16_encode(fingerprint, sizeof(fingerprint), v3_ns->cert->cache_info.identity_digest, DIGEST_LEN); @@ -186,7 +182,8 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, flags, params, voter->nickname, fingerprint, voter->address, - ipaddr, voter->dir_port, voter->or_port, voter->contact); + fmt_addr32(addr), voter->dir_port, voter->or_port, + voter->contact); if (r < 0) { log_err(LD_BUG, "Insufficient memory for network status line"); @@ -1529,8 +1526,6 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_sort(dir_sources, _compare_dir_src_ents_by_authority_id); SMARTLIST_FOREACH_BEGIN(dir_sources, const dir_src_ent_t *, e) { - struct in_addr in; - char ip[INET_NTOA_BUF_LEN]; char fingerprint[HEX_DIGEST_LEN+1]; char votedigest[HEX_DIGEST_LEN+1]; networkstatus_t *v = e->v; @@ -1540,8 +1535,6 @@ networkstatus_compute_consensus(smartlist_t *votes, if (e->is_legacy) tor_assert(consensus_method >= 2); - in.s_addr = htonl(voter->addr); - tor_inet_ntoa(&in, ip, sizeof(ip)); base16_encode(fingerprint, sizeof(fingerprint), e->digest, DIGEST_LEN); base16_encode(votedigest, sizeof(votedigest), voter->vote_digest, DIGEST_LEN); @@ -1549,7 +1542,7 @@ networkstatus_compute_consensus(smartlist_t *votes, tor_asprintf(&buf, "dir-source %s%s %s %s %s %d %d\n", voter->nickname, e->is_legacy ? "-legacy" : "", - fingerprint, voter->address, ip, + fingerprint, voter->address, fmt_addr32(voter->addr), voter->dir_port, voter->or_port); smartlist_add(chunks, buf); diff --git a/src/or/dirvote.h b/src/or/dirvote.h index cd5fe86bc1..b2b8d282ed 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -63,6 +63,7 @@ const char *dirvote_get_pending_detached_signatures(void); #define DGV_INCLUDE_PREVIOUS 4 const cached_dir_t *dirvote_get_vote(const char *fp, int flags); void set_routerstatus_from_routerinfo(routerstatus_t *rs, + node_t *node, routerinfo_t *ri, time_t now, int naming, int listbadexits, int listbaddirs); diff --git a/src/or/dns.c b/src/or/dns.c index dcccd1390d..a3ddc7a48b 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -675,7 +675,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, cached_resolve_t *resolve; cached_resolve_t search; pending_connection_t *pending_connection; - routerinfo_t *me; + const routerinfo_t *me; tor_addr_t addr; time_t now = time(NULL); uint8_t is_reverse = 0; diff --git a/src/or/eventdns.c b/src/or/eventdns.c index 75a25bd088..05831197d6 100644 --- a/src/or/eventdns.c +++ b/src/or/eventdns.c @@ -2288,7 +2288,7 @@ _evdns_nameserver_add_impl(const struct sockaddr *address, evtimer_set(&ns->timeout_event, nameserver_prod_callback, ns); - ns->socket = socket(PF_INET, SOCK_DGRAM, 0); + ns->socket = tor_open_socket(PF_INET, SOCK_DGRAM, 0); if (ns->socket < 0) { err = 1; goto out1; } #ifdef WIN32 { @@ -3037,7 +3037,7 @@ evdns_resolv_conf_parse(int flags, const char *const filename) { log(EVDNS_LOG_DEBUG, "Parsing resolv.conf file %s", filename); - fd = open(filename, O_RDONLY); + fd = tor_open_cloexec(filename, O_RDONLY, 0); if (fd < 0) { evdns_resolv_set_defaults(flags); return 1; @@ -3457,7 +3457,7 @@ main(int c, char **v) { if (servertest) { int sock; struct sockaddr_in my_addr; - sock = socket(PF_INET, SOCK_DGRAM, 0); + sock = tor_open_socket(PF_INET, SOCK_DGRAM, 0); fcntl(sock, F_SETFL, O_NONBLOCK); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(10053); diff --git a/src/or/geoip.c b/src/or/geoip.c index d4e279f538..7f0c5c3ee0 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -44,6 +44,9 @@ static strmap_t *country_idxplus1_by_lc_code = NULL; /** A list of all known geoip_entry_t, sorted by ip_low. */ static smartlist_t *geoip_entries = NULL; +/** SHA1 digest of the GeoIP file to include in extra-info descriptors. */ +static char geoip_digest[DIGEST_LEN]; + /** Return the index of the <b>country</b>'s entry in the GeoIP DB * if it is a valid 2-letter country code, otherwise return -1. */ @@ -201,8 +204,9 @@ geoip_load_file(const char *filename, or_options_t *options) FILE *f; const char *msg = ""; int severity = options_need_geoip_info(options, &msg) ? LOG_WARN : LOG_INFO; + crypto_digest_env_t *geoip_digest_env = NULL; clear_geoip_db(); - if (!(f = fopen(filename, "r"))) { + if (!(f = tor_fopen_cloexec(filename, "r"))) { log_fn(severity, LD_GENERAL, "Failed to open GEOIP file %s. %s", filename, msg); return -1; @@ -214,11 +218,13 @@ geoip_load_file(const char *filename, or_options_t *options) smartlist_free(geoip_entries); } geoip_entries = smartlist_create(); + geoip_digest_env = crypto_new_digest_env(); log_notice(LD_GENERAL, "Parsing GEOIP file."); while (!feof(f)) { char buf[512]; if (fgets(buf, (int)sizeof(buf), f) == NULL) break; + crypto_digest_add_bytes(geoip_digest_env, buf, strlen(buf)); /* FFFF track full country name. */ geoip_parse_entry(buf); } @@ -231,6 +237,11 @@ geoip_load_file(const char *filename, or_options_t *options) * country. */ refresh_all_country_info(); + /* Remember file digest so that we can include it in our extra-info + * descriptors. */ + crypto_digest_get_digest(geoip_digest_env, geoip_digest, DIGEST_LEN); + crypto_free_digest_env(geoip_digest_env); + return 0; } @@ -278,6 +289,15 @@ geoip_is_loaded(void) return geoip_countries != NULL && geoip_entries != NULL; } +/** Return the hex-encoded SHA1 digest of the loaded GeoIP file. The + * result does not need to be deallocated, but will be overwritten by the + * next call of hex_str(). */ +const char * +geoip_db_digest(void) +{ + return hex_str(geoip_digest, DIGEST_LEN); +} + /** Entry in a map from IP address to the last time we've seen an incoming * connection from that IP address. Used by bridges only, to track which * countries have them blocked. */ diff --git a/src/or/geoip.h b/src/or/geoip.h index bafbeea0f2..1a8baae416 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -21,6 +21,7 @@ int geoip_get_country_by_ip(uint32_t ipaddr); int geoip_get_n_countries(void); const char *geoip_get_country_name(country_t num); int geoip_is_loaded(void); +const char *geoip_db_digest(void); country_t geoip_get_country(const char *countrycode); void geoip_note_client_seen(geoip_client_action_t action, diff --git a/src/or/main.c b/src/or/main.c index 4b512905c3..aa97609442 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -33,6 +33,7 @@ #include "main.h" #include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "ntmain.h" #include "onion.h" #include "policies.h" @@ -56,6 +57,10 @@ #include <event.h> #endif +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent.h> +#endif + void evdns_shutdown(int); /********* PROTOTYPES **********/ @@ -72,6 +77,7 @@ static int connection_should_read_from_linked_conn(connection_t *conn); /********* START VARIABLES **********/ +#ifndef USE_BUFFEREVENTS int global_read_bucket; /**< Max number of bytes I can read this second. */ int global_write_bucket; /**< Max number of bytes I can write this second. */ @@ -79,13 +85,17 @@ int global_write_bucket; /**< Max number of bytes I can write this second. */ int global_relayed_read_bucket; /** Max number of relayed (bandwidth class 1) bytes I can write this second. */ int global_relayed_write_bucket; - /** What was the read bucket before the last second_elapsed_callback() call? * (used to determine how many bytes we've read). */ static int stats_prev_global_read_bucket; /** What was the write bucket before the last second_elapsed_callback() call? * (used to determine how many bytes we've written). */ static int stats_prev_global_write_bucket; +#else +static uint64_t stats_prev_n_read = 0; +static uint64_t stats_prev_n_written = 0; +#endif + /* XXX we might want to keep stats about global_relayed_*_bucket too. Or not.*/ /** How many bytes have we read since we started the process? */ static uint64_t stats_n_bytes_read = 0; @@ -151,12 +161,38 @@ int can_complete_circuit=0; * ****************************************************************************/ +#if 0 && defined(USE_BUFFEREVENTS) +static void +free_old_inbuf(connection_t *conn) +{ + if (! conn->inbuf) + return; + + tor_assert(conn->outbuf); + tor_assert(buf_datalen(conn->inbuf) == 0); + tor_assert(buf_datalen(conn->outbuf) == 0); + buf_free(conn->inbuf); + buf_free(conn->outbuf); + conn->inbuf = conn->outbuf = NULL; + + if (conn->read_event) { + event_del(conn->read_event); + tor_event_free(conn->read_event); + } + if (conn->write_event) { + event_del(conn->read_event); + tor_event_free(conn->write_event); + } + conn->read_event = conn->write_event = NULL; +} +#endif + /** Add <b>conn</b> to the array of connections that we can poll on. The * connection's socket must be set; the connection starts out * non-reading and non-writing. */ int -connection_add(connection_t *conn) +connection_add_impl(connection_t *conn, int is_connecting) { tor_assert(conn); tor_assert(conn->s >= 0 || @@ -168,11 +204,63 @@ connection_add(connection_t *conn) conn->conn_array_index = smartlist_len(connection_array); smartlist_add(connection_array, conn); - if (conn->s >= 0 || conn->linked) { +#ifdef USE_BUFFEREVENTS + if (connection_type_uses_bufferevent(conn)) { + if (conn->s >= 0 && !conn->linked) { + conn->bufev = bufferevent_socket_new( + tor_libevent_get_base(), + conn->s, + BEV_OPT_DEFER_CALLBACKS); + if (!conn->bufev) { + log_warn(LD_BUG, "Unable to create socket bufferevent"); + smartlist_del(connection_array, conn->conn_array_index); + conn->conn_array_index = -1; + return -1; + } + if (is_connecting) { + /* Put the bufferevent into a "connecting" state so that we'll get + * a "connected" event callback on successful write. */ + bufferevent_socket_connect(conn->bufev, NULL, 0); + } + connection_configure_bufferevent_callbacks(conn); + } else if (conn->linked && conn->linked_conn && + connection_type_uses_bufferevent(conn->linked_conn)) { + tor_assert(conn->s < 0); + if (!conn->bufev) { + struct bufferevent *pair[2] = { NULL, NULL }; + if (bufferevent_pair_new(tor_libevent_get_base(), + BEV_OPT_DEFER_CALLBACKS, + pair) < 0) { + log_warn(LD_BUG, "Unable to create bufferevent pair"); + smartlist_del(connection_array, conn->conn_array_index); + conn->conn_array_index = -1; + return -1; + } + tor_assert(pair[0]); + conn->bufev = pair[0]; + conn->linked_conn->bufev = pair[1]; + } /* else the other side already was added, and got a bufferevent_pair */ + connection_configure_bufferevent_callbacks(conn); + } else { + tor_assert(!conn->linked); + } + + if (conn->bufev) + tor_assert(conn->inbuf == NULL); + + if (conn->linked_conn && conn->linked_conn->bufev) + tor_assert(conn->linked_conn->inbuf == NULL); + } +#else + (void) is_connecting; +#endif + + if (!HAS_BUFFEREVENT(conn) && (conn->s >= 0 || conn->linked)) { conn->read_event = tor_event_new(tor_libevent_get_base(), conn->s, EV_READ|EV_PERSIST, conn_read_callback, conn); conn->write_event = tor_event_new(tor_libevent_get_base(), conn->s, EV_WRITE|EV_PERSIST, conn_write_callback, conn); + /* XXXX CHECK FOR NULL RETURN! */ } log_debug(LD_NET,"new conn type %s, socket %d, address %s, n_conns %d.", @@ -196,6 +284,12 @@ connection_unregister_events(connection_t *conn) log_warn(LD_BUG, "Error removing write event for %d", conn->s); tor_free(conn->write_event); } +#ifdef USE_BUFFEREVENTS + if (conn->bufev) { + bufferevent_free(conn->bufev); + conn->bufev = NULL; + } +#endif if (conn->dns_server_port) { dnsserv_close_listener(conn); } @@ -304,12 +398,37 @@ get_connection_array(void) return connection_array; } +/** Provides the traffic read and written over the life of the process. */ + +uint64_t +get_bytes_read(void) +{ + return stats_n_bytes_read; +} + +uint64_t +get_bytes_written(void) +{ + return stats_n_bytes_written; +} + /** Set the event mask on <b>conn</b> to <b>events</b>. (The event * mask is a bitmask whose bits are READ_EVENT and WRITE_EVENT) */ void connection_watch_events(connection_t *conn, watchable_events_t events) { + IF_HAS_BUFFEREVENT(conn, { + short ev = ((short)events) & (EV_READ|EV_WRITE); + short old_ev = bufferevent_get_enabled(conn->bufev); + if ((ev & ~old_ev) != 0) { + bufferevent_enable(conn->bufev, ev); + } + if ((old_ev & ~ev) != 0) { + bufferevent_disable(conn->bufev, old_ev & ~ev); + } + return; + }); if (events & READ_EVENT) connection_start_reading(conn); else @@ -327,6 +446,9 @@ connection_is_reading(connection_t *conn) { tor_assert(conn); + IF_HAS_BUFFEREVENT(conn, + return (bufferevent_get_enabled(conn->bufev) & EV_READ) != 0; + ); return conn->reading_from_linked_conn || (conn->read_event && event_pending(conn->read_event, EV_READ, NULL)); } @@ -336,6 +458,12 @@ void connection_stop_reading(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_disable(conn->bufev, EV_READ); + return; + }); + tor_assert(conn->read_event); if (conn->linked) { @@ -355,6 +483,12 @@ void connection_start_reading(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_enable(conn->bufev, EV_READ); + return; + }); + tor_assert(conn->read_event); if (conn->linked) { @@ -376,6 +510,10 @@ connection_is_writing(connection_t *conn) { tor_assert(conn); + IF_HAS_BUFFEREVENT(conn, + return (bufferevent_get_enabled(conn->bufev) & EV_WRITE) != 0; + ); + return conn->writing_to_linked_conn || (conn->write_event && event_pending(conn->write_event, EV_WRITE, NULL)); } @@ -385,6 +523,12 @@ void connection_stop_writing(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_disable(conn->bufev, EV_WRITE); + return; + }); + tor_assert(conn->write_event); if (conn->linked) { @@ -405,6 +549,12 @@ void connection_start_writing(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_enable(conn->bufev, EV_WRITE); + return; + }); + tor_assert(conn->write_event); if (conn->linked) { @@ -590,7 +740,23 @@ conn_close_if_marked(int i) assert_connection_ok(conn, now); /* assert_all_pending_dns_resolves_ok(); */ +#ifdef USE_BUFFEREVENTS + if (conn->bufev) { + if (conn->hold_open_until_flushed && + evbuffer_get_length(bufferevent_get_output(conn->bufev))) { + /* don't close yet. */ + return 0; + } + if (conn->linked_conn && ! conn->linked_conn->marked_for_close) { + /* We need to do this explicitly so that the linked connection + * notices that there was an EOF. */ + bufferevent_flush(conn->bufev, EV_WRITE, BEV_FINISHED); + } + } +#endif + log_debug(LD_NET,"Cleaning up connection (fd %d).",conn->s); + IF_HAS_BUFFEREVENT(conn, goto unlink); if ((conn->s >= 0 || conn->linked_conn) && connection_wants_to_flush(conn)) { /* s == -1 means it's an incomplete edge connection, or that the socket * has already been closed as unflushable. */ @@ -613,8 +779,8 @@ conn_close_if_marked(int i) } log_debug(LD_GENERAL, "Flushed last %d bytes from a linked conn; " "%d left; flushlen %d; wants-to-flush==%d", retval, - (int)buf_datalen(conn->outbuf), - (int)conn->outbuf_flushlen, + (int)connection_get_outbuf_len(conn), + (int)conn->outbuf_flushlen, connection_wants_to_flush(conn)); } else if (connection_speaks_cells(conn)) { if (conn->state == OR_CONN_STATE_OPEN) { @@ -651,13 +817,17 @@ conn_close_if_marked(int i) "something is wrong with your network connection, or " "something is wrong with theirs. " "(fd %d, type %s, state %d, marked at %s:%d).", - (int)buf_datalen(conn->outbuf), + (int)connection_get_outbuf_len(conn), escaped_safe_str_client(conn->address), conn->s, conn_type_to_string(conn->type), conn->state, conn->marked_for_close_file, conn->marked_for_close); } } + +#ifdef USE_BUFFEREVENTS + unlink: +#endif connection_unlink(conn); /* unlink, remove, free */ return 1; } @@ -703,10 +873,13 @@ directory_info_has_arrived(time_t now, int from_cache) "I learned some more directory information, but not enough to " "build a circuit: %s", get_dir_info_status_string()); update_router_descriptor_downloads(now); + update_microdesc_downloads(now); return; } else { - if (directory_fetches_from_authorities(options)) + if (directory_fetches_from_authorities(options)) { update_router_descriptor_downloads(now); + update_microdesc_downloads(now); + } /* if we have enough dir info, then update our guard status with * whatever we just learned. */ @@ -744,7 +917,8 @@ run_connection_housekeeping(int i, time_t now) int past_keepalive = now >= conn->timestamp_lastwritten + options->KeepalivePeriod; - if (conn->outbuf && !buf_datalen(conn->outbuf) && conn->type == CONN_TYPE_OR) + if (conn->outbuf && !connection_get_outbuf_len(conn) && + conn->type == CONN_TYPE_OR) TO_OR_CONN(conn)->timestamp_lastempty = now; if (conn->marked_for_close) { @@ -764,7 +938,7 @@ run_connection_housekeeping(int i, time_t now) /* This check is temporary; it's to let us know whether we should consider * parsing partial serverdesc responses. */ if (conn->purpose == DIR_PURPOSE_FETCH_SERVERDESC && - buf_datalen(conn->inbuf)>=1024) { + connection_get_inbuf_len(conn) >= 1024) { log_info(LD_DIR,"Trying to extract information from wedged server desc " "download."); connection_dir_reached_eof(TO_DIR_CONN(conn)); @@ -781,7 +955,11 @@ run_connection_housekeeping(int i, time_t now) the connection or send a keepalive, depending. */ or_conn = TO_OR_CONN(conn); +#ifdef USE_BUFFEREVENTS + tor_assert(conn->bufev); +#else tor_assert(conn->outbuf); +#endif if (or_conn->is_bad_for_new_circs && !or_conn->n_circuits) { /* It's bad for new circuits, and has no unmarked circuits on it: @@ -793,8 +971,7 @@ run_connection_housekeeping(int i, time_t now) connection_or_connect_failed(TO_OR_CONN(conn), END_OR_CONN_REASON_TIMEOUT, "Tor gave up on the connection"); - connection_mark_for_close(conn); - conn->hold_open_until_flushed = 1; + connection_mark_and_flush(conn); } else if (!connection_state_is_open(conn)) { if (past_keepalive) { /* We never managed to actually get this connection open and happy. */ @@ -803,13 +980,12 @@ run_connection_housekeeping(int i, time_t now) connection_mark_for_close(conn); } } else if (we_are_hibernating() && !or_conn->n_circuits && - !buf_datalen(conn->outbuf)) { + !connection_get_outbuf_len(conn)) { /* We're hibernating, there's no circuits, and nothing to flush.*/ log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) " "[Hibernating or exiting].", conn->s,conn->address, conn->port); - connection_mark_for_close(conn); - conn->hold_open_until_flushed = 1; + connection_mark_and_flush(conn); } else if (!or_conn->n_circuits && now >= or_conn->timestamp_last_added_nonpadding + IDLE_OR_CONN_TIMEOUT) { @@ -817,7 +993,6 @@ run_connection_housekeeping(int i, time_t now) "[idle %d].", conn->s,conn->address, conn->port, (int)(now - or_conn->timestamp_last_added_nonpadding)); connection_mark_for_close(conn); - conn->hold_open_until_flushed = 1; } else if ( now >= or_conn->timestamp_lastempty + options->KeepalivePeriod*10 && now >= conn->timestamp_lastwritten + options->KeepalivePeriod*10) { @@ -825,10 +1000,10 @@ run_connection_housekeeping(int i, time_t now) "Expiring stuck OR connection to fd %d (%s:%d). (%d bytes to " "flush; %d seconds since last write)", conn->s, conn->address, conn->port, - (int)buf_datalen(conn->outbuf), + (int)connection_get_outbuf_len(conn), (int)(now-conn->timestamp_lastwritten)); connection_mark_for_close(conn); - } else if (past_keepalive && !buf_datalen(conn->outbuf)) { + } else if (past_keepalive && !connection_get_outbuf_len(conn)) { /* send a padding cell */ log_fn(LOG_DEBUG,LD_OR,"Sending keepalive to (%s:%d)", conn->address, conn->port); @@ -872,6 +1047,7 @@ run_scheduled_events(time_t now) static time_t time_to_check_for_expired_networkstatus = 0; static time_t time_to_write_stats_files = 0; static time_t time_to_write_bridge_stats = 0; + static time_t time_to_check_port_forwarding = 0; static int should_init_bridge_stats = 1; static time_t time_to_retry_dns_init = 0; or_options_t *options = get_options(); @@ -885,6 +1061,16 @@ run_scheduled_events(time_t now) */ consider_hibernation(now); +#if 0 + { + static time_t nl_check_time = 0; + if (nl_check_time <= now) { + nodelist_assert_ok(); + nl_check_time = now + 30; + } + } +#endif + /* 0b. If we've deferred a signewnym, make sure it gets handled * eventually. */ if (signewnym_is_pending && @@ -915,6 +1101,7 @@ run_scheduled_events(time_t now) if (time_to_try_getting_descriptors < now) { update_router_descriptor_downloads(now); update_extrainfo_downloads(now); + update_microdesc_downloads(now); if (options->UseBridges) fetch_bridge_descriptors(options, now); if (router_have_minimum_dir_info()) @@ -1034,6 +1221,11 @@ run_scheduled_events(time_t now) if (next_write && next_write < next_time_to_write_stats_files) next_time_to_write_stats_files = next_write; } + if (options->ConnDirectionStatistics) { + time_t next_write = rep_hist_conn_stats_write(time_to_write_stats_files); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } time_to_write_stats_files = next_time_to_write_stats_files; } @@ -1114,7 +1306,7 @@ run_scheduled_events(time_t now) /* If we haven't checked for 12 hours and our bandwidth estimate is * low, do another bandwidth test. This is especially important for * bridges, since they might go long periods without much use. */ - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (time_to_recheck_bandwidth && me && me->bandwidthcapacity < me->bandwidthrate && me->bandwidthcapacity < 51200) { @@ -1237,6 +1429,17 @@ run_scheduled_events(time_t now) #define BRIDGE_STATUSFILE_INTERVAL (30*60) time_to_write_bridge_status_file = now+BRIDGE_STATUSFILE_INTERVAL; } + + if (time_to_check_port_forwarding < now && + options->PortForwarding && + server_mode(options)) { +#define PORT_FORWARDING_CHECK_INTERVAL 5 + tor_check_port_forwarding(options->PortForwardingHelper, + options->DirPort, + options->ORPort, + now); + time_to_check_port_forwarding = now+PORT_FORWARDING_CHECK_INTERVAL; + } } /** Timer: used to invoke second_elapsed_callback() once per second. */ @@ -1256,6 +1459,9 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) size_t bytes_written; size_t bytes_read; int seconds_elapsed; +#ifdef USE_BUFFEREVENTS + uint64_t cur_read,cur_written; +#endif or_options_t *options = get_options(); (void)timer; (void)arg; @@ -1267,9 +1473,15 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) update_approx_time(now); /* the second has rolled over. check more stuff. */ + seconds_elapsed = current_second ? (int)(now - current_second) : 0; +#ifdef USE_BUFFEREVENTS + connection_get_rate_limit_totals(&cur_read, &cur_written); + bytes_written = (size_t)(cur_written - stats_prev_n_written); + bytes_read = (size_t)(cur_read - stats_prev_n_read); +#else bytes_written = stats_prev_global_write_bucket - global_write_bucket; bytes_read = stats_prev_global_read_bucket - global_read_bucket; - seconds_elapsed = current_second ? (int)(now - current_second) : 0; +#endif stats_n_bytes_read += bytes_read; stats_n_bytes_written += bytes_written; if (accounting_is_enabled(options) && seconds_elapsed >= 0) @@ -1279,8 +1491,13 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) if (seconds_elapsed > 0) connection_bucket_refill(seconds_elapsed, now); +#ifdef USE_BUFFEREVENTS + stats_prev_n_written = cur_written; + stats_prev_n_read = cur_read; +#else stats_prev_global_read_bucket = global_read_bucket; stats_prev_global_write_bucket = global_write_bucket; +#endif if (server_mode(options) && !we_are_hibernating() && @@ -1290,7 +1507,7 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) (stats_n_seconds_working+seconds_elapsed) / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { /* every 20 minutes, check and complain if necessary */ - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (me && !check_whether_orport_reachable()) { log_warn(LD_CONFIG,"Your server (%s:%d) has not managed to confirm that " "its ORPort is reachable. Please check your firewalls, ports, " @@ -1481,8 +1698,10 @@ do_main_loop(void) /* Set up our buckets */ connection_bucket_init(); +#ifndef USE_BUFFEREVENTS stats_prev_global_read_bucket = global_read_bucket; stats_prev_global_write_bucket = global_write_bucket; +#endif /* initialize the bootstrap status events to know we're starting up */ control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0); @@ -1643,11 +1862,13 @@ signal_callback(int fd, short events, void *arg) case SIGUSR1: /* prefer to log it at INFO, but make sure we always see it */ dumpstats(get_min_log_level()<LOG_INFO ? get_min_log_level() : LOG_INFO); + control_event_signal(sig); break; case SIGUSR2: switch_logs_debug(); log_debug(LD_GENERAL,"Caught USR2, going to loglevel debug. " "Send HUP to change back."); + control_event_signal(sig); break; case SIGHUP: if (do_hup() < 0) { @@ -1655,6 +1876,7 @@ signal_callback(int fd, short events, void *arg) tor_cleanup(); exit(1); } + control_event_signal(sig); break; #ifdef SIGCHLD case SIGCHLD: @@ -1671,11 +1893,13 @@ signal_callback(int fd, short events, void *arg) (int)(MAX_SIGNEWNYM_RATE+time_of_last_signewnym-now)); } else { signewnym_impl(now); + control_event_signal(sig); } break; } case SIGCLEARDNSCACHE: addressmap_clear_transient(); + control_event_signal(sig); break; } } @@ -1726,13 +1950,13 @@ dumpstats(int severity) log(severity,LD_GENERAL, "Conn %d: %d bytes waiting on inbuf (len %d, last read %d secs ago)", i, - (int)buf_datalen(conn->inbuf), + (int)connection_get_inbuf_len(conn), (int)buf_allocation(conn->inbuf), (int)(now - conn->timestamp_lastread)); log(severity,LD_GENERAL, "Conn %d: %d bytes waiting on outbuf " "(len %d, last written %d secs ago)",i, - (int)buf_datalen(conn->outbuf), + (int)connection_get_outbuf_len(conn), (int)buf_allocation(conn->outbuf), (int)(now - conn->timestamp_lastwritten)); if (conn->type == CONN_TYPE_OR) { @@ -2029,6 +2253,7 @@ tor_free_all(int postfork) connection_free_all(); buf_shrink_freelists(1); memarea_clear_freelist(); + nodelist_free_all(); microdesc_free_all(); if (!postfork) { config_free_all(); diff --git a/src/or/main.h b/src/or/main.h index d399aaaaf3..4e15d4dacb 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -14,7 +14,9 @@ extern int can_complete_circuit; -int connection_add(connection_t *conn); +int connection_add_impl(connection_t *conn, int is_connecting); +#define connection_add(conn) connection_add_impl((conn), 0) +#define connection_add_connecting(conn) connection_add_impl((conn), 1) int connection_remove(connection_t *conn); void connection_unregister_events(connection_t *conn); int connection_in_array(connection_t *conn); @@ -22,8 +24,11 @@ void add_connection_to_closeable_list(connection_t *conn); int connection_is_on_closeable_list(connection_t *conn); smartlist_t *get_connection_array(void); +uint64_t get_bytes_read(void); +uint64_t get_bytes_written(void); typedef enum watchable_events { + /* Yes, it is intentional that these match Libevent's EV_READ and EV_WRITE */ READ_EVENT=0x02, WRITE_EVENT=0x04 } watchable_events_t; diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 2c4b3435f7..2480b8e3e0 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -3,7 +3,13 @@ #include "or.h" #include "config.h" +#include "directory.h" +#include "dirserv.h" #include "microdesc.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "policies.h" +#include "routerlist.h" #include "routerparse.h" /** A data structure to hold a bunch of cached microdescriptors. There are @@ -23,6 +29,8 @@ struct microdesc_cache_t { tor_mmap_t *cache_content; /** Number of bytes used in the journal file. */ size_t journal_len; + /** Number of bytes in descriptors removed as too old. */ + size_t bytes_dropped; /** Total bytes of microdescriptor bodies we have added to this cache */ uint64_t total_len_seen; @@ -119,15 +127,19 @@ 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. Return a list of the added - * microdescriptors. */ + * <b>SAVED_NOWHERE</b>, do not allow annotations. If listed_at is positive, + * 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 + * digest, and alter the list to contain only the digests of those microdescs + * we didn't find. + * Return a list of the added microdescriptors. */ smartlist_t * microdescs_add_to_cache(microdesc_cache_t *cache, const char *s, const char *eos, saved_location_t where, - int no_save) + int no_save, time_t listed_at, + smartlist_t *requested_digests256) { - /*XXXX need an argument that sets last_listed as appropriate. */ - smartlist_t *descriptors, *added; const int allow_annotations = (where != SAVED_NOWHERE); const int copy_body = (where != SAVED_IN_CACHE); @@ -135,6 +147,33 @@ microdescs_add_to_cache(microdesc_cache_t *cache, descriptors = microdescs_parse_from_string(s, eos, allow_annotations, copy_body); + if (listed_at > 0) { + SMARTLIST_FOREACH(descriptors, microdesc_t *, md, + md->last_listed = listed_at); + } + if (requested_digests256) { + digestmap_t *requested; /* XXXX actuqlly we should just use a + digest256map */ + requested = digestmap_new(); + SMARTLIST_FOREACH(requested_digests256, const char *, cp, + digestmap_set(requested, cp, (void*)1)); + SMARTLIST_FOREACH_BEGIN(descriptors, microdesc_t *, md) { + if (digestmap_get(requested, md->digest)) { + digestmap_set(requested, md->digest, (void*)2); + } else { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Received non-requested microcdesc"); + microdesc_free(md); + SMARTLIST_DEL_CURRENT(descriptors, md); + } + } SMARTLIST_FOREACH_END(md); + SMARTLIST_FOREACH_BEGIN(requested_digests256, char *, cp) { + if (digestmap_get(requested, cp) == (void*)2) { + tor_free(cp); + SMARTLIST_DEL_CURRENT(requested_digests256, cp); + } + } SMARTLIST_FOREACH_END(cp); + digestmap_free(requested, NULL); + } added = microdescs_add_list_to_cache(cache, descriptors, where, no_save); smartlist_free(descriptors); @@ -207,10 +246,15 @@ microdescs_add_list_to_cache(microdesc_cache_t *cache, { size_t old_content_len = cache->cache_content ? cache->cache_content->size : 0; - if (cache->journal_len > 16384 + old_content_len && - cache->journal_len > old_content_len * 2) { + if ((cache->journal_len > 16384 + old_content_len && + cache->journal_len > old_content_len / 2)) microdesc_cache_rebuild(cache); - } + } + + { + networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && ns->flavor == FLAV_MICRODESC) + SMARTLIST_FOREACH(added, microdesc_t *, md, nodelist_add_microdesc(md)); } return added; @@ -251,7 +295,7 @@ microdesc_cache_reload(microdesc_cache_t *cache) mm = cache->cache_content = tor_mmap_file(cache->cache_fname); if (mm) { added = microdescs_add_to_cache(cache, mm->data, mm->data+mm->size, - SAVED_IN_CACHE, 0); + SAVED_IN_CACHE, 0, -1, NULL); if (added) { total += smartlist_len(added); smartlist_free(added); @@ -263,7 +307,7 @@ microdesc_cache_reload(microdesc_cache_t *cache) if (journal_content) { added = microdescs_add_to_cache(cache, journal_content, journal_content+st.st_size, - SAVED_IN_JOURNAL, 0); + SAVED_IN_JOURNAL, 0, -1, NULL); if (added) { total += smartlist_len(added); smartlist_free(added); @@ -275,6 +319,53 @@ microdesc_cache_reload(microdesc_cache_t *cache) return 0; } +/** By default, we remove any microdescriptors that have gone at least this + * long without appearing in a current consensus. */ +#define TOLERATE_MICRODESC_AGE (7*24*60*60) + +/** Remove all microdescriptors from <b>cache</b> that haven't been listed for + * a long time. Does not rebuild the cache on disk. If <b>cutoff</b> is + * positive, specifically remove microdescriptors that have been unlisted + * since <b>cutoff</b>. If <b>force</b> is true, remove microdescriptors even + * if we have no current live microdescriptor consensus. + */ +void +microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force) +{ + microdesc_t **mdp, *victim; + int dropped=0, kept=0; + size_t bytes_dropped = 0; + time_t now = time(NULL); + + /* If we don't know a live consensus, don't believe last_listed values: we + * might be starting up after being down for a while. */ + if (! force && + ! networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC)) + return; + + if (cutoff <= 0) + cutoff = now - TOLERATE_MICRODESC_AGE; + + for (mdp = HT_START(microdesc_map, &cache->map); mdp != NULL; ) { + if ((*mdp)->last_listed < cutoff) { + ++dropped; + victim = *mdp; + mdp = HT_NEXT_RMV(microdesc_map, &cache->map, mdp); + bytes_dropped += victim->bodylen; + microdesc_free(victim); + } else { + ++kept; + mdp = HT_NEXT(microdesc_map, &cache->map, mdp); + } + } + + if (dropped) { + log_notice(LD_DIR, "Removed %d/%d microdescriptors as old.", + dropped,dropped+kept); + cache->bytes_dropped += bytes_dropped; + } +} + /** Regenerate the main cache file for <b>cache</b>, clear the journal file, * and update every microdesc_t in the cache with pointers to its new * location. */ @@ -290,6 +381,11 @@ microdesc_cache_rebuild(microdesc_cache_t *cache) int orig_size, new_size; log_info(LD_DIR, "Rebuilding the microdescriptor cache..."); + + /* Remove dead descriptors */ + microdesc_cache_clean(cache, 0/*cutoff*/, 0/*force*/); + + /* Calculate starting disk usage */ orig_size = (int)(cache->cache_content ? cache->cache_content->size : 0); orig_size += (int)cache->journal_len; @@ -313,6 +409,7 @@ microdesc_cache_rebuild(microdesc_cache_t *cache) /* log? return -1? die? coredump the universe? */ continue; } + tor_assert(((size_t)size) == annotation_len + md->bodylen); md->off = off + annotation_len; off += size; if (md->saved_location != SAVED_IN_CACHE) { @@ -337,13 +434,28 @@ microdesc_cache_rebuild(microdesc_cache_t *cache) SMARTLIST_FOREACH_BEGIN(wrote, microdesc_t *, md) { tor_assert(md->saved_location == SAVED_IN_CACHE); md->body = (char*)cache->cache_content->data + md->off; - tor_assert(!memcmp(md->body, "onion-key", 9)); + if (PREDICT_UNLIKELY( + md->bodylen < 9 || memcmp(md->body, "onion-key", 9) != 0)) { + /* XXXX023 once bug 2022 is solved, we can kill this block and turn it + * into just the tor_assert(!memcmp) */ + off_t avail = cache->cache_content->size - md->off; + char *bad_str; + tor_assert(avail >= 0); + bad_str = tor_strndup(md->body, MIN(128, (size_t)avail)); + log_err(LD_BUG, "After rebuilding microdesc cache, offsets seem wrong. " + " At offset %d, I expected to find a microdescriptor starting " + " with \"onion-key\". Instead I got %s.", + (int)md->off, escaped(bad_str)); + tor_free(bad_str); + tor_assert(!memcmp(md->body, "onion-key", 9)); + } } SMARTLIST_FOREACH_END(md); smartlist_free(wrote); write_str_to_file(cache->journal_fname, "", 1); cache->journal_len = 0; + cache->bytes_dropped = 0; new_size = (int)cache->cache_content->size; log_info(LD_DIR, "Done rebuilding microdesc cache. " @@ -370,7 +482,7 @@ microdesc_free(microdesc_t *md) SMARTLIST_FOREACH(md->family, char *, cp, tor_free(cp)); smartlist_free(md->family); } - tor_free(md->exitsummary); + short_policy_free(md->exit_policy); tor_free(md); } @@ -412,3 +524,100 @@ microdesc_average_size(microdesc_cache_t *cache) return (size_t)(cache->total_len_seen / cache->n_seen); } +/** Return a smartlist of all the sha256 digest of the microdescriptors that + * are listed in <b>ns</b> but not present in <b>cache</b>. Returns pointers + * to internals of <b>ns</b>; you should not free the members of the resulting + * smartlist. Omit all microdescriptors whose digest appear in <b>skip</b>. */ +smartlist_t * +microdesc_list_missing_digest256(networkstatus_t *ns, microdesc_cache_t *cache, + int downloadable_only, digestmap_t *skip) +{ + smartlist_t *result = smartlist_create(); + time_t now = time(NULL); + tor_assert(ns->flavor == FLAV_MICRODESC); + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + if (microdesc_cache_lookup_by_digest256(cache, rs->descriptor_digest)) + continue; + if (downloadable_only && + !download_status_is_ready(&rs->dl_status, now, + MAX_MICRODESC_DOWNLOAD_FAILURES)) + continue; + if (skip && digestmap_get(skip, rs->descriptor_digest)) + continue; + /* XXXX Also skip if we're a noncache and wouldn't use this router. + * XXXX NM Microdesc + */ + smartlist_add(result, rs->descriptor_digest); + } SMARTLIST_FOREACH_END(rs); + return result; +} + +/** Launch download requests for mircodescriptors as appropriate. + * + * Specifically, we should launch download requests if we are configured to + * download mirodescriptors, and there are some microdescriptors listed the + * current microdesc consensus that we don't have, and either we never asked + * for them, or we failed to download them but we're willing to retry. + */ +void +update_microdesc_downloads(time_t now) +{ + or_options_t *options = get_options(); + networkstatus_t *consensus; + smartlist_t *missing; + digestmap_t *pending; + + if (should_delay_dir_fetches(options)) + return; + if (directory_too_idle_to_fetch_descriptors(options, now)) + return; + + consensus = networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC); + if (!consensus) + return; + + if (!directory_caches_dir_info(options)) { + /* Right now, only caches fetch microdescriptors. + * XXXX NM Microdescs */ + return; + } + + pending = digestmap_new(); + list_pending_microdesc_downloads(pending); + + missing = microdesc_list_missing_digest256(consensus, + get_microdesc_cache(), + 1, + pending); + digestmap_free(pending, NULL); + + launch_descriptor_downloads(DIR_PURPOSE_FETCH_MICRODESC, + missing, NULL, now); + + smartlist_free(missing); +} + +/** For every microdescriptor listed in the current microdecriptor consensus, + * update its last_listed field to be at least as recent as the publication + * time of the current microdescriptor consensus. + */ +void +update_microdescs_from_networkstatus(time_t now) +{ + microdesc_cache_t *cache = get_microdesc_cache(); + microdesc_t *md; + networkstatus_t *ns = + networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC); + + if (! ns) + return; + + tor_assert(ns->flavor == FLAV_MICRODESC); + + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + md = microdesc_cache_lookup_by_digest256(cache, rs->descriptor_digest); + if (md && ns->valid_after > md->last_listed) + md->last_listed = ns->valid_after; + } SMARTLIST_FOREACH_END(rs); +} + diff --git a/src/or/microdesc.h b/src/or/microdesc.h index b3e12f8ef0..d96d34a77c 100644 --- a/src/or/microdesc.h +++ b/src/or/microdesc.h @@ -16,11 +16,13 @@ microdesc_cache_t *get_microdesc_cache(void); smartlist_t *microdescs_add_to_cache(microdesc_cache_t *cache, const char *s, const char *eos, saved_location_t where, - int no_save); + int no_save, time_t listed_at, + smartlist_t *requested_digests256); smartlist_t *microdescs_add_list_to_cache(microdesc_cache_t *cache, smartlist_t *descriptors, saved_location_t where, int no_save); +void microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force); int microdesc_cache_rebuild(microdesc_cache_t *cache); int microdesc_cache_reload(microdesc_cache_t *cache); void microdesc_cache_clear(microdesc_cache_t *cache); @@ -30,8 +32,16 @@ microdesc_t *microdesc_cache_lookup_by_digest256(microdesc_cache_t *cache, size_t microdesc_average_size(microdesc_cache_t *cache); +smartlist_t *microdesc_list_missing_digest256(networkstatus_t *ns, + microdesc_cache_t *cache, + int downloadable_only, + digestmap_t *skip); + void microdesc_free(microdesc_t *md); void microdesc_free_all(void); +void update_microdesc_downloads(time_t now); +void update_microdescs_from_networkstatus(time_t now); + #endif diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 94bcb41002..405db12591 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -20,7 +20,9 @@ #include "dirserv.h" #include "dirvote.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "relay.h" #include "router.h" #include "routerlist.h" @@ -44,8 +46,19 @@ static strmap_t *named_server_map = NULL; * as unnamed for some server in the consensus. */ static strmap_t *unnamed_server_map = NULL; -/** Most recently received and validated v3 consensus network status. */ -static networkstatus_t *current_consensus = NULL; +/** Most recently received and validated v3 consensus network status, + * of whichever type we are using for our own circuits. This will be the same + * as one of current_ns_consensus or current_md_consensus. + */ +#define current_consensus current_ns_consensus + +/** Most recently received and validated v3 "ns"-flavored consensus network + * status. */ +static networkstatus_t *current_ns_consensus = NULL; + +/** Most recently received and validated v3 "microdec"-flavored consensus + * network status. */ +static networkstatus_t *current_md_consensus = NULL; /** A v3 consensus networkstatus that we've received, but which we don't * have enough certificates to be happy about. */ @@ -94,9 +107,8 @@ void networkstatus_reset_warnings(void) { if (current_consensus) { - SMARTLIST_FOREACH(current_consensus->routerstatus_list, - routerstatus_t *, rs, - rs->name_lookup_warned = 0); + SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + node->name_lookup_warned = 0); } have_warned_about_old_version = 0; @@ -271,6 +283,7 @@ router_reload_consensus_networkstatus(void) update_certificate_downloads(time(NULL)); routers_update_all_from_networkstatus(time(NULL), 3); + update_microdescs_from_networkstatus(time(NULL)); return 0; } @@ -926,10 +939,9 @@ compare_digest_to_routerstatus_entry(const void *_key, const void **_member) return memcmp(key, rs->identity_digest, DIGEST_LEN); } -/** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or - * NULL if none was found. */ +/** As networkstatus_v2_find_entry, but do not return a const pointer */ routerstatus_t * -networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest) +networkstatus_v2_find_mutable_entry(networkstatus_v2_t *ns, const char *digest) { return smartlist_bsearch(ns->entries, digest, compare_digest_to_routerstatus_entry); @@ -937,14 +949,29 @@ networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest) /** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or * NULL if none was found. */ +const routerstatus_t * +networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest) +{ + return networkstatus_v2_find_mutable_entry(ns, digest); +} + +/** As networkstatus_find_entry, but do not return a const pointer */ routerstatus_t * -networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest) +networkstatus_vote_find_mutable_entry(networkstatus_t *ns, const char *digest) { return smartlist_bsearch(ns->routerstatus_list, digest, compare_digest_to_routerstatus_entry); } -/*XXXX make this static once functions are moved into this file. */ +/** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or + * NULL if none was found. */ +const routerstatus_t * +networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest) +{ + return networkstatus_vote_find_mutable_entry(ns, digest); +} + +/*XXXX MOVE make this static once functions are moved into this file. */ /** Search the routerstatuses in <b>ns</b> for one whose identity digest is * <b>digest</b>. Return value and set *<b>found_out</b> as for * smartlist_bsearch_idx(). */ @@ -966,22 +993,37 @@ networkstatus_get_v2_list(void) return networkstatus_v2_list; } -/** Return the consensus view of the status of the router whose current - * <i>descriptor</i> digest is <b>digest</b>, or NULL if no such router is - * known. */ +/* As router_get_consensus_status_by_descriptor_digest, but does not return + * a const pointer */ routerstatus_t * -router_get_consensus_status_by_descriptor_digest(const char *digest) +router_get_mutable_consensus_status_by_descriptor_digest( + networkstatus_t *consensus, + const char *digest) { - if (!current_consensus) return NULL; - if (!current_consensus->desc_digest_map) { - digestmap_t * m = current_consensus->desc_digest_map = digestmap_new(); - SMARTLIST_FOREACH(current_consensus->routerstatus_list, + if (!consensus) + consensus = current_consensus; + if (!consensus) + return NULL; + if (!consensus->desc_digest_map) { + digestmap_t *m = consensus->desc_digest_map = digestmap_new(); + SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs, { digestmap_set(m, rs->descriptor_digest, rs); }); } - return digestmap_get(current_consensus->desc_digest_map, digest); + return digestmap_get(consensus->desc_digest_map, digest); +} + +/** Return the consensus view of the status of the router whose current + * <i>descriptor</i> digest in <b>consensus</b> is <b>digest</b>, or NULL if + * no such router is known. */ +const routerstatus_t * +router_get_consensus_status_by_descriptor_digest(networkstatus_t *consensus, + const char *digest) +{ + return router_get_mutable_consensus_status_by_descriptor_digest( + consensus, digest); } /** Given the digest of a router descriptor, return its current download @@ -990,7 +1032,10 @@ download_status_t * router_get_dl_status_by_descriptor_digest(const char *d) { routerstatus_t *rs; - if ((rs = router_get_consensus_status_by_descriptor_digest(d))) + if (!current_ns_consensus) + return NULL; + if ((rs = router_get_mutable_consensus_status_by_descriptor_digest( + current_ns_consensus, d))) return &rs->dl_status; if (v2_download_status_map) return digestmap_get(v2_download_status_map, d); @@ -998,10 +1043,9 @@ router_get_dl_status_by_descriptor_digest(const char *d) return NULL; } -/** Return the consensus view of the status of the router whose identity - * digest is <b>digest</b>, or NULL if we don't know about any such router. */ +/** As router_get_consensus_status_by_id, but do not return a const pointer */ routerstatus_t * -router_get_consensus_status_by_id(const char *digest) +router_get_mutable_consensus_status_by_id(const char *digest) { if (!current_consensus) return NULL; @@ -1009,100 +1053,27 @@ router_get_consensus_status_by_id(const char *digest) compare_digest_to_routerstatus_entry); } +/** Return the consensus view of the status of the router whose identity + * digest is <b>digest</b>, or NULL if we don't know about any such router. */ +const routerstatus_t * +router_get_consensus_status_by_id(const char *digest) +{ + return router_get_mutable_consensus_status_by_id(digest); +} + /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return * the corresponding routerstatus_t, or NULL if none exists. Warn the * user if <b>warn_if_unnamed</b> is set, and they have specified a router by * nickname, but the Named flag isn't set for that router. */ -routerstatus_t * +const routerstatus_t * router_get_consensus_status_by_nickname(const char *nickname, int warn_if_unnamed) { - char digest[DIGEST_LEN]; - routerstatus_t *best=NULL; - smartlist_t *matches=NULL; - const char *named_id=NULL; - - if (!current_consensus || !nickname) - return NULL; - - /* Is this name really a hexadecimal identity digest? */ - if (nickname[0] == '$') { - if (base16_decode(digest, DIGEST_LEN, nickname+1, strlen(nickname+1))<0) - return NULL; - return networkstatus_vote_find_entry(current_consensus, digest); - } else if (strlen(nickname) == HEX_DIGEST_LEN && - (base16_decode(digest, DIGEST_LEN, nickname, strlen(nickname))==0)) { - return networkstatus_vote_find_entry(current_consensus, digest); - } - - /* Is there a server that is Named with this name? */ - if (named_server_map) - named_id = strmap_get_lc(named_server_map, nickname); - if (named_id) - return networkstatus_vote_find_entry(current_consensus, named_id); - - /* Okay; is this name listed as Unnamed? */ - if (unnamed_server_map && - strmap_get_lc(unnamed_server_map, nickname)) { - log_info(LD_GENERAL, "The name %s is listed as Unnamed; it is not the " - "canonical name of any server we know.", escaped(nickname)); + const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); + if (node) + return node->rs; + else return NULL; - } - - /* This name is not canonical for any server; go through the list and - * see who it matches. */ - /*XXXX This is inefficient; optimize it if it matters. */ - matches = smartlist_create(); - SMARTLIST_FOREACH(current_consensus->routerstatus_list, - routerstatus_t *, lrs, - { - if (!strcasecmp(lrs->nickname, nickname)) { - if (lrs->is_named) { - tor_fragile_assert() /* This should never happen. */ - smartlist_free(matches); - return lrs; - } else { - if (lrs->is_unnamed) { - tor_fragile_assert(); /* nor should this. */ - smartlist_clear(matches); - best=NULL; - break; - } - smartlist_add(matches, lrs); - best = lrs; - } - } - }); - - if (smartlist_len(matches)>1 && warn_if_unnamed) { - int any_unwarned=0; - SMARTLIST_FOREACH(matches, routerstatus_t *, lrs, - { - if (! lrs->name_lookup_warned) { - lrs->name_lookup_warned=1; - any_unwarned=1; - } - }); - if (any_unwarned) { - log_warn(LD_CONFIG,"There are multiple matches for the nickname \"%s\"," - " but none is listed as named by the directory authorities. " - "Choosing one arbitrarily.", nickname); - } - } else if (warn_if_unnamed && best && !best->name_lookup_warned) { - char fp[HEX_DIGEST_LEN+1]; - base16_encode(fp, sizeof(fp), - best->identity_digest, DIGEST_LEN); - log_warn(LD_CONFIG, - "When looking up a status, you specified a server \"%s\" by name, " - "but the directory authorities do not have any key registered for " - "this nickname -- so it could be used by any server, " - "not just the one you meant. " - "To make sure you get the same server in the future, refer to " - "it by key, as \"$%s\".", nickname, fp); - best->name_lookup_warned = 1; - } - smartlist_free(matches); - return best; } /** Return the identity digest that's mapped to officially by @@ -1199,6 +1170,25 @@ update_v2_networkstatus_cache_downloads(time_t now) } } +/** DOCDOC */ +static int +we_want_to_fetch_flavor(or_options_t *options, int flavor) +{ + if (flavor < 0 || flavor > N_CONSENSUS_FLAVORS) { + /* This flavor is crazy; we don't want it */ + /*XXXX handle unrecognized flavors later */ + return 0; + } + if (authdir_mode_v3(options) || directory_caches_dir_info(options)) { + /* We want to serve all flavors to others, regardless if we would use + * it ourselves. */ + return 1; + } + /* Otherwise, we want the flavor only if we want to use it to build + * circuits. */ + 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 @@ -1211,48 +1201,65 @@ static void update_consensus_networkstatus_downloads(time_t now) { int i; + or_options_t *options = get_options(); + if (!networkstatus_get_live_consensus(now)) time_to_download_next_consensus = now; /* No live consensus? Get one now!*/ if (time_to_download_next_consensus > now) return; /* Wait until the current consensus is older. */ - /* XXXXNM Microdescs: may need to download more types. */ - if (!download_status_is_ready(&consensus_dl_status[FLAV_NS], now, - CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES)) - return; /* We failed downloading a consensus too recently. */ - if (connection_get_by_type_purpose(CONN_TYPE_DIR, - DIR_PURPOSE_FETCH_CONSENSUS)) - return; /* There's an in-progress download.*/ for (i=0; i < N_CONSENSUS_FLAVORS; ++i) { - consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i]; + /* XXXX need some way to download unknown flavors if we are caching. */ + const char *resource; + consensus_waiting_for_certs_t *waiting; + + if (! we_want_to_fetch_flavor(options, i)) + continue; + + resource = i==FLAV_NS ? NULL : networkstatus_get_flavor_name(i); + + if (!download_status_is_ready(&consensus_dl_status[i], now, + CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES)) + continue; /* We failed downloading a consensus too recently. */ + if (connection_dir_get_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, resource)) + continue; /* There's an in-progress download.*/ + + waiting = &consensus_waiting_for_certs[i]; if (waiting->consensus) { /* XXXX make sure this doesn't delay sane downloads. */ - if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) - return; /* We're still getting certs for this one. */ - else { + if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) { + continue; /* We're still getting certs for this one. */ + } else { if (!waiting->dl_failed) { - download_status_failed(&consensus_dl_status[FLAV_NS], 0); + download_status_failed(&consensus_dl_status[i], 0); waiting->dl_failed=1; } } } - } - log_info(LD_DIR, "Launching networkstatus consensus download."); - directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, - ROUTER_PURPOSE_GENERAL, NULL, - PDS_RETRY_IF_NO_SERVERS); + log_info(LD_DIR, "Launching %s networkstatus consensus download.", + networkstatus_get_flavor_name(i)); + + directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, + ROUTER_PURPOSE_GENERAL, resource, + PDS_RETRY_IF_NO_SERVERS); + } } /** Called when an attempt to download a consensus fails: note that the * failure occurred, and possibly retry. */ void -networkstatus_consensus_download_failed(int status_code) +networkstatus_consensus_download_failed(int status_code, const char *flavname) { - /* XXXXNM Microdescs: may need to handle more types. */ - download_status_failed(&consensus_dl_status[FLAV_NS], status_code); - /* Retry immediately, if appropriate. */ - update_consensus_networkstatus_downloads(time(NULL)); + int flav = networkstatus_parse_flavor_name(flavname); + if (flav >= 0) { + tor_assert(flav < N_CONSENSUS_FLAVORS); + /* XXXX handle unrecognized flavors */ + download_status_failed(&consensus_dl_status[flav], status_code); + /* Retry immediately, if appropriate. */ + update_consensus_networkstatus_downloads(time(NULL)); + } } /** How long do we (as a cache) wait after a consensus becomes non-fresh @@ -1373,7 +1380,10 @@ update_certificate_downloads(time_t now) now); } - authority_certs_fetch_missing(current_consensus, now); + if (current_ns_consensus) + authority_certs_fetch_missing(current_ns_consensus, now); + if (current_md_consensus) + authority_certs_fetch_missing(current_md_consensus, now); } /** Return 1 if we have a consensus but we don't have enough certificates @@ -1405,6 +1415,18 @@ networkstatus_get_latest_consensus(void) return current_consensus; } +/** DOCDOC */ +networkstatus_t * +networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) +{ + if (f == FLAV_NS) + return current_ns_consensus; + else if (f == FLAV_MICRODESC) + return current_md_consensus; + else + tor_assert(0); +} + /** Return the most recent consensus that we have downloaded, or NULL if it is * no longer live. */ networkstatus_t * @@ -1424,13 +1446,15 @@ networkstatus_get_live_consensus(time_t now) /** As networkstatus_get_live_consensus(), but is way more tolerant of expired * consensuses. */ networkstatus_t * -networkstatus_get_reasonably_live_consensus(time_t now) +networkstatus_get_reasonably_live_consensus(time_t now, int flavor) { #define REASONABLY_LIVE_TIME (24*60*60) - if (current_consensus && - current_consensus->valid_after <= now && - now <= current_consensus->valid_until+REASONABLY_LIVE_TIME) - return current_consensus; + networkstatus_t *consensus = + networkstatus_get_latest_consensus_by_flavor(flavor); + if (consensus && + consensus->valid_after <= now && + now <= consensus->valid_until+REASONABLY_LIVE_TIME) + return consensus; else return NULL; } @@ -1451,7 +1475,7 @@ routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b) a->is_exit != b->is_exit || a->is_stable != b->is_stable || a->is_fast != b->is_fast || - a->is_running != b->is_running || + a->is_flagged_running != b->is_flagged_running || a->is_named != b->is_named || a->is_unnamed != b->is_unnamed || a->is_valid != b->is_valid || @@ -1492,13 +1516,14 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c, } changed = smartlist_create(); - SMARTLIST_FOREACH_JOIN(old_c->routerstatus_list, routerstatus_t *, rs_old, - new_c->routerstatus_list, routerstatus_t *, rs_new, - memcmp(rs_old->identity_digest, - rs_new->identity_digest, DIGEST_LEN), - smartlist_add(changed, rs_new)) { + SMARTLIST_FOREACH_JOIN( + old_c->routerstatus_list, const routerstatus_t *, rs_old, + new_c->routerstatus_list, const routerstatus_t *, rs_new, + memcmp(rs_old->identity_digest, + rs_new->identity_digest, DIGEST_LEN), + smartlist_add(changed, (void*) rs_new)) { if (routerstatus_has_changed(rs_old, rs_new)) - smartlist_add(changed, rs_new); + smartlist_add(changed, (void*)rs_new); } SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new); control_event_networkstatus_changed(changed); @@ -1522,7 +1547,6 @@ networkstatus_copy_old_consensus_info(networkstatus_t *new_c, rs_new->identity_digest, DIGEST_LEN), STMT_NIL) { /* Okay, so we're looking at the same identity. */ - rs_new->name_lookup_warned = rs_old->name_lookup_warned; rs_new->last_dir_503_at = rs_old->last_dir_503_at; if (!memcmp(rs_old->descriptor_digest, rs_new->descriptor_digest, @@ -1615,9 +1639,16 @@ networkstatus_set_current_consensus(const char *consensus, if (!strcmp(flavor, "ns")) { consensus_fname = get_datadir_fname("cached-consensus"); unverified_fname = get_datadir_fname("unverified-consensus"); - if (current_consensus) { - current_digests = ¤t_consensus->digests; - current_valid_after = current_consensus->valid_after; + if (current_ns_consensus) { + current_digests = ¤t_ns_consensus->digests; + current_valid_after = current_ns_consensus->valid_after; + } + } else if (!strcmp(flavor, "microdesc")) { + consensus_fname = get_datadir_fname("cached-microdesc-consensus"); + unverified_fname = get_datadir_fname("unverified-microdesc-consensus"); + if (current_md_consensus) { + current_digests = ¤t_md_consensus->digests; + current_valid_after = current_md_consensus->valid_after; } } else { cached_dir_t *cur; @@ -1703,15 +1734,27 @@ networkstatus_set_current_consensus(const char *consensus, if (flav == USABLE_CONSENSUS_FLAVOR) { notify_control_networkstatus_changed(current_consensus, c); - - if (current_consensus) { - networkstatus_copy_old_consensus_info(c, current_consensus); - networkstatus_vote_free(current_consensus); + } + if (flav == FLAV_NS) { + if (current_ns_consensus) { + networkstatus_copy_old_consensus_info(c, current_ns_consensus); + networkstatus_vote_free(current_ns_consensus); /* Defensive programming : we should set current_consensus very soon, * but we're about to call some stuff in the meantime, and leaving this * dangling pointer around has proven to be trouble. */ - current_consensus = NULL; + current_ns_consensus = NULL; + } + current_ns_consensus = c; + free_consensus = 0; /* avoid free */ + } else if (flav == FLAV_MICRODESC) { + if (current_md_consensus) { + networkstatus_copy_old_consensus_info(c, current_md_consensus); + networkstatus_vote_free(current_md_consensus); + /* more defensive programming */ + current_md_consensus = NULL; } + current_md_consensus = c; + free_consensus = 0; /* avoid free */ } waiting = &consensus_waiting_for_certs[flav]; @@ -1737,11 +1780,11 @@ networkstatus_set_current_consensus(const char *consensus, } if (flav == USABLE_CONSENSUS_FLAVOR) { - current_consensus = c; - free_consensus = 0; /* Prevent free. */ - /* XXXXNM Microdescs: needs a non-ns variant. */ update_consensus_networkstatus_fetch_time(now); + + nodelist_set_consensus(current_consensus); + dirvote_recalculate_timing(options, now); routerstatus_list_update_named_server_map(); cell_ewma_set_scale_factor(options, current_consensus); @@ -1763,11 +1806,11 @@ networkstatus_set_current_consensus(const char *consensus, write_str_to_file(consensus_fname, consensus, 0); } - if (ftime_definitely_before(now, current_consensus->valid_after)) { + if (time_definitely_before(now, c->valid_after, 60)) { char tbuf[ISO_TIME_LEN+1]; char dbuf[64]; - long delta = now - current_consensus->valid_after; - format_iso_time(tbuf, current_consensus->valid_after); + long delta = now - c->valid_after; + format_iso_time(tbuf, c->valid_after); format_time_interval(dbuf, sizeof(dbuf), delta); log_warn(LD_GENERAL, "Our clock is %s behind the time published in the " "consensus network status document (%s GMT). Tor needs an " @@ -1818,7 +1861,8 @@ void routers_update_all_from_networkstatus(time_t now, int dir_version) { routerlist_t *rl = router_get_routerlist(); - networkstatus_t *consensus = networkstatus_get_live_consensus(now); + networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now, + FLAV_NS); if (networkstatus_v2_list_has_changed) download_status_map_update_from_v2_networkstatus(); @@ -1890,7 +1934,7 @@ download_status_map_update_from_v2_networkstatus(void) dl_status = digestmap_new(); SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) { + SMARTLIST_FOREACH_BEGIN(ns->entries, const routerstatus_t *, rs) { const char *d = rs->descriptor_digest; download_status_t *s; if (digestmap_get(dl_status, d)) @@ -1918,7 +1962,8 @@ routerstatus_list_update_named_server_map(void) named_server_map = strmap_new(); strmap_free(unnamed_server_map, NULL); unnamed_server_map = strmap_new(); - SMARTLIST_FOREACH(current_consensus->routerstatus_list, routerstatus_t *, rs, + SMARTLIST_FOREACH(current_consensus->routerstatus_list, + const routerstatus_t *, rs, { if (rs->is_named) { strmap_set_lc(named_server_map, rs->nickname, @@ -1940,7 +1985,6 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, trusted_dir_server_t *ds; or_options_t *options = get_options(); int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); - int namingdir = authdir && options->NamingAuthoritativeDir; networkstatus_t *ns = current_consensus; if (!ns || !smartlist_len(ns->routerstatus_list)) return; @@ -1954,25 +1998,19 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, memcmp(rs->identity_digest, router->cache_info.identity_digest, DIGEST_LEN), { +#if 0 /* We have no routerstatus for this router. Clear flags and skip it. */ - if (!namingdir) - router->is_named = 0; if (!authdir) { if (router->purpose == ROUTER_PURPOSE_GENERAL) router_clear_status_flags(router); } +#endif }) { /* We have a routerstatus for this router. */ const char *digest = router->cache_info.identity_digest; ds = router_get_trusteddirserver_by_digest(digest); - if (!namingdir) { - if (rs->is_named && !strcasecmp(router->nickname, rs->nickname)) - router->is_named = 1; - else - router->is_named = 0; - } /* Is it the same descriptor, or only the same identity? */ if (!memcmp(router->cache_info.signed_descriptor_digest, rs->descriptor_digest, DIGEST_LEN)) { @@ -1980,28 +2018,17 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, router->cache_info.last_listed_as_valid_until = ns->valid_until; } - if (!authdir) { - /* If we're not an authdir, believe others. */ - router->is_valid = rs->is_valid; - router->is_running = rs->is_running; - router->is_fast = rs->is_fast; - router->is_stable = rs->is_stable; - router->is_possible_guard = rs->is_possible_guard; - router->is_exit = rs->is_exit; - router->is_bad_directory = rs->is_bad_directory; - router->is_bad_exit = rs->is_bad_exit; - router->is_hs_dir = rs->is_hs_dir; - } else { + if (authdir) { /* If we _are_ an authority, we should check whether this router * is one that will cause us to need a reachability test. */ routerinfo_t *old_router = - router_get_by_digest(router->cache_info.identity_digest); + router_get_mutable_by_digest(router->cache_info.identity_digest); if (old_router != router) { router->needs_retest_if_added = dirserv_should_launch_reachability_test(router, old_router); } } - if (router->is_running && ds) { + if (rs->is_flagged_running && ds) { download_status_reset(&ds->v2_ns_dl_status); } if (reset_failures) { @@ -2010,10 +2037,9 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, } SMARTLIST_FOREACH_JOIN_END(rs, router); /* Now update last_listed_as_valid_until from v2 networkstatuses. */ - /* XXXX If this is slow, we need to rethink the code. */ SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, { time_t live_until = ns->published_on + V2_NETWORKSTATUS_ROUTER_LIFETIME; - SMARTLIST_FOREACH_JOIN(ns->entries, routerstatus_t *, rs, + SMARTLIST_FOREACH_JOIN(ns->entries, const routerstatus_t *, rs, routers, routerinfo_t *, ri, memcmp(rs->identity_digest, ri->cache_info.identity_digest, DIGEST_LEN), @@ -2034,7 +2060,7 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, void signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs) { - networkstatus_t *ns = current_consensus; + networkstatus_t *ns = current_ns_consensus; if (!ns) return; @@ -2042,11 +2068,11 @@ signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs) char dummy[DIGEST_LEN]; /* instantiates the digest map. */ memset(dummy, 0, sizeof(dummy)); - router_get_consensus_status_by_descriptor_digest(dummy); + router_get_consensus_status_by_descriptor_digest(ns, dummy); } SMARTLIST_FOREACH(descs, signed_descriptor_t *, d, { - routerstatus_t *rs = digestmap_get(ns->desc_digest_map, + const routerstatus_t *rs = digestmap_get(ns->desc_digest_map, d->signed_descriptor_digest); if (rs) { if (ns->valid_until > d->last_listed_as_valid_until) @@ -2059,7 +2085,7 @@ signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs) * return the result in a newly allocated string. Used only by controller * interface (for now.) */ char * -networkstatus_getinfo_helper_single(routerstatus_t *rs) +networkstatus_getinfo_helper_single(const routerstatus_t *rs) { char buf[RS_ENTRY_LEN+1]; routerstatus_format_entry(buf, sizeof(buf), rs, NULL, NS_CONTROL_PORT); @@ -2092,6 +2118,9 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) statuses = smartlist_create(); SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (!node) + continue; if (ri->cache_info.published_on < cutoff) continue; if (ri->purpose != purpose) @@ -2099,7 +2128,7 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) if (bridge_auth && ri->purpose == ROUTER_PURPOSE_BRIDGE) dirserv_set_router_is_running(ri, now); /* then generate and write out status lines for each of them */ - set_routerstatus_from_routerinfo(&rs, ri, now, 0, 0, 0); + set_routerstatus_from_routerinfo(&rs, node, ri, now, 0, 0, 0); smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs)); }); @@ -2215,7 +2244,7 @@ getinfo_helper_networkstatus(control_connection_t *conn, const char *question, char **answer, const char **errmsg) { - routerstatus_t *status; + const routerstatus_t *status; (void) conn; if (!current_consensus) { @@ -2226,7 +2255,7 @@ getinfo_helper_networkstatus(control_connection_t *conn, if (!strcmp(question, "ns/all")) { smartlist_t *statuses = smartlist_create(); SMARTLIST_FOREACH(current_consensus->routerstatus_list, - routerstatus_t *, rs, + const routerstatus_t *, rs, { smartlist_add(statuses, networkstatus_getinfo_helper_single(rs)); }); @@ -2270,8 +2299,9 @@ networkstatus_free_all(void) digestmap_free(v2_download_status_map, _tor_free); v2_download_status_map = NULL; - networkstatus_vote_free(current_consensus); - current_consensus = NULL; + networkstatus_vote_free(current_ns_consensus); + networkstatus_vote_free(current_md_consensus); + current_md_consensus = current_ns_consensus = NULL; for (i=0; i < N_CONSENSUS_FLAVORS; ++i) { consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i]; diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 2f18dc9525..e01e6b5288 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -38,22 +38,34 @@ int router_set_networkstatus_v2(const char *s, time_t arrived_at, void networkstatus_v2_list_clean(time_t now); int compare_digest_to_routerstatus_entry(const void *_key, const void **_member); -routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns, +const routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest); -routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns, +const routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns, + const char *digest); +routerstatus_t *networkstatus_v2_find_mutable_entry(networkstatus_v2_t *ns, + const char *digest); +routerstatus_t *networkstatus_vote_find_mutable_entry(networkstatus_t *ns, const char *digest); int networkstatus_vote_find_entry_idx(networkstatus_t *ns, const char *digest, int *found_out); const smartlist_t *networkstatus_get_v2_list(void); download_status_t *router_get_dl_status_by_descriptor_digest(const char *d); -routerstatus_t *router_get_consensus_status_by_id(const char *digest); -routerstatus_t *router_get_consensus_status_by_descriptor_digest( - const char *digest); -routerstatus_t *router_get_consensus_status_by_nickname(const char *nickname, - int warn_if_unnamed); +const routerstatus_t *router_get_consensus_status_by_id(const char *digest); +routerstatus_t *router_get_mutable_consensus_status_by_id( + const char *digest); +const routerstatus_t *router_get_consensus_status_by_descriptor_digest( + networkstatus_t *consensus, + const char *digest); +routerstatus_t *router_get_mutable_consensus_status_by_descriptor_digest( + networkstatus_t *consensus, + const char *digest); +const routerstatus_t *router_get_consensus_status_by_nickname( + const char *nickname, + int warn_if_unnamed); const char *networkstatus_get_router_digest_by_nickname(const char *nickname); int networkstatus_nickname_is_unnamed(const char *nickname); -void networkstatus_consensus_download_failed(int status_code); +void networkstatus_consensus_download_failed(int status_code, + const char *flavname); void update_consensus_networkstatus_fetch_time(time_t now); int should_delay_dir_fetches(or_options_t *options); void update_networkstatus_downloads(time_t now); @@ -61,8 +73,11 @@ void update_certificate_downloads(time_t now); int consensus_is_waiting_for_certs(void); networkstatus_v2_t *networkstatus_v2_get_by_digest(const char *digest); networkstatus_t *networkstatus_get_latest_consensus(void); +networkstatus_t *networkstatus_get_latest_consensus_by_flavor( + consensus_flavor_t f); networkstatus_t *networkstatus_get_live_consensus(time_t now); -networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now); +networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now, + int flavor); #define NSSET_FROM_CACHE 1 #define NSSET_WAS_WAITING_FOR_CERTS 2 #define NSSET_DONT_DOWNLOAD_CERTS 4 @@ -78,7 +93,7 @@ void routers_update_status_from_consensus_networkstatus(smartlist_t *routers, void signed_descs_update_status_from_consensus_networkstatus( smartlist_t *descs); -char *networkstatus_getinfo_helper_single(routerstatus_t *rs); +char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); void networkstatus_dump_bridge_status_to_file(time_t now); int32_t get_net_param_from_list(smartlist_t *net_params, const char *name, diff --git a/src/or/nodelist.c b/src/or/nodelist.c new file mode 100644 index 0000000000..a736dc3b8c --- /dev/null +++ b/src/or/nodelist.c @@ -0,0 +1,733 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "config.h" +#include "dirserv.h" +#include "microdesc.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "policies.h" +#include "router.h" +#include "routerlist.h" + +#include <string.h> + +static void nodelist_drop_node(node_t *node, int remove_from_ht); +static void node_free(node_t *node); + +/** 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 + * is currently in the routerlist, or currently in the consensus we're using. + */ +typedef struct nodelist_t { + /* A list of all the nodes. */ + smartlist_t *nodes; + /* Hash table to map from node ID digest to node. */ + HT_HEAD(nodelist_map, node_t) nodes_by_id; + +} nodelist_t; + +static INLINE unsigned int +node_id_hash(const node_t *node) +{ +#if SIZEOF_INT == 4 + const uint32_t *p = (const uint32_t*)node->identity; + return p[0] ^ p[1] ^ p[2] ^ p[3] ^ p[4]; +#elif SIZEOF_INT == 8 + const uint64_t *p = (const uint32_t*)node->identity; + const uint32_t *p32 = (const uint32_t*)node->identity; + return p[0] ^ p[1] ^ p32[4]; +#endif +} + +static INLINE unsigned int +node_id_eq(const node_t *node1, const node_t *node2) +{ + return 0 == memcmp(node1->identity, node2->identity, DIGEST_LEN); +} + +HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq); +HT_GENERATE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, + 0.6, malloc, realloc, free); + +/** The global nodelist. */ +static nodelist_t *the_nodelist=NULL; + +/** Create an empty nodelist if we haven't done so already. */ +static void +init_nodelist(void) +{ + if (PREDICT_UNLIKELY(the_nodelist == NULL)) { + the_nodelist = tor_malloc_zero(sizeof(nodelist_t)); + HT_INIT(nodelist_map, &the_nodelist->nodes_by_id); + the_nodelist->nodes = smartlist_create(); + } +} + +/** As node_get_by_id, but returns a non-const pointer */ +node_t * +node_get_mutable_by_id(const char *identity_digest) +{ + node_t search, *node; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return NULL; + + memcpy(&search.identity, identity_digest, DIGEST_LEN); + node = HT_FIND(nodelist_map, &the_nodelist->nodes_by_id, &search); + return node; +} + +/** Return the node_t whose identity is <b>identity_digest</b>, or NULL + * if no such node exists. */ +const node_t * +node_get_by_id(const char *identity_digest) +{ + return node_get_mutable_by_id(identity_digest); +} + +/** Internal: return the node_t whose identity_digest is + * <b>identity_digest</b>. If none exists, create a new one, add it to the + * nodelist, and return it. + * + * Requires that the nodelist be initialized. + */ +static node_t * +node_get_or_create(const char *identity_digest) +{ + node_t *node; + + if ((node = node_get_mutable_by_id(identity_digest))) + return node; + + node = tor_malloc_zero(sizeof(node_t)); + memcpy(node->identity, identity_digest, DIGEST_LEN); + HT_INSERT(nodelist_map, &the_nodelist->nodes_by_id, node); + + smartlist_add(the_nodelist->nodes, node); + node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1; + + node->country = -1; + + return node; +} + +/** Add <b>ri</b> to the nodelist. */ +node_t * +nodelist_add_routerinfo(routerinfo_t *ri) +{ + node_t *node; + init_nodelist(); + node = node_get_or_create(ri->cache_info.identity_digest); + node->ri = ri; + + if (node->country == -1) + node_set_country(node); + + if (authdir_mode(get_options())) { + const char *discard=NULL; + uint32_t status = dirserv_router_get_status(ri, &discard); + dirserv_set_node_flags_from_authoritative_status(node, status); + } + + return node; +} + +/** Set the appropriate node_t to use <b>md</b> as its microdescriptor. + * + * Called when a new microdesc has arrived and the usable consensus flavor + * is "microdesc". + **/ +node_t * +nodelist_add_microdesc(microdesc_t *md) +{ + networkstatus_t *ns = + networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); + const routerstatus_t *rs; + node_t *node; + if (ns == NULL) + return NULL; + init_nodelist(); + + /* Microdescriptors don't carry an identity digest, so we need to figure + * it out by looking up the routerstatus. */ + rs = router_get_consensus_status_by_descriptor_digest(ns, md->digest); + if (rs == NULL) + return NULL; + node = node_get_mutable_by_id(rs->identity_digest); + if (node) + node->md = md; + return node; +} + +/** Tell the nodelist that the current usable consensus to <b>ns</b>. + * This makes the nodelist change all of the routerstatus entries for + * the nodes, drop nodes that no longer have enough info to get used, + * and grab microdescriptors into nodes as appropriate. + */ +void +nodelist_set_consensus(networkstatus_t *ns) +{ + or_options_t *options = get_options(); + int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); + init_nodelist(); + + SMARTLIST_FOREACH(the_nodelist->nodes, node_t *, node, + node->rs = NULL); + + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + node_t *node = node_get_or_create(rs->identity_digest); + node->rs = rs; + if (ns->flavor == FLAV_MICRODESC) { + if (node->md == NULL || + 0!=memcmp(node->md->digest,rs->descriptor_digest,DIGEST256_LEN)) { + node->md = microdesc_cache_lookup_by_digest256(NULL, + rs->descriptor_digest); + } + } + + node_set_country(node); + + /* If we're not an authdir, believe others. */ + if (!authdir) { + node->is_valid = rs->is_valid; + node->is_running = rs->is_flagged_running; + node->is_fast = rs->is_fast; + node->is_stable = rs->is_stable; + node->is_possible_guard = rs->is_possible_guard; + node->is_exit = rs->is_exit; + node->is_bad_directory = rs->is_bad_directory; + node->is_bad_exit = rs->is_bad_exit; + node->is_hs_dir = rs->is_hs_dir; + } + + } SMARTLIST_FOREACH_END(rs); + + nodelist_purge(); + + if (! authdir) { + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + /* We have no routerstatus for this router. Clear flags so we can skip + * it, maybe.*/ + if (!node->rs) { + tor_assert(node->ri); /* if it had only an md, or nothing, purge + * would have removed it. */ + if (node->ri->purpose == ROUTER_PURPOSE_GENERAL) { + /* Clear all flags. */ + node->is_valid = node->is_running = node->is_hs_dir = + node->is_fast = node->is_stable = + node->is_possible_guard = node->is_exit = + node->is_bad_exit = node->is_bad_directory = 0; + } + } + } SMARTLIST_FOREACH_END(node); + } +} + +/** Helper: return true iff a node has a usable amount of information*/ +static INLINE int +node_is_usable(const node_t *node) +{ + return (node->rs) || (node->ri); +} + +/** Tell the nodelist that <b>md</b> is no longer a microdescriptor for the + * node with <b>identity_digest</b>. */ +void +nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md) +{ + node_t *node = node_get_mutable_by_id(identity_digest); + if (node && node->md == md) + node->md = NULL; +} + +/** Tell the nodelist that <b>ri</b> is no longer in the routerlist. */ +void +nodelist_remove_routerinfo(routerinfo_t *ri) +{ + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (node && node->ri == ri) { + node->ri = NULL; + if (! node_is_usable(node)) { + nodelist_drop_node(node, 1); + node_free(node); + } + } +} + +/** Remove <b>node</b> from the nodelist. (Asserts that it was there to begin + * with.) */ +static void +nodelist_drop_node(node_t *node, int remove_from_ht) +{ + node_t *tmp; + int idx; + if (remove_from_ht) { + tmp = HT_REMOVE(nodelist_map, &the_nodelist->nodes_by_id, node); + tor_assert(tmp == node); + } + + idx = node->nodelist_idx; + tor_assert(idx >= 0); + + tor_assert(node == smartlist_get(the_nodelist->nodes, idx)); + smartlist_del(the_nodelist->nodes, idx); + if (idx < smartlist_len(the_nodelist->nodes)) { + tmp = smartlist_get(the_nodelist->nodes, idx); + tmp->nodelist_idx = idx; + } + node->nodelist_idx = -1; +} + +/** Release storage held by <b>node</b> */ +static void +node_free(node_t *node) +{ + if (!node) + return; + tor_assert(node->nodelist_idx == -1); + tor_free(node); +} + +/** Remove all entries from the nodelist that don't have enough info to be + * usable for anything. */ +void +nodelist_purge(void) +{ + node_t **iter; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return; + + /* Remove the non-usable nodes. */ + for (iter = HT_START(nodelist_map, &the_nodelist->nodes_by_id); iter; ) { + node_t *node = *iter; + + if (node_is_usable(node)) { + iter = HT_NEXT(nodelist_map, &the_nodelist->nodes_by_id, iter); + } else { + iter = HT_NEXT_RMV(nodelist_map, &the_nodelist->nodes_by_id, iter); + nodelist_drop_node(node, 0); + node_free(node); + } + } + nodelist_assert_ok(); +} + +/** Release all storage held by the nodelist. */ +void +nodelist_free_all(void) +{ + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return; + + HT_CLEAR(nodelist_map, &the_nodelist->nodes_by_id); + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + node->nodelist_idx = -1; + node_free(node); + } SMARTLIST_FOREACH_END(node); + + smartlist_free(the_nodelist->nodes); + + tor_free(the_nodelist); +} + +/** Check that the nodelist is internally consistent, and consistent with + * the directory info it's derived from. + */ +void +nodelist_assert_ok(void) +{ + routerlist_t *rl = router_get_routerlist(); + networkstatus_t *ns = networkstatus_get_latest_consensus(); + digestmap_t *dm = digestmap_new(); + + if (!the_nodelist) + return; + + /* every routerinfo in rl->routers should be in the nodelist. */ + if (rl) { + SMARTLIST_FOREACH_BEGIN(rl->routers, routerinfo_t *, ri) { + const node_t *node = node_get_by_id(ri->cache_info.identity_digest); + tor_assert(node && node->ri == ri); + tor_assert(0 == memcmp(ri->cache_info.identity_digest, + node->identity, DIGEST_LEN)); + tor_assert(! digestmap_get(dm, node->identity)); + digestmap_set(dm, node->identity, (void*)node); + } SMARTLIST_FOREACH_END(ri); + } + + /* every routerstatus in ns should be in the nodelist */ + if (ns) { + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + const node_t *node = node_get_by_id(rs->identity_digest); + tor_assert(node && node->rs == rs); + tor_assert(0 == memcmp(rs->identity_digest, node->identity, DIGEST_LEN)); + digestmap_set(dm, node->identity, (void*)node); + if (ns->flavor == FLAV_MICRODESC) { + /* If it's a microdesc consensus, every entry that has a + * microdescriptor should be in the nodelist. + */ + microdesc_t *md = + microdesc_cache_lookup_by_digest256(NULL, rs->descriptor_digest); + tor_assert(md == node->md); + } + } SMARTLIST_FOREACH_END(rs); + } + + /* The nodelist should have no other entries, and its entries should be + * well-formed. */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + tor_assert(digestmap_get(dm, node->identity) != NULL); + tor_assert(node_sl_idx == node->nodelist_idx); + } SMARTLIST_FOREACH_END(node); + + tor_assert((long)smartlist_len(the_nodelist->nodes) == + (long)HT_SIZE(&the_nodelist->nodes_by_id)); + + digestmap_free(dm, NULL); +} + +/** Return a list of a node_t * for every node we know about. The caller + * MUST NOT modify the list. (You can set and clear flags in the nodes if + * you must, but you must not add or remove nodes.) */ +smartlist_t * +nodelist_get_list(void) +{ + init_nodelist(); + return the_nodelist->nodes; +} + +/** Given a hex-encoded nickname of the format DIGEST, $DIGEST, $DIGEST=name, + * or $DIGEST~name, return the node with the matching identity digest and + * nickname (if any). Return NULL if no such node exists, or if <b>hex_id</b> + * is not well-formed. */ +const node_t * +node_get_by_hex_id(const char *hex_id) +{ + char digest_buf[DIGEST_LEN]; + char nn_buf[MAX_NICKNAME_LEN+1]; + char nn_char='\0'; + + if (hex_digest_nickname_decode(hex_id, digest_buf, &nn_char, nn_buf)==0) { + const node_t *node = node_get_by_id(digest_buf); + if (!node) + return NULL; + if (nn_char) { + const char *real_name = node_get_nickname(node); + if (!real_name || strcasecmp(real_name, nn_buf)) + return NULL; + if (nn_char == '=') { + const char *named_id = + networkstatus_get_router_digest_by_nickname(nn_buf); + if (!named_id || memcmp(named_id, digest_buf, DIGEST_LEN)) + return NULL; + } + } + return node; + } + + return NULL; +} + +/** Given a nickname (possibly verbose, possibly a hexadecimal digest), return + * the corresponding node_t, or NULL if none exists. Warn the user if + * <b>warn_if_unnamed</b> is set, and they have specified a router by + * nickname, but the Named flag isn't set for that router. */ +const node_t * +node_get_by_nickname(const char *nickname, int warn_if_unnamed) +{ + const node_t *node; + if (!the_nodelist) + return NULL; + + /* Handle these cases: DIGEST, $DIGEST, $DIGEST=name, $DIGEST~name. */ + if ((node = node_get_by_hex_id(nickname)) != NULL) + return node; + + if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) + return NULL; + + /* Okay, so if we get here, the nickname is just a nickname. Is there + * a binding for it in the consensus? */ + { + const char *named_id = + networkstatus_get_router_digest_by_nickname(nickname); + if (named_id) + return node_get_by_id(named_id); + } + + /* Is it marked as owned-by-someone-else? */ + if (networkstatus_nickname_is_unnamed(nickname)) { + log_info(LD_GENERAL, "The name %s is listed as Unnamed: there is some " + "router that holds it, but not one listed in the current " + "consensus.", escaped(nickname)); + return NULL; + } + + /* Okay, so the name is not canonical for anybody. */ + { + smartlist_t *matches = smartlist_create(); + const node_t *choice = NULL; + + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + if (!strcasecmp(node_get_nickname(node), nickname)) + smartlist_add(matches, node); + } SMARTLIST_FOREACH_END(node); + + if (smartlist_len(matches)>1 && warn_if_unnamed) { + int any_unwarned = 0; + SMARTLIST_FOREACH_BEGIN(matches, node_t *, node) { + if (!node->name_lookup_warned) { + node->name_lookup_warned = 1; + any_unwarned = 1; + } + } SMARTLIST_FOREACH_END(node); + + if (any_unwarned) { + log_warn(LD_CONFIG, "There are multiple matches for the name %s, " + "but none is listed as Named in the directory consensus. " + "Choosing one arbitrarily.", nickname); + } + } else if (smartlist_len(matches)>1 && warn_if_unnamed) { + char fp[HEX_DIGEST_LEN+1]; + node_t *node = smartlist_get(matches, 0); + if (node->name_lookup_warned) { + base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN); + log_warn(LD_CONFIG, + "You specified a server \"%s\" by name, but the directory " + "authorities do not have any key registered for this " + "nickname -- so it could be used by any server, not just " + "the one you meant. " + "To make sure you get the same server in the future, refer " + "to it by key, as \"$%s\".", nickname, fp); + node->name_lookup_warned = 1; + } + } + + if (smartlist_len(matches)) + choice = smartlist_get(matches, 0); + + smartlist_free(matches); + return choice; + } +} + +/** Return the nickname of <b>node</b>, or NULL if we can't find one. */ +const char * +node_get_nickname(const node_t *node) +{ + tor_assert(node); + if (node->rs) + return node->rs->nickname; + else if (node->ri) + return node->ri->nickname; + else + return NULL; +} + +/** Return true iff the nickname of <b>node</b> is canonical, based on the + * latest consensus. */ +int +node_is_named(const node_t *node) +{ + const char *named_id; + const char *nickname = node_get_nickname(node); + if (!nickname) + return 0; + named_id = networkstatus_get_router_digest_by_nickname(nickname); + if (!named_id) + return 0; + return !memcmp(named_id, node->identity, DIGEST_LEN); +} + +/** Return true iff <b>node</b> appears to be a directory authority or + * directory cache */ +int +node_is_dir(const node_t *node) +{ + if (node->rs) + return node->rs->dir_port != 0; + else if (node->ri) + return node->ri->dir_port != 0; + else + return 0; +} + +/** Return true iff <b>node</b> has either kind of usable descriptor -- that + * is, a routerdecriptor or a microdescriptor. */ +int +node_has_descriptor(const node_t *node) +{ + return (node->ri || + (node->rs && node->md)); +} + +/** Return the router_purpose of <b>node</b>. */ +int +node_get_purpose(const node_t *node) +{ + if (node->ri) + return node->ri->purpose; + else + return ROUTER_PURPOSE_GENERAL; +} + +/** Compute the verbose ("extended") nickname of <b>node</b> and store it + * into the MAX_VERBOSE_NICKNAME_LEN+1 character buffer at + * <b>verbose_nickname_out</b> */ +void +node_get_verbose_nickname(const node_t *node, + char *verbose_name_out) +{ + const char *nickname = node_get_nickname(node); + int is_named = node_is_named(node); + verbose_name_out[0] = '$'; + base16_encode(verbose_name_out+1, HEX_DIGEST_LEN+1, node->identity, + DIGEST_LEN); + if (!nickname) + return; + verbose_name_out[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + strlcpy(verbose_name_out+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); +} + +/** Return true iff it seems that <b>node</b> allows circuits to exit + * through it directlry from the client. */ +int +node_allows_single_hop_exits(const node_t *node) +{ + if (node && node->ri) + return node->ri->allow_single_hop_exits; + else + return 0; +} + +/** Return true iff it seems that <b>node</b> has an exit policy that doesn't + * actually permit anything to exit, or we don't know its exit policy */ +int +node_exit_policy_rejects_all(const node_t *node) +{ + if (node->rejects_all) + return 1; + + if (node->ri) + return node->ri->policy_is_reject_star; + else if (node->md) + return node->md->exit_policy == NULL || + short_policy_is_reject_star(node->md->exit_policy); + else + return 1; +} + +/** Copy the address for <b>node</b> into *<b>addr_out</b>. */ +int +node_get_addr(const node_t *node, tor_addr_t *addr_out) +{ + if (node->ri) { + tor_addr_from_ipv4h(addr_out, node->ri->addr); + return 0; + } else if (node->rs) { + tor_addr_from_ipv4h(addr_out, node->rs->addr); + return 0; + } + return -1; +} + +/** Return the host-order IPv4 address for <b>node</b>, or 0 if it doesn't + * seem to have one. */ +uint32_t +node_get_addr_ipv4h(const node_t *node) +{ + if (node->ri) { + return node->ri->addr; + } else if (node->rs) { + return node->rs->addr; + } + return 0; +} + +/** Copy a string representation of the IP address for <b>node</b> into the + * <b>len</b>-byte buffer at <b>buf</b>. + */ +void +node_get_address_string(const node_t *node, char *buf, size_t len) +{ + if (node->ri) { + strlcpy(buf, node->ri->address, len); + } else if (node->rs) { + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, node->rs->addr); + tor_addr_to_str(buf, &addr, len, 0); + } else { + buf[0] = '\0'; + } +} + +/** Return <b>node</b>'s declared uptime, or -1 if it doesn't seem to have + * one. */ +long +node_get_declared_uptime(const node_t *node) +{ + if (node->ri) + return node->ri->uptime; + else + return -1; +} + +/** Return <b>node</b>'s declared or_port */ +uint16_t +node_get_orport(const node_t *node) +{ + if (node->ri) + return node->ri->or_port; + else if (node->rs) + return node->rs->or_port; + else + return 0; +} + +/** Return <b>node</b>'s platform string, or NULL if we don't know it. */ +const char * +node_get_platform(const node_t *node) +{ + /* If we wanted, we could record the version in the routerstatus_t, since + * the consensus lists it. We don't, though, so this function just won't + * work with microdescriptors. */ + if (node->ri) + return node->ri->platform; + else + return NULL; +} + +/** Return <b>node</b>'s time of publication, or 0 if we don't have one. */ +time_t +node_get_published_on(const node_t *node) +{ + if (node->ri) + return node->ri->cache_info.published_on; + else + return 0; +} + +/** Return true iff <b>node</b> is one representing this router. */ +int +node_is_me(const node_t *node) +{ + return router_digest_is_me(node->identity); +} + +/** Return <b>node</b> declared family (as a list of names), or NULL if + * the node didn't declare a family. */ +const smartlist_t * +node_get_declared_family(const node_t *node) +{ + if (node->ri && node->ri->declared_family) + return node->ri->declared_family; + else if (node->md && node->md->family) + return node->md->family; + else + return NULL; +} + diff --git a/src/or/nodelist.h b/src/or/nodelist.h new file mode 100644 index 0000000000..08f11da591 --- /dev/null +++ b/src/or/nodelist.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file microdesc.h + * \brief Header file for microdesc.c. + **/ + +#ifndef _TOR_NODELIST_H +#define _TOR_NODELIST_H + +node_t *node_get_mutable_by_id(const char *identity_digest); +const node_t *node_get_by_id(const char *identity_digest); +const node_t *node_get_by_hex_id(const char *identity_digest); +node_t *nodelist_add_routerinfo(routerinfo_t *ri); +node_t *nodelist_add_microdesc(microdesc_t *md); +void nodelist_set_consensus(networkstatus_t *ns); + +void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md); +void nodelist_remove_routerinfo(routerinfo_t *ri); +void nodelist_purge(void); + +void nodelist_free_all(void); +void nodelist_assert_ok(void); + +const node_t *node_get_by_nickname(const char *nickname, int warn_if_unnamed); +void node_get_verbose_nickname(const node_t *node, + char *verbose_name_out); +int node_is_named(const node_t *node); +int node_is_dir(const node_t *node); +int node_has_descriptor(const node_t *node); +int node_get_purpose(const node_t *node); +#define node_is_bridge(node) \ + (node_get_purpose((node)) == ROUTER_PURPOSE_BRIDGE) +int node_is_me(const node_t *node); +int node_exit_policy_rejects_all(const node_t *node); +int node_get_addr(const node_t *node, tor_addr_t *addr_out); +uint32_t node_get_addr_ipv4h(const node_t *node); +int node_allows_single_hop_exits(const node_t *node); +uint16_t node_get_orport(const node_t *node); +const char *node_get_nickname(const node_t *node); +const char *node_get_platform(const node_t *node); +void node_get_address_string(const node_t *node, char *cp, size_t len); +long node_get_declared_uptime(const node_t *node); +time_t node_get_published_on(const node_t *node); +const smartlist_t *node_get_declared_family(const node_t *node); + +smartlist_t *nodelist_get_list(void); + +/* XXXX These need to move out of routerlist.c */ +void nodelist_refresh_countries(void); +void node_set_country(node_t *node); +void nodelist_add_node_family(smartlist_t *nodes, const node_t *node); +int nodes_in_same_family(const node_t *node1, const node_t *node2); + +#endif + diff --git a/src/or/ntmain.c b/src/or/ntmain.c index 06ca2df91e..88537f3314 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -183,7 +183,6 @@ nt_service_loadlibrary(void) */ int nt_service_is_stopping(void) -/* XXXX this function would probably _love_ to be inline, in 0.2.0. */ { /* If we haven't loaded the function pointers, we can't possibly be an NT * service trying to shut down. */ diff --git a/src/or/or.h b/src/or/or.h index cb36126d99..8aef2d0233 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -83,6 +83,13 @@ #define snprintf _snprintf #endif +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent.h> +#include <event2/buffer.h> +#include <event2/util.h> +#endif + +#include "crypto.h" #include "tortls.h" #include "../common/torlog.h" #include "container.h" @@ -384,7 +391,9 @@ typedef enum { /** A connection to a hidden service directory server: download a v2 rendezvous * descriptor. */ #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 -#define _DIR_PURPOSE_MAX 18 +/** A connection to a directory server: download a microdescriptor. */ +#define DIR_PURPOSE_FETCH_MICRODESC 19 +#define _DIR_PURPOSE_MAX 19 /** True iff <b>p</b> is a purpose corresponding to uploading data to a * directory server. */ @@ -800,6 +809,9 @@ typedef enum { * Tor 0.1.2.x is obsolete, we can remove this. */ #define DEFAULT_CLIENT_NICKNAME "client" +/** Name chosen by routers that don't configure nicknames */ +#define UNNAMED_ROUTER_NICKNAME "Unnamed" + /** Number of bytes in a SOCKS4 header. */ #define SOCKS4_NETWORK_LEN 8 @@ -845,8 +857,8 @@ typedef struct cell_t { typedef struct var_cell_t { uint8_t command; circid_t circ_id; - uint16_t payload_len; - uint8_t payload[1]; + uint16_t payload_len; /**< The actual length of <b>payload</b>. */ + uint8_t payload[FLEXIBLE_ARRAY_MEMBER]; } var_cell_t; /** A cell as packed for writing to the network. */ @@ -963,6 +975,7 @@ typedef struct connection_t { /** Our socket; -1 if this connection is closed, or has no socket. */ evutil_socket_t s; int conn_array_index; /**< Index into the global connection array. */ + struct event *read_event; /**< Libevent event structure. */ struct event *write_event; /**< Libevent event structure. */ buf_t *inbuf; /**< Buffer holding data read over this connection. */ @@ -973,6 +986,11 @@ typedef struct connection_t { * read? */ time_t timestamp_lastwritten; /**< When was the last time libevent said we * could write? */ + +#ifdef USE_BUFFEREVENTS + struct bufferevent *bufev; /**< A Libevent buffered IO structure. */ +#endif + time_t timestamp_created; /**< When was this connection_t created? */ /* XXXX_IP6 make this IPv6-capable */ @@ -1072,10 +1090,16 @@ typedef struct or_connection_t { /* bandwidth* and *_bucket only used by ORs in OPEN state: */ int bandwidthrate; /**< Bytes/s added to the bucket. (OPEN ORs only.) */ int bandwidthburst; /**< Max bucket size for this conn. (OPEN ORs only.) */ +#ifndef USE_BUFFEREVENTS int read_bucket; /**< When this hits 0, stop receiving. Every second we * add 'bandwidthrate' to this, capping it at * bandwidthburst. (OPEN ORs only) */ int write_bucket; /**< When this hits 0, stop writing. Like read_bucket. */ +#else + /** DOCDOC */ + /* XXXX we could share this among all connections. */ + struct ev_token_bucket_cfg *bucket_cfg; +#endif int n_circuits; /**< How many circuits use this connection as p_conn or * n_conn ? */ @@ -1179,8 +1203,13 @@ typedef struct edge_connection_t { typedef struct dir_connection_t { connection_t _base; - char *requested_resource; /**< Which 'resource' did we ask the directory - * for? */ + /** Which 'resource' did we ask the directory for? This is typically the part + * of the URL string that defines, relative to the directory conn purpose, + * what thing we want. For example, in router descriptor downloads by + * descriptor digest, it contains "d/", then one ore more +-separated + * fingerprints. + **/ + char *requested_resource; unsigned int dirconn_direct:1; /**< Is this dirconn direct, or via Tor? */ /* Used only for server sides of some dir connections, to implement @@ -1271,6 +1300,51 @@ static INLINE control_connection_t *TO_CONTROL_CONN(connection_t *c) return DOWNCAST(control_connection_t, c); } +/* Conditional macros to help write code that works whether bufferevents are + disabled or not. + + We can't just write: + if (conn->bufev) { + do bufferevent stuff; + } else { + do other stuff; + } + because the bufferevent stuff won't even compile unless we have a fairly + new version of Libevent. Instead, we say: + IF_HAS_BUFFEREVENT(conn, { do_bufferevent_stuff } ); + or: + IF_HAS_BUFFEREVENT(conn, { + do bufferevent stuff; + }) ELSE_IF_NO_BUFFEREVENT { + do non-bufferevent stuff; + } + If we're compiling with bufferevent support, then the macros expand more or + less to: + if (conn->bufev) { + do_bufferevent_stuff; + } else { + do non-bufferevent stuff; + } + and if we aren't using bufferevents, they expand more or less to: + { do non-bufferevent stuff; } +*/ +#ifdef USE_BUFFEREVENTS +#define HAS_BUFFEREVENT(c) (((c)->bufev) != NULL) +#define IF_HAS_BUFFEREVENT(c, stmt) \ + if ((c)->bufev) do { \ + stmt ; \ + } while (0) +#define ELSE_IF_NO_BUFFEREVENT ; else +#define IF_HAS_NO_BUFFEREVENT(c) \ + if (NULL == (c)->bufev) +#else +#define HAS_BUFFEREVENT(c) (0) +#define IF_HAS_BUFFEREVENT(c, stmt) (void)0 +#define ELSE_IF_NO_BUFFEREVENT ; +#define IF_HAS_NO_BUFFEREVENT(c) \ + if (1) +#endif + /** What action type does an address policy indicate: accept or reject? */ typedef enum { ADDR_POLICY_ACCEPT=1, @@ -1434,59 +1508,49 @@ typedef struct { char *contact_info; /**< Declared contact info for this router. */ unsigned int is_hibernating:1; /**< Whether the router claims to be * hibernating */ - unsigned int has_old_dnsworkers:1; /**< Whether the router is using - * dnsworker code. */ - unsigned int caches_extra_info:1; /**< Whether the router caches and serves - * extrainfo documents. */ - unsigned int allow_single_hop_exits:1; /**< Whether the router allows - * single hop exits. */ - - /* local info */ - unsigned int is_running:1; /**< As far as we know, is this OR currently - * running? */ - unsigned int is_valid:1; /**< Has a trusted dirserver validated this OR? - * (For Authdir: Have we validated this OR?) - */ - unsigned int is_named:1; /**< Do we believe the nickname that this OR gives - * us? */ - unsigned int is_fast:1; /** Do we think this is a fast OR? */ - unsigned int is_stable:1; /** Do we think this is a stable OR? */ - unsigned int is_possible_guard:1; /**< Do we think this is an OK guard? */ - unsigned int is_exit:1; /**< Do we think this is an OK exit? */ - unsigned int is_bad_exit:1; /**< Do we think this exit is censored, borked, - * or otherwise nasty? */ - unsigned int is_bad_directory:1; /**< Do we think this directory is junky, - * underpowered, or otherwise useless? */ + unsigned int caches_extra_info:1; /**< Whether the router says it caches and + * serves extrainfo documents. */ + unsigned int allow_single_hop_exits:1; /**< Whether the router says + * it allows single hop exits. */ + unsigned int wants_to_be_hs_dir:1; /**< True iff this router claims to be * a hidden service directory. */ - unsigned int is_hs_dir:1; /**< True iff this router is a hidden service - * directory according to the authorities. */ unsigned int policy_is_reject_star:1; /**< True iff the exit policy for this * router rejects everything. */ /** True if, after we have added this router, we should re-launch * tests for it. */ unsigned int needs_retest_if_added:1; -/** Tor can use this router for general positions in circuits. */ +/** Tor can use this router for general positions in circuits; we got it + * from a directory server as usual, or we're an authority and a server + * uploaded it. */ #define ROUTER_PURPOSE_GENERAL 0 -/** Tor should avoid using this router for circuit-building. */ +/** Tor should avoid using this router for circuit-building: we got it + * from a crontroller. If the controller wants to use it, it'll have to + * ask for it by identity. */ #define ROUTER_PURPOSE_CONTROLLER 1 -/** Tor should use this router only for bridge positions in circuits. */ +/** Tor should use this router only for bridge positions in circuits: we got + * it via a directory request from the bridge itself, or a bridge + * authority. x*/ #define ROUTER_PURPOSE_BRIDGE 2 /** Tor should not use this router; it was marked in cached-descriptors with * a purpose we didn't recognize. */ #define ROUTER_PURPOSE_UNKNOWN 255 - uint8_t purpose; /** What positions in a circuit is this router good for? */ + /* In what way did we find out about this router? One of ROUTER_PURPOSE_*. + * Routers of different purposes are kept segregated and used for different + * things; see notes on ROUTER_PURPOSE_* macros above. + */ + uint8_t purpose; /* The below items are used only by authdirservers for * reachability testing. */ + /** When was the last time we could reach this OR? */ time_t last_reachable; /** When did we start testing reachability for this OR? */ time_t testing_since; - /** According to the geoip db what country is this router in? */ - country_t country; + } routerinfo_t; /** Information needed to keep and cache a signed extra-info document. */ @@ -1512,8 +1576,9 @@ typedef struct routerstatus_t { * has. */ char identity_digest[DIGEST_LEN]; /**< Digest of the router's identity * key. */ - char descriptor_digest[DIGEST_LEN]; /**< Digest of the router's most recent - * descriptor. */ + /** Digest of the router's most recent descriptor or microdescriptor. + * If it's a descriptor, we only use the first DIGEST_LEN bytes. */ + char descriptor_digest[DIGEST256_LEN]; uint32_t addr; /**< IPv4 address for this router. */ uint16_t or_port; /**< OR port for this router. */ uint16_t dir_port; /**< Directory port for this router. */ @@ -1521,7 +1586,11 @@ typedef struct routerstatus_t { unsigned int is_exit:1; /**< True iff this router is a good exit. */ unsigned int is_stable:1; /**< True iff this router stays up a long time. */ unsigned int is_fast:1; /**< True iff this router has good bandwidth. */ - unsigned int is_running:1; /**< True iff this router is up. */ + /** True iff this router is called 'running' in the consensus. We give it + * this funny name so that we don't accidentally use this bit as a view of + * whether we think the router is *currently* running. If that's what you + * want to know, look at is_running in node_t. */ + unsigned int is_flagged_running:1; unsigned int is_named:1; /**< True iff "nickname" belongs to this router. */ unsigned int is_unnamed:1; /**< True iff "nickname" belongs to another * router. */ @@ -1573,15 +1642,31 @@ typedef struct routerstatus_t { * from this authority.) Applies in v2 networkstatus document only. */ unsigned int need_to_mirror:1; - unsigned int name_lookup_warned:1; /**< Have we warned the user for referring - * to this (unnamed) router by nickname? - */ time_t last_dir_503_at; /**< When did this router last tell us that it * was too busy to serve directory info? */ download_status_t dl_status; } routerstatus_t; +/** A single entry in a parsed policy summary, describing a range of ports. */ +typedef struct short_policy_entry_t { + uint16_t min_port, max_port; +} short_policy_entry_t; + +/** A short_poliy_t is the parsed version of a policy summary. */ +typedef struct short_policy_t { + /** True if the members of 'entries' are port ranges to accept; false if + * they are port ranges to reject */ + unsigned int is_accept : 1; + /** The actual number of values in 'entries'. */ + unsigned int n_entries : 31; + /** An array of 0 or more short_policy_entry_t values, each descriping a + * range of ports that this policy accepts or rejects (depending on the + * value of is_accept). + */ + short_policy_entry_t entries[FLEXIBLE_ARRAY_MEMBER]; +} short_policy_t; + /** A microdescriptor is the smallest amount of information needed to build a * circuit through a router. They are generated by the directory authorities, * using information from the uploaded routerinfo documents. They are not @@ -1623,15 +1708,83 @@ typedef struct microdesc_t { crypto_pk_env_t *onion_pkey; /** As routerinfo_t.family */ smartlist_t *family; - /** Encoded exit policy summary */ - char *exitsummary; /**< exit policy summary - - * XXX this probably should not stay a string. */ + /** Exit policy summary */ + short_policy_t *exit_policy; } microdesc_t; +/** A node_t represents a Tor router. + * + * Specifically, a node_t is a Tor router as we are using it: a router that + * we are considering for circuits, connections, and so on. A node_t is a + * thin wrapper around the routerstatus, routerinfo, and microdesc for a + * single wrapper, and provides a consistent interface for all of them. + * + * Also, a node_t has mutable state. While a routerinfo, a routerstatus, + * and a microdesc have[*] only the information read from a router + * descriptor, a consensus entry, and a microdescriptor (respectively)... + * a node_t has flags based on *our own current opinion* of the node. + * + * [*] Actually, there is some leftover information in each that is mutable. + * We should try to excise that. + */ +typedef struct node_t { + /* Indexing information */ + + /** Used to look up the node_t by its identity digest. */ + HT_ENTRY(node_t) ht_ent; + /** Position of the node within the list of nodes */ + int nodelist_idx; + + /** The identity digest of this node_t. No more than one node_t per + * identity may exist at a time. */ + char identity[DIGEST_LEN]; + + microdesc_t *md; + routerinfo_t *ri; + routerstatus_t *rs; + + /* local info: copied from routerstatus, then possibly frobbed based + * on experience. Authorities set this stuff directly. */ + + unsigned int is_running:1; /**< As far as we know, is this OR currently + * running? */ + unsigned int is_valid:1; /**< Has a trusted dirserver validated this OR? + * (For Authdir: Have we validated this OR?) + */ + unsigned int is_fast:1; /** Do we think this is a fast OR? */ + unsigned int is_stable:1; /** Do we think this is a stable OR? */ + unsigned int is_possible_guard:1; /**< Do we think this is an OK guard? */ + unsigned int is_exit:1; /**< Do we think this is an OK exit? */ + unsigned int is_bad_exit:1; /**< Do we think this exit is censored, borked, + * or otherwise nasty? */ + unsigned int is_bad_directory:1; /**< Do we think this directory is junky, + * underpowered, or otherwise useless? */ + unsigned int is_hs_dir:1; /**< True iff this router is a hidden service + * directory according to the authorities. */ + + /* Local info: warning state. */ + + unsigned int name_lookup_warned:1; /**< Have we warned the user for referring + * to this (unnamed) router by nickname? + */ + + /** Local info: we treat this node as if it rejects everything */ + unsigned int rejects_all:1; + + /* Local info: derived. */ + + /** According to the geoip db what country is this router in? */ + country_t country; +} 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? */ @@ -2107,10 +2260,12 @@ typedef struct circuit_t { * length ONIONSKIN_CHALLENGE_LEN. */ char *n_conn_onionskin; - time_t timestamp_created; /**< When was this circuit created? */ + /** When was this circuit created? We keep this timestamp with a higher + * resolution than most so that the circuit-build-time tracking code can + * get millisecond resolution. */ + struct timeval timestamp_created; time_t timestamp_dirty; /**< When the circuit was first used, or 0 if the * circuit is clean. */ - struct timeval highres_created; /**< When exactly was the circuit created? */ uint16_t marked_for_close; /**< Should we close this circuit at the end of * the main loop? (If true, holds the line number @@ -2335,6 +2490,7 @@ typedef struct { config_line_t *Logs; /**< New-style list of configuration lines * for logs */ + int LogTimeGranularity; /**< Log resolution in milliseconds. */ char *DebugLogFile; /**< Where to send verbose log messages. */ char *DataDirectory; /**< OR only: where to store long-term data. */ @@ -2591,7 +2747,8 @@ typedef struct { char *MyFamily; /**< Declared family for this OR. */ config_line_t *NodeFamilies; /**< List of config lines for - * node families */ + * node families */ + smartlist_t *NodeFamilySets; /**< List of parsed NodeFamilies values. */ config_line_t *AuthDirBadDir; /**< Address policy for descriptors to * mark as bad dir mirrors. */ config_line_t *AuthDirBadExit; /**< Address policy for descriptors to @@ -2696,6 +2853,10 @@ typedef struct { * possible. */ int PreferTunneledDirConns; /**< If true, avoid dirservers that don't * support BEGIN_DIR, when possible. */ + int PortForwarding; /**< If true, use NAT-PMP or UPnP to automatically + * forward the DirPort and ORPort on the NAT device */ + char *PortForwardingHelper; /** < Filename or full path of the port + forwarding helper executable */ int AllowNonRFC953Hostnames; /**< If true, we allow connections to hostnames * with weird characters. */ /** If true, we try resolving hostnames with weird characters. */ @@ -2733,6 +2894,9 @@ typedef struct { /** If true, the user wants us to collect statistics on port usage. */ int ExitPortStatistics; + /** If true, the user wants us to collect connection statistics. */ + int ConnDirectionStatistics; + /** If true, the user wants us to collect cell statistics. */ int CellStatistics; @@ -2825,6 +2989,12 @@ typedef struct { */ double CircuitPriorityHalflife; + /** If true, do not enable IOCP on windows with bufferevents, even if + * we think we could. */ + int DisableIOCP; + /** For testing only: will go away in 0.2.3.x. */ + int _UseFilteringSSLBufferevents; + } or_options_t; /** Persistent state for an onion router, as saved to disk. */ @@ -2933,8 +3103,6 @@ struct socks_request_t { * every connection. */ }; -/* all the function prototypes go here */ - /********************************* circuitbuild.c **********************/ /** How many hops does a general-purpose circuit have by default? */ @@ -3303,7 +3471,7 @@ typedef enum { ADDR_POLICY_PROBABLY_ACCEPTED=1, /** Part of the address was unknown, but as far as we can tell, it was * rejected. */ - ADDR_POLICY_PROBABLY_REJECTED=2 + ADDR_POLICY_PROBABLY_REJECTED=2, } addr_policy_result_t; /********************************* rephist.c ***************************/ @@ -3434,6 +3602,8 @@ typedef struct trusted_dir_server_t { * fetches to _any_ single directory server.] */ #define PDS_NO_EXISTING_SERVERDESC_FETCH (1<<3) +#define PDS_NO_EXISTING_MICRODESC_FETCH (1<<4) + #define _PDS_PREFER_TUNNELED_DIR_CONNS (1<<16) /** Possible ways to weight routers when choosing one randomly. See @@ -3451,7 +3621,8 @@ typedef enum { CRN_NEED_GUARD = 1<<2, CRN_ALLOW_INVALID = 1<<3, /* XXXX not used, apparently. */ - CRN_WEIGHT_AS_EXIT = 1<<5 + CRN_WEIGHT_AS_EXIT = 1<<5, + CRN_NEED_DESC = 1<<6 } router_crn_flags_t; /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ diff --git a/src/or/policies.c b/src/or/policies.c index 6947222b2e..e5af7887c7 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -11,6 +11,7 @@ #include "or.h" #include "config.h" #include "dirserv.h" +#include "nodelist.h" #include "policies.h" #include "routerparse.h" #include "ht.h" @@ -261,7 +262,7 @@ fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port) /** Return true iff we think our firewall will let us make an OR connection to * <b>ri</b>. */ int -fascist_firewall_allows_or(routerinfo_t *ri) +fascist_firewall_allows_or(const routerinfo_t *ri) { /* XXXX proposal 118 */ tor_addr_t addr; @@ -269,6 +270,22 @@ fascist_firewall_allows_or(routerinfo_t *ri) return fascist_firewall_allows_address_or(&addr, ri->or_port); } +/** Return true iff we think our firewall will let us make an OR connection to + * <b>node</b>. */ +int +fascist_firewall_allows_node(const node_t *node) +{ + if (node->ri) { + return fascist_firewall_allows_or(node->ri); + } else if (node->rs) { + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, node->rs->addr); + return fascist_firewall_allows_address_or(&addr, node->rs->or_port); + } else { + return 1; + } +} + /** Return true iff we think our firewall will let us make a directory * connection to addr:port. */ int @@ -858,15 +875,11 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, return 0; } -/** Replace the exit policy of <b>r</b> with reject *:*. */ +/** Replace the exit policy of <b>node</b> with reject *:* */ void -policies_set_router_exitpolicy_to_reject_all(routerinfo_t *r) +policies_set_node_exitpolicy_to_reject_all(node_t *node) { - addr_policy_t *item; - addr_policy_list_free(r->exit_policy); - r->exit_policy = smartlist_create(); - item = router_parse_addr_policy_item_from_string("reject *:*", -1); - smartlist_add(r->exit_policy, item); + node->rejects_all = 1; } /** Return 1 if there is at least one /8 subnet in <b>policy</b> that @@ -1075,7 +1088,7 @@ policy_summary_split(smartlist_t *summary, int start_at_index; int i = 0; - /* XXXX Do a binary search if run time matters */ + while (AT(i)->prt_max < prt_min) i++; if (AT(i)->prt_min != prt_min) { @@ -1288,6 +1301,195 @@ policy_summarize(smartlist_t *policy) return result; } +/** Convert a summarized policy string into a short_policy_t. Return NULL + * if the string is not well-formed. */ +short_policy_t * +parse_short_policy(const char *summary) +{ + const char *orig_summary = summary; + short_policy_t *result; + int is_accept; + int n_entries; + short_policy_entry_t entries[MAX_EXITPOLICY_SUMMARY_LEN]; /* overkill */ + const char *next; + + if (!strcmpstart(summary, "accept ")) { + is_accept = 1; + summary += strlen("accept "); + } else if (!strcmpstart(summary, "reject ")) { + is_accept = 0; + summary += strlen("reject "); + } else { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Unrecognized policy summary keyword"); + return NULL; + } + + n_entries = 0; + for ( ; *summary; summary = next) { + const char *comma = strchr(summary, ','); + unsigned low, high; + char dummy; + char ent_buf[32]; + + next = comma ? comma+1 : strchr(summary, '\0'); + + if (n_entries == MAX_EXITPOLICY_SUMMARY_LEN) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Impossibly long policy summary %s", + escaped(orig_summary)); + return NULL; + } + + if (! TOR_ISDIGIT(*summary) || next-summary > (int)(sizeof(ent_buf)-1)) { + /* unrecognized entry format. skip it. */ + continue; + } + if (next-summary < 2) { + /* empty; skip it. */ + continue; + } + + memcpy(ent_buf, summary, next-summary-1); + ent_buf[next-summary-1] = '\0'; + + if (tor_sscanf(ent_buf, "%u-%u%c", &low, &high, &dummy) == 2) { + if (low<1 || low>65535 || high<1 || high>65535) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "Found bad entry in policy summary %s", escaped(orig_summary)); + return NULL; + } + } else if (tor_sscanf(ent_buf, "%u%c", &low, &dummy) == 1) { + if (low<1 || low>65535) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "Found bad entry in policy summary %s", escaped(orig_summary)); + return NULL; + } + high = low; + } else { + log_fn(LOG_PROTOCOL_WARN, LD_DIR,"Found bad entry in policy summary %s", + escaped(orig_summary)); + return NULL; + } + + entries[n_entries].min_port = low; + entries[n_entries].max_port = high; + n_entries++; + } + + if (n_entries == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "Found no port-range entries in summary %s", escaped(orig_summary)); + return NULL; + } + + { + size_t size = STRUCT_OFFSET(short_policy_t, entries) + + sizeof(short_policy_entry_t)*(n_entries); + result = tor_malloc_zero(size); + + tor_assert( (char*)&result->entries[n_entries-1] < ((char*)result)+size); + } + + result->is_accept = is_accept; + result->n_entries = n_entries; + memcpy(result->entries, entries, sizeof(short_policy_entry_t)*n_entries); + return result; +} + +/** Release all storage held in <b>policy</b>. */ +void +short_policy_free(short_policy_t *policy) +{ + tor_free(policy); +} + +/** See whether the <b>addr</b>:<b>port</b> address is likely to be accepted + * or rejected by the summarized policy <b>policy</b>. Return values are as + * for compare_tor_addr_to_addr_policy. Unlike the regular addr_policy + * functions, requires the <b>port</b> be specified. */ +addr_policy_result_t +compare_tor_addr_to_short_policy(const tor_addr_t *addr, uint16_t port, + const short_policy_t *policy) +{ + int i; + int found_match = 0; + int accept; + (void)addr; + + tor_assert(port != 0); + + if (addr && (tor_addr_is_internal(addr, 0) || + tor_addr_is_null(addr) || + tor_addr_is_loopback(addr))) + return ADDR_POLICY_REJECTED; + + for (i=0; i < policy->n_entries; ++i) { + const short_policy_entry_t *e = &policy->entries[i]; + if (e->min_port <= port && port <= e->max_port) { + found_match = 1; + break; + } + } + + if (found_match) + accept = policy->is_accept; + else + accept = ! policy->is_accept; + + /* ???? are these right? */ + if (accept) + return ADDR_POLICY_PROBABLY_ACCEPTED; + else + return ADDR_POLICY_REJECTED; +} + +/** Return true iff <b>policy</b> seems reject all ports */ +int +short_policy_is_reject_star(const short_policy_t *policy) +{ + /* This doesn't need to be as much on the lookout as policy_is_reject_star, + * since policy summaries are from the consensus or from consensus + * microdescs. + */ + tor_assert(policy); + /* Check for an exact match of "reject 1-65535". */ + return (policy->is_accept == 0 && policy->n_entries == 1 && + policy->entries[0].min_port == 1 && + policy->entries[0].max_port == 65535); +} + +/** Decides whether addr:port is probably or definitely accepted or rejcted by + * <b>node</b>. See compare_tor_addr_to_addr_policy for details on addr/port + * interpretation. */ +addr_policy_result_t +compare_addr_to_node_policy(uint32_t addr, uint16_t port, const node_t *node) +{ + tor_addr_t a; + tor_addr_from_ipv4h(&a, addr); + return compare_tor_addr_to_node_policy(&a, port, node); +} + +/** Decides whether addr:port is probably or definitely accepted or rejcted by + * <b>node</b>. See compare_tor_addr_to_addr_policy for details on addr/port + * interpretation. */ +addr_policy_result_t +compare_tor_addr_to_node_policy(const tor_addr_t *addr, uint16_t port, + const node_t *node) +{ + if (node->rejects_all) + return ADDR_POLICY_REJECTED; + + if (node->ri) + return compare_tor_addr_to_addr_policy(addr, port, node->ri->exit_policy); + else if (node->md && node->md) { + if (node->md->exit_policy == NULL) + return ADDR_POLICY_REJECTED; + else + return compare_tor_addr_to_short_policy(addr, port, + node->md->exit_policy); + } else + return ADDR_POLICY_PROBABLY_REJECTED; +} + /** Implementation for GETINFO control command: knows the answer for questions * about "exit-policy/..." */ int diff --git a/src/or/policies.h b/src/or/policies.h index a954ac4f5f..371123c1e8 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -19,7 +19,8 @@ int firewall_is_fascist_or(void); int fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port); -int fascist_firewall_allows_or(routerinfo_t *ri); +int fascist_firewall_allows_or(const routerinfo_t *ri); +int fascist_firewall_allows_node(const node_t *node); int fascist_firewall_allows_address_dir(const tor_addr_t *addr, uint16_t port); int dir_policy_permits_address(const tor_addr_t *addr); int socks_policy_permits_address(const tor_addr_t *addr); @@ -38,10 +39,16 @@ addr_policy_result_t compare_tor_addr_to_addr_policy(const tor_addr_t *addr, uint16_t port, const smartlist_t *policy); addr_policy_result_t compare_addr_to_addr_policy(uint32_t addr, uint16_t port, const smartlist_t *policy); + +addr_policy_result_t compare_addr_to_node_policy(uint32_t addr, + uint16_t port, const node_t *node); +addr_policy_result_t compare_tor_addr_to_node_policy(const tor_addr_t *addr, + uint16_t port, const node_t *node); + int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, int rejectprivate, const char *local_address, int add_default_policy); -void policies_set_router_exitpolicy_to_reject_all(routerinfo_t *exitrouter); +void policies_set_node_exitpolicy_to_reject_all(node_t *exitrouter); int exit_policy_is_general_exit(smartlist_t *policy); int policy_is_reject_star(const smartlist_t *policy); int getinfo_helper_policies(control_connection_t *conn, @@ -56,5 +63,12 @@ void policies_free_all(void); char *policy_summarize(smartlist_t *policy); +short_policy_t *parse_short_policy(const char *summary); +void short_policy_free(short_policy_t *policy); +int short_policy_is_reject_star(const short_policy_t *policy); +addr_policy_result_t compare_tor_addr_to_short_policy( + const tor_addr_t *addr, uint16_t port, + const short_policy_t *policy); + #endif diff --git a/src/or/relay.c b/src/or/relay.c index 32ac96edf4..c2035b0266 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -24,6 +24,7 @@ #include "main.h" #include "mempool.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -704,7 +705,7 @@ connection_ap_process_end_not_open( edge_connection_t *conn, crypt_path_t *layer_hint) { struct in_addr in; - routerinfo_t *exitrouter; + node_t *exitrouter; int reason = *(cell->payload+RELAY_HEADER_SIZE); int control_reason = reason | END_STREAM_REASON_FLAG_REMOTE; (void) layer_hint; /* unused */ @@ -712,11 +713,12 @@ connection_ap_process_end_not_open( if (rh->length > 0 && edge_reason_is_retriable(reason) && !connection_edge_is_rendezvous_stream(conn) /* avoid retry if rend */ ) { + const char *chosen_exit_digest = + circ->build_state->chosen_exit->identity_digest; log_info(LD_APP,"Address '%s' refused due to '%s'. Considering retrying.", safe_str(conn->socks_request->address), stream_end_reason_to_string(reason)); - exitrouter = - router_get_by_digest(circ->build_state->chosen_exit->identity_digest); + exitrouter = node_get_mutable_by_id(chosen_exit_digest); switch (reason) { case END_STREAM_REASON_EXITPOLICY: if (rh->length >= 5) { @@ -751,8 +753,8 @@ connection_ap_process_end_not_open( log_info(LD_APP, "Exitrouter '%s' seems to be more restrictive than its exit " "policy. Not using this router as exit for now.", - exitrouter->nickname); - policies_set_router_exitpolicy_to_reject_all(exitrouter); + node_get_nickname(exitrouter)); + policies_set_node_exitpolicy_to_reject_all(exitrouter); } /* rewrite it to an IP if we learned one. */ if (addressmap_rewrite(conn->socks_request->address, @@ -815,7 +817,7 @@ connection_ap_process_end_not_open( case END_STREAM_REASON_HIBERNATING: case END_STREAM_REASON_RESOURCELIMIT: if (exitrouter) { - policies_set_router_exitpolicy_to_reject_all(exitrouter); + policies_set_node_exitpolicy_to_reject_all(exitrouter); } if (conn->chosen_exit_optional) { /* stop wanting a specific exit */ @@ -898,12 +900,8 @@ connection_edge_process_relay_cell_not_open( int ttl; if (!addr || (get_options()->ClientDNSRejectInternalAddresses && is_internal_IP(addr, 0))) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; - a.s_addr = htonl(addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_APP, - "...but it claims the IP address was %s. Closing.", buf); + log_info(LD_APP, "...but it claims the IP address was %s. Closing.", + fmt_addr32(addr)); connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return 0; @@ -980,11 +978,8 @@ connection_edge_process_relay_cell_not_open( uint32_t addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+2)); if (get_options()->ClientDNSRejectInternalAddresses && is_internal_IP(addr, 0)) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; - a.s_addr = htonl(addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_APP,"Got a resolve with answer %s. Rejecting.", buf); + log_info(LD_APP,"Got a resolve with answer %s. Rejecting.", + fmt_addr32(addr)); connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_ERROR_TRANSIENT, 0, NULL, 0, TIME_MAX); @@ -1149,8 +1144,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (!conn->_base.marked_for_close) { /* only mark it if not already marked. it's possible to * get the 'end' right around when the client hangs up on us. */ - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); } return 0; case RELAY_COMMAND_EXTEND: @@ -1357,7 +1351,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, return 0; } - amount_to_process = buf_datalen(conn->_base.inbuf); + amount_to_process = connection_get_inbuf_len(TO_CONN(conn)); if (!amount_to_process) return 0; @@ -1376,7 +1370,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, connection_fetch_from_buf(payload, length, TO_CONN(conn)); log_debug(domain,"(%d) Packaging %d bytes (%d waiting).", conn->_base.s, - (int)length, (int)buf_datalen(conn->_base.inbuf)); + (int)length, (int)connection_get_inbuf_len(TO_CONN(conn))); if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, payload, length) < 0 ) @@ -1528,7 +1522,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); - if (buf_datalen(conn->_base.inbuf) > 0) + if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; } } @@ -1539,7 +1533,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); - if (buf_datalen(conn->_base.inbuf) > 0) + if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; } } @@ -1578,7 +1572,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, } /* If there's still data to read, we'll be coming back to this stream. */ - if (buf_datalen(conn->_base.inbuf)) + if (connection_get_inbuf_len(TO_CONN(conn))) ++n_streams_left; /* If the circuit won't accept any more data, return without looking @@ -2242,7 +2236,7 @@ set_streams_blocked_on_circ(circuit_t *circ, or_connection_t *orconn, edge->edge_blocked_on_circ = block; } - if (!conn->read_event) { + if (!conn->read_event && !HAS_BUFFEREVENT(conn)) { /* This connection is a placeholder for something; probably a DNS * request. It can't actually stop or start reading.*/ continue; @@ -2443,7 +2437,7 @@ append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, make_circuit_active_on_conn(circ, orconn); } - if (! buf_datalen(orconn->_base.outbuf)) { + if (! connection_get_outbuf_len(TO_CONN(orconn))) { /* 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. diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 7c626c6a64..88c9fd7afb 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -16,6 +16,7 @@ #include "connection_edge.h" #include "directory.h" #include "main.h" +#include "nodelist.h" #include "relay.h" #include "rendclient.h" #include "rendcommon.h" @@ -414,7 +415,7 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) SMARTLIST_FOREACH(responsible_dirs, routerstatus_t *, dir, { if (lookup_last_hid_serv_request(dir, desc_id_base32, 0, 0) + REND_HID_SERV_DIR_REQUERY_PERIOD >= now || - !router_get_by_digest(dir->identity_digest)) + !router_get_by_id_digest(dir->identity_digest)) SMARTLIST_DEL_CURRENT(responsible_dirs, dir); }); @@ -739,7 +740,6 @@ rend_client_get_random_intro(const rend_data_t *rend_query) int i; rend_cache_entry_t *entry; rend_intro_point_t *intro; - routerinfo_t *router; if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) { log_warn(LD_REND, @@ -756,11 +756,12 @@ rend_client_get_random_intro(const rend_data_t *rend_query) intro = smartlist_get(entry->parsed->intro_nodes, i); /* Do we need to look up the router or is the extend info complete? */ if (!intro->extend_info->onion_key) { + const node_t *node; if (tor_digest_is_zero(intro->extend_info->identity_digest)) - router = router_get_by_hexdigest(intro->extend_info->nickname); + node = node_get_by_hex_id(intro->extend_info->nickname); else - router = router_get_by_digest(intro->extend_info->identity_digest); - if (!router) { + node = node_get_by_id(intro->extend_info->identity_digest); + if (!node) { log_info(LD_REND, "Unknown router with nickname '%s'; trying another.", intro->extend_info->nickname); rend_intro_point_free(intro); @@ -768,7 +769,7 @@ rend_client_get_random_intro(const rend_data_t *rend_query) goto again; } extend_info_free(intro->extend_info); - intro->extend_info = extend_info_from_router(router); + intro->extend_info = extend_info_from_node(node); } return extend_info_dup(intro->extend_info); } diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 44b5a4b4c0..9f775ef842 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -14,6 +14,7 @@ #include "config.h" #include "directory.h" #include "networkstatus.h" +#include "nodelist.h" #include "rendclient.h" #include "rendcommon.h" #include "rendservice.h" @@ -1001,7 +1002,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, } else { char *rp_nickname; size_t nickname_field_len; - routerinfo_t *router; + const node_t *node; int version; if (*buf == 1) { rp_nickname = buf+1; @@ -1028,8 +1029,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, len -= nickname_field_len; len -= rp_nickname - buf; /* also remove header space used by version, if * any */ - router = router_get_by_nickname(rp_nickname, 0); - if (!router) { + node = node_get_by_nickname(rp_nickname, 0); + if (!node) { log_info(LD_REND, "Couldn't find router %s named in introduce2 cell.", escaped_safe_str_client(rp_nickname)); /* XXXX Add a no-such-router reason? */ @@ -1037,7 +1038,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, goto err; } - extend_info = extend_info_from_router(router); + extend_info = extend_info_from_node(node); } if (len != REND_COOKIE_LEN+DH_KEY_LEN) { @@ -1578,7 +1579,7 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, hs_dir->identity_digest)) /* Don't upload descriptor if we succeeded in doing so last time. */ continue; - if (!router_get_by_digest(hs_dir->identity_digest)) { + if (!router_get_by_id_digest(hs_dir->identity_digest)) { log_info(LD_REND, "Not sending publish request for v2 descriptor to " "hidden service directory '%s'; we don't have its " "router descriptor. Queuing for later upload.", @@ -1755,19 +1756,19 @@ void rend_services_introduce(void) { int i,j,r; - routerinfo_t *router; + const node_t *node; rend_service_t *service; rend_intro_point_t *intro; int changed, prev_intro_nodes; - smartlist_t *intro_routers; + smartlist_t *intro_nodes; time_t now; or_options_t *options = get_options(); - intro_routers = smartlist_create(); + intro_nodes = smartlist_create(); now = time(NULL); for (i=0; i < smartlist_len(rend_service_list); ++i) { - smartlist_clear(intro_routers); + smartlist_clear(intro_nodes); service = smartlist_get(rend_service_list, i); tor_assert(service); @@ -1787,8 +1788,8 @@ rend_services_introduce(void) service. */ for (j=0; j < smartlist_len(service->intro_nodes); ++j) { intro = smartlist_get(service->intro_nodes, j); - router = router_get_by_digest(intro->extend_info->identity_digest); - if (!router || !find_intro_circuit(intro, service->pk_digest)) { + node = node_get_by_id(intro->extend_info->identity_digest); + if (!node || !find_intro_circuit(intro, service->pk_digest)) { log_info(LD_REND,"Giving up on %s as intro point for %s.", intro->extend_info->nickname, service->service_id); if (service->desc) { @@ -1807,8 +1808,8 @@ rend_services_introduce(void) smartlist_del(service->intro_nodes,j--); changed = 1; } - if (router) - smartlist_add(intro_routers, router); + if (node) + smartlist_add(intro_nodes, (void*)node); } /* We have enough intro points, and the intro points we thought we had were @@ -1837,26 +1838,26 @@ rend_services_introduce(void) #define NUM_INTRO_POINTS_INIT (NUM_INTRO_POINTS + 2) for (j=prev_intro_nodes; j < (prev_intro_nodes == 0 ? NUM_INTRO_POINTS_INIT : NUM_INTRO_POINTS); ++j) { - router_crn_flags_t flags = CRN_NEED_UPTIME; + router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC; if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION) flags |= CRN_ALLOW_INVALID; - router = router_choose_random_node(intro_routers, - options->ExcludeNodes, flags); - if (!router) { + node = router_choose_random_node(intro_nodes, + options->ExcludeNodes, flags); + if (!node) { log_warn(LD_REND, "Could only establish %d introduction points for %s.", smartlist_len(service->intro_nodes), service->service_id); break; } changed = 1; - smartlist_add(intro_routers, router); + smartlist_add(intro_nodes, (void*)node); intro = tor_malloc_zero(sizeof(rend_intro_point_t)); - intro->extend_info = extend_info_from_router(router); + intro->extend_info = extend_info_from_node(node); intro->intro_key = crypto_new_pk_env(); tor_assert(!crypto_pk_generate_key(intro->intro_key)); smartlist_add(service->intro_nodes, intro); log_info(LD_REND, "Picked router %s as an intro point for %s.", - router->nickname, service->service_id); + node_get_nickname(node), service->service_id); } /* If there's no need to launch new circuits, stop here. */ @@ -1873,7 +1874,7 @@ rend_services_introduce(void) } } } - smartlist_free(intro_routers); + smartlist_free(intro_nodes); } /** Regenerate and upload rendezvous service descriptors for all diff --git a/src/or/rephist.c b/src/or/rephist.c index d85a8b7c3a..3146d70940 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -7,13 +7,14 @@ * \brief Basic history and "reputation" functionality to remember * which servers have worked in the past, how much bandwidth we've * been using, which ports we tend to want, and so on; further, - * exit port statistics and cell statistics. + * exit port statistics, cell statistics, and connection statistics. **/ #include "or.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "nodelist.h" #include "rephist.h" #include "router.h" #include "routerlist.h" @@ -579,7 +580,7 @@ rep_hist_dump_stats(time_t now, int severity) size_t len; int ret; unsigned long upt, downt; - routerinfo_t *r; + const node_t *node; rep_history_clean(now - get_options()->RephistTrackTime); @@ -593,8 +594,8 @@ rep_hist_dump_stats(time_t now, int severity) digestmap_iter_get(orhist_it, &digest1, &or_history_p); or_history = (or_history_t*) or_history_p; - if ((r = router_get_by_digest(digest1))) - name1 = r->nickname; + if ((node = node_get_by_id(digest1)) && node_get_nickname(node)) + name1 = node_get_nickname(node); else name1 = "(unknown)"; base16_encode(hexdigest1, sizeof(hexdigest1), digest1, DIGEST_LEN); @@ -624,8 +625,8 @@ rep_hist_dump_stats(time_t now, int severity) lhist_it = digestmap_iter_next(or_history->link_history_map, lhist_it)) { digestmap_iter_get(lhist_it, &digest2, &link_history_p); - if ((r = router_get_by_digest(digest2))) - name2 = r->nickname; + if ((node = node_get_by_id(digest2)) && node_get_nickname(node)) + name2 = node_get_nickname(node); else name2 = "(unknown)"; @@ -756,7 +757,7 @@ rep_hist_record_mtbf_data(time_t now, int missing_means_down) base16_encode(dbuf, sizeof(dbuf), digest, DIGEST_LEN); if (missing_means_down && hist->start_of_run && - !router_get_by_digest(digest)) { + !router_get_by_id_digest(digest)) { /* We think this relay is running, but it's not listed in our * routerlist. Somehow it fell out without telling us it went * down. Complain and also correct it. */ @@ -871,28 +872,32 @@ rep_hist_get_router_stability_doc(time_t now) } DIGESTMAP_FOREACH(history_map, id, or_history_t *, hist) { - routerinfo_t *ri; + const node_t *node; char dbuf[BASE64_DIGEST_LEN+1]; char header_buf[512]; char *info; digest_to_base64(dbuf, id); - ri = router_get_by_digest(id); - if (ri) { - char *ip = tor_dup_ip(ri->addr); + node = node_get_by_id(id); + if (node) { + char ip[INET_NTOA_BUF_LEN+1]; char tbuf[ISO_TIME_LEN+1]; - format_iso_time(tbuf, ri->cache_info.published_on); + time_t published = node_get_published_on(node); + node_get_address_string(node,ip,sizeof(ip)); + if (published > 0) + format_iso_time(tbuf, published); + else + strlcpy(tbuf, "???", sizeof(tbuf)); tor_snprintf(header_buf, sizeof(header_buf), "router %s %s %s\n" "published %s\n" "relevant-flags %s%s%s\n" "declared-uptime %ld\n", - dbuf, ri->nickname, ip, + dbuf, node_get_nickname(node), ip, tbuf, - ri->is_running ? "Running " : "", - ri->is_valid ? "Valid " : "", - ri->is_hibernating ? "Hibernating " : "", - ri->uptime); - tor_free(ip); + node->is_running ? "Running " : "", + node->is_valid ? "Valid " : "", + node->ri && node->ri->is_hibernating ? "Hibernating " : "", + node_get_declared_uptime(node)); } else { tor_snprintf(header_buf, sizeof(header_buf), "router %s {no descriptor}\n", dbuf); @@ -1648,10 +1653,13 @@ rep_hist_load_state(or_state_t *state, char **err) /*********************************************************************/ +typedef struct predicted_port_t { + uint16_t port; + time_t time; +} predicted_port_t; + /** A list of port numbers that have been used recently. */ static smartlist_t *predicted_ports_list=NULL; -/** The corresponding most recently used time for each port. */ -static smartlist_t *predicted_ports_times=NULL; /** We just got an application request for a connection with * port <b>port</b>. Remember it for the future, so we can keep @@ -1660,14 +1668,11 @@ static smartlist_t *predicted_ports_times=NULL; static void add_predicted_port(time_t now, uint16_t port) { - /* XXXX we could just use uintptr_t here, I think. */ - uint16_t *tmp_port = tor_malloc(sizeof(uint16_t)); - time_t *tmp_time = tor_malloc(sizeof(time_t)); - *tmp_port = port; - *tmp_time = now; - rephist_total_alloc += sizeof(uint16_t) + sizeof(time_t); - smartlist_add(predicted_ports_list, tmp_port); - smartlist_add(predicted_ports_times, tmp_time); + predicted_port_t *pp = tor_malloc(sizeof(predicted_port_t)); + pp->port = port; + pp->time = now; + rephist_total_alloc += sizeof(*pp); + smartlist_add(predicted_ports_list, pp); } /** Initialize whatever memory and structs are needed for predicting @@ -1678,7 +1683,6 @@ static void predicted_ports_init(void) { predicted_ports_list = smartlist_create(); - predicted_ports_times = smartlist_create(); add_predicted_port(time(NULL), 80); /* add one to kickstart us */ } @@ -1688,12 +1692,11 @@ predicted_ports_init(void) static void predicted_ports_free(void) { - rephist_total_alloc -= smartlist_len(predicted_ports_list)*sizeof(uint16_t); - SMARTLIST_FOREACH(predicted_ports_list, char *, cp, tor_free(cp)); + rephist_total_alloc -= + smartlist_len(predicted_ports_list)*sizeof(predicted_port_t); + SMARTLIST_FOREACH(predicted_ports_list, predicted_port_t *, + pp, tor_free(pp)); smartlist_free(predicted_ports_list); - rephist_total_alloc -= smartlist_len(predicted_ports_times)*sizeof(time_t); - SMARTLIST_FOREACH(predicted_ports_times, char *, cp, tor_free(cp)); - smartlist_free(predicted_ports_times); } /** Remember that <b>port</b> has been asked for as of time <b>now</b>. @@ -1703,24 +1706,17 @@ predicted_ports_free(void) void rep_hist_note_used_port(time_t now, uint16_t port) { - int i; - uint16_t *tmp_port; - time_t *tmp_time; - tor_assert(predicted_ports_list); - tor_assert(predicted_ports_times); if (!port) /* record nothing */ return; - for (i = 0; i < smartlist_len(predicted_ports_list); ++i) { - tmp_port = smartlist_get(predicted_ports_list, i); - tmp_time = smartlist_get(predicted_ports_times, i); - if (*tmp_port == port) { - *tmp_time = now; + SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) { + if (pp->port == port) { + pp->time = now; return; } - } + } SMARTLIST_FOREACH_END(pp); /* it's not there yet; we need to add it */ add_predicted_port(now, port); } @@ -1729,36 +1725,28 @@ rep_hist_note_used_port(time_t now, uint16_t port) * we'll want to make connections to the same port in the future. */ #define PREDICTED_CIRCS_RELEVANCE_TIME (60*60) -/** Return a pointer to the list of port numbers that +/** Return a newly allocated pointer to a list of uint16_t * for ports that * are likely to be asked for in the near future. - * - * The caller promises not to mess with it. */ smartlist_t * rep_hist_get_predicted_ports(time_t now) { - int i; - uint16_t *tmp_port; - time_t *tmp_time; - + smartlist_t *out = smartlist_create(); tor_assert(predicted_ports_list); - tor_assert(predicted_ports_times); /* clean out obsolete entries */ - for (i = 0; i < smartlist_len(predicted_ports_list); ++i) { - tmp_time = smartlist_get(predicted_ports_times, i); - if (*tmp_time + PREDICTED_CIRCS_RELEVANCE_TIME < now) { - tmp_port = smartlist_get(predicted_ports_list, i); - log_debug(LD_CIRC, "Expiring predicted port %d", *tmp_port); - smartlist_del(predicted_ports_list, i); - smartlist_del(predicted_ports_times, i); - rephist_total_alloc -= sizeof(uint16_t)+sizeof(time_t); - tor_free(tmp_port); - tor_free(tmp_time); - i--; + SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) { + if (pp->time + PREDICTED_CIRCS_RELEVANCE_TIME < now) { + log_debug(LD_CIRC, "Expiring predicted port %d", pp->port); + + rephist_total_alloc -= sizeof(predicted_port_t); + tor_free(pp); + SMARTLIST_DEL_CURRENT(predicted_ports_list, pp); + } else { + smartlist_add(out, tor_memdup(&pp->port, sizeof(uint16_t))); } - } - return predicted_ports_list; + } SMARTLIST_FOREACH_END(pp); + return out; } /** The user asked us to do a resolve. Rather than keeping track of @@ -1994,7 +1982,9 @@ rep_hist_exit_stats_term(void) tor_free(exit_streams); } -/** Helper for qsort: compare two ints. */ +/** Helper for qsort: compare two ints. Does not handle overflow properly, + * but works fine for sorting an array of port numbers, which is what we use + * it for. */ static int _compare_int(const void *x, const void *y) { @@ -2242,7 +2232,6 @@ typedef struct circ_buffer_stats_t { uint32_t processed_cells; double mean_num_cells_in_queue; double mean_time_cells_in_queue; - uint32_t local_circ_id; } circ_buffer_stats_t; /** Holds stats. */ @@ -2265,9 +2254,9 @@ rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t end_of_interval) return; if (!circuits_for_buffer_stats) circuits_for_buffer_stats = smartlist_create(); - start_of_interval = circ->timestamp_created > - start_of_buffer_stats_interval ? - circ->timestamp_created : + start_of_interval = (circ->timestamp_created.tv_sec > + start_of_buffer_stats_interval) ? + circ->timestamp_created.tv_sec : start_of_buffer_stats_interval; interval_length = (int) (end_of_interval - start_of_interval); stat = tor_malloc_zero(sizeof(circ_buffer_stats_t)); @@ -2433,6 +2422,227 @@ rep_hist_buffer_stats_write(time_t now) return start_of_buffer_stats_interval + WRITE_STATS_INTERVAL; } +/*** Connection statistics ***/ + +/** Start of the current connection stats interval or 0 if we're not + * collecting connection statistics. */ +static time_t start_of_conn_stats_interval; + +/** Initialize connection stats. */ +void +rep_hist_conn_stats_init(time_t now) +{ + start_of_conn_stats_interval = now; +} + +/* Count connections that we read and wrote less than these many bytes + * from/to as below threshold. */ +#define BIDI_THRESHOLD 20480 + +/* Count connections that we read or wrote at least this factor as many + * bytes from/to than we wrote or read to/from as mostly reading or + * writing. */ +#define BIDI_FACTOR 10 + +/* Interval length in seconds for considering read and written bytes for + * connection stats. */ +#define BIDI_INTERVAL 10 + +/* Start of next BIDI_INTERVAL second interval. */ +static time_t bidi_next_interval = 0; + +/* Number of connections that we read and wrote less than BIDI_THRESHOLD + * bytes from/to in BIDI_INTERVAL seconds. */ +static uint32_t below_threshold = 0; + +/* Number of connections that we read at least BIDI_FACTOR times more + * bytes from than we wrote to in BIDI_INTERVAL seconds. */ +static uint32_t mostly_read = 0; + +/* Number of connections that we wrote at least BIDI_FACTOR times more + * bytes to than we read from in BIDI_INTERVAL seconds. */ +static uint32_t mostly_written = 0; + +/* Number of connections that we read and wrote at least BIDI_THRESHOLD + * bytes from/to, but not BIDI_FACTOR times more in either direction in + * BIDI_INTERVAL seconds. */ +static uint32_t both_read_and_written = 0; + +/* Entry in a map from connection ID to the number of read and written + * bytes on this connection in a BIDI_INTERVAL second interval. */ +typedef struct bidi_map_entry_t { + HT_ENTRY(bidi_map_entry_t) node; + uint64_t conn_id; /**< Connection ID */ + size_t read; /**< Number of read bytes */ + size_t written; /**< Number of written bytes */ +} bidi_map_entry_t; + +/** Map of OR connections together with the number of read and written + * bytes in the current BIDI_INTERVAL second interval. */ +static HT_HEAD(bidimap, bidi_map_entry_t) bidi_map = + HT_INITIALIZER(); + +static int +bidi_map_ent_eq(const bidi_map_entry_t *a, const bidi_map_entry_t *b) +{ + return a->conn_id == b->conn_id; +} + +static unsigned +bidi_map_ent_hash(const bidi_map_entry_t *entry) +{ + return (unsigned) entry->conn_id; +} + +HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, + bidi_map_ent_eq); +HT_GENERATE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, + bidi_map_ent_eq, 0.6, malloc, realloc, free); + +static void +bidi_map_free(void) +{ + bidi_map_entry_t **ptr, **next, *ent; + for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) { + ent = *ptr; + next = HT_NEXT_RMV(bidimap, &bidi_map, ptr); + tor_free(ent); + } + HT_CLEAR(bidimap, &bidi_map); +} + +/** Reset counters for conn statistics. */ +void +rep_hist_reset_conn_stats(time_t now) +{ + start_of_conn_stats_interval = now; + below_threshold = 0; + mostly_read = 0; + mostly_written = 0; + both_read_and_written = 0; + bidi_map_free(); +} + +/** Stop collecting connection stats in a way that we can re-start doing + * so in rep_hist_conn_stats_init(). */ +void +rep_hist_conn_stats_term(void) +{ + rep_hist_reset_conn_stats(0); +} + +/** We read <b>num_read</b> bytes and wrote <b>num_written</b> from/to OR + * connection <b>conn_id</b> in second <b>when</b>. If this is the first + * observation in a new interval, sum up the last observations. Add bytes + * for this connection. */ +void +rep_hist_note_or_conn_bytes(uint64_t conn_id, size_t num_read, + size_t num_written, time_t when) +{ + if (!start_of_conn_stats_interval) + return; + /* Initialize */ + if (bidi_next_interval == 0) + bidi_next_interval = when + BIDI_INTERVAL; + /* Sum up last period's statistics */ + if (when >= bidi_next_interval) { + bidi_map_entry_t **ptr, **next, *ent; + for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) { + ent = *ptr; + if (ent->read + ent->written < BIDI_THRESHOLD) + below_threshold++; + else if (ent->read >= ent->written * BIDI_FACTOR) + mostly_read++; + else if (ent->written >= ent->read * BIDI_FACTOR) + mostly_written++; + else + both_read_and_written++; + next = HT_NEXT_RMV(bidimap, &bidi_map, ptr); + tor_free(ent); + } + while (when >= bidi_next_interval) + bidi_next_interval += BIDI_INTERVAL; + log_info(LD_GENERAL, "%d below threshold, %d mostly read, " + "%d mostly written, %d both read and written.", + below_threshold, mostly_read, mostly_written, + both_read_and_written); + } + /* Add this connection's bytes. */ + if (num_read > 0 || num_written > 0) { + bidi_map_entry_t *entry, lookup; + lookup.conn_id = conn_id; + entry = HT_FIND(bidimap, &bidi_map, &lookup); + if (entry) { + entry->written += num_written; + entry->read += num_read; + } else { + entry = tor_malloc_zero(sizeof(bidi_map_entry_t)); + entry->conn_id = conn_id; + entry->written = num_written; + entry->read = num_read; + HT_INSERT(bidimap, &bidi_map, entry); + } + } +} + +/** Return a newly allocated string containing the connection statistics + * until <b>now</b>, or NULL if we're not collecting conn stats. */ +char * +rep_hist_format_conn_stats(time_t now) +{ + char *result, written[ISO_TIME_LEN+1]; + + if (!start_of_conn_stats_interval) + return NULL; /* Not initialized. */ + + format_iso_time(written, now); + tor_asprintf(&result, "conn-bi-direct %s (%d s) %d,%d,%d,%d\n", + written, + (unsigned) (now - start_of_conn_stats_interval), + below_threshold, + mostly_read, + mostly_written, + both_read_and_written); + return result; +} + +/** If 24 hours have passed since the beginning of the current conn stats + * period, write conn stats to $DATADIR/stats/conn-stats (possibly + * overwriting an existing file) and reset counters. Return when we would + * next want to write conn stats or 0 if we never want to write. */ +time_t +rep_hist_conn_stats_write(time_t now) +{ + char *statsdir = NULL, *filename = NULL, *str = NULL; + + if (!start_of_conn_stats_interval) + return 0; /* Not initialized. */ + if (start_of_conn_stats_interval + WRITE_STATS_INTERVAL > now) + goto done; /* Not ready to write */ + + /* Generate history string. */ + str = rep_hist_format_conn_stats(now); + + /* Reset counters. */ + rep_hist_reset_conn_stats(now); + + /* Try to write to disk. */ + statsdir = get_datadir_fname("stats"); + if (check_private_dir(statsdir, CPD_CREATE) < 0) { + log_warn(LD_HIST, "Unable to create stats/ directory!"); + goto done; + } + 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; +} + /** Free all storage held by the OR/link history caches, by the * bandwidth history arrays, by the port history, or by statistics . */ void @@ -2447,5 +2657,6 @@ rep_hist_free_all(void) tor_free(exit_streams); built_last_stability_doc_at = 0; predicted_ports_free(); + bidi_map_free(); } diff --git a/src/or/rephist.h b/src/or/rephist.h index 9a39070817..cb0ebf0849 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -76,5 +76,13 @@ void rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t rep_hist_buffer_stats_write(time_t now); void rep_hist_buffer_stats_term(void); +void rep_hist_conn_stats_init(time_t now); +void rep_hist_note_or_conn_bytes(uint64_t conn_id, size_t num_read, + size_t num_written, time_t when); +void rep_hist_reset_conn_stats(time_t now); +char *rep_hist_format_conn_stats(time_t now); +time_t rep_hist_conn_stats_write(time_t now); +void rep_hist_conn_stats_term(void); + #endif diff --git a/src/or/router.c b/src/or/router.c index 3bb37de8cf..1748652ff9 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -7,6 +7,7 @@ #define ROUTER_PRIVATE #include "or.h" +#include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" @@ -19,6 +20,7 @@ #include "hibernate.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "relay.h" #include "rephist.h" @@ -150,8 +152,8 @@ assert_identity_keys_ok(void) } else { /* assert that we have set the client and server keys to be unequal */ if (server_identitykey) - tor_assert(0!=crypto_pk_cmp_keys(client_identitykey, - server_identitykey)); + tor_assert(0!=crypto_pk_cmp_keys(client_identitykey, + server_identitykey)); } } @@ -847,18 +849,21 @@ decide_to_advertise_dirport(or_options_t *options, uint16_t dir_port) void consider_testing_reachability(int test_or, int test_dir) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); int orport_reachable = check_whether_orport_reachable(); tor_addr_t addr; if (!me) return; if (test_or && (!orport_reachable || !circuit_enough_testing_circs())) { + extend_info_t *ei; log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.", !orport_reachable ? "reachability" : "bandwidth", me->address, me->or_port); - circuit_launch_by_router(CIRCUIT_PURPOSE_TESTING, me, - CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); + ei = extend_info_from_router(me); + circuit_launch_by_extend_info(CIRCUIT_PURPOSE_TESTING, ei, + CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); + extend_info_free(ei); } tor_addr_from_ipv4h(&addr, me->addr); @@ -882,7 +887,7 @@ void router_orport_found_reachable(void) { if (!can_reach_or_port) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); log_notice(LD_OR,"Self-testing indicates your ORPort is reachable from " "the outside. Excellent.%s", get_options()->_PublishServerDescriptor != NO_AUTHORITY ? @@ -905,7 +910,7 @@ void router_dirport_found_reachable(void) { if (!can_reach_dir_port) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); log_notice(LD_DIRSERV,"Self-testing indicates your DirPort is reachable " "from the outside. Excellent."); can_reach_dir_port = 1; @@ -1176,7 +1181,7 @@ static int desc_needs_upload = 0; void router_upload_dir_desc_to_dirservers(int force) { - routerinfo_t *ri; + const routerinfo_t *ri; extrainfo_t *ei; char *msg; size_t desc_len, extra_len = 0, total_len; @@ -1270,7 +1275,7 @@ router_extrainfo_digest_is_me(const char *digest) /** A wrapper around router_digest_is_me(). */ int -router_is_me(routerinfo_t *router) +router_is_me(const routerinfo_t *router) { return router_digest_is_me(router->cache_info.identity_digest); } @@ -1289,7 +1294,7 @@ router_fingerprint_is_me(const char *fp) /** Return a routerinfo for this OR, rebuilding a fresh one if * necessary. Return NULL on error, or if called on an OP. */ -routerinfo_t * +const routerinfo_t * router_get_my_routerinfo(void) { if (!server_mode(get_options())) @@ -1341,8 +1346,6 @@ static int router_guess_address_from_dir_headers(uint32_t *guess); int router_pick_published_address(or_options_t *options, uint32_t *addr) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; if (resolve_my_address(LOG_INFO, options, addr, NULL) < 0) { log_info(LD_CONFIG, "Could not determine our address locally. " "Checking if directory headers provide any hints."); @@ -1352,9 +1355,7 @@ router_pick_published_address(or_options_t *options, uint32_t *addr) return -1; } } - a.s_addr = htonl(*addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_CONFIG,"Success: chose address '%s'.", buf); + log_info(LD_CONFIG,"Success: chose address '%s'.", fmt_addr32(*addr)); return 0; } @@ -1416,13 +1417,12 @@ router_rebuild_descriptor(int force) ri->policy_is_reject_star = policy_is_reject_star(ri->exit_policy); - if (desc_routerinfo) { /* inherit values */ - ri->is_valid = desc_routerinfo->is_valid; - ri->is_running = desc_routerinfo->is_running; - ri->is_named = desc_routerinfo->is_named; - } +#if 0 + /* XXXX NM NM I belive this is safe to remove */ if (authdir_mode(options)) ri->is_valid = ri->is_named = 1; /* believe in yourself */ +#endif + if (options->MyFamily) { smartlist_t *family; if (!warned_nonexistent_family) @@ -1431,13 +1431,12 @@ router_rebuild_descriptor(int force) ri->declared_family = smartlist_create(); smartlist_split_string(family, options->MyFamily, ",", SPLIT_SKIP_SPACE|SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH(family, char *, name, - { - routerinfo_t *member; + SMARTLIST_FOREACH_BEGIN(family, char *, name) { + const node_t *member; if (!strcasecmp(name, options->Nickname)) - member = ri; + goto skip; /* Don't list ourself, that's redundant */ else - member = router_get_by_nickname(name, 1); + member = node_get_by_nickname(name, 1); if (!member) { int is_legal = is_legal_nickname_or_hexdigest(name); if (!smartlist_string_isin(warned_nonexistent_family, name) && @@ -1457,19 +1456,21 @@ router_rebuild_descriptor(int force) smartlist_add(ri->declared_family, name); name = NULL; } - } else if (router_is_me(member)) { + } else if (router_digest_is_me(member->identity)) { /* Don't list ourself in our own family; that's redundant */ + /* XXX shouldn't be possible */ } else { char *fp = tor_malloc(HEX_DIGEST_LEN+2); fp[0] = '$'; base16_encode(fp+1,HEX_DIGEST_LEN+1, - member->cache_info.identity_digest, DIGEST_LEN); + member->identity, DIGEST_LEN); smartlist_add(ri->declared_family, fp); if (smartlist_string_isin(warned_nonexistent_family, name)) smartlist_string_remove(warned_nonexistent_family, name); } + skip: tor_free(name); - }); + } SMARTLIST_FOREACH_END(name); /* remove duplicates from the list */ smartlist_sort_strings(ri->declared_family); @@ -1530,8 +1531,6 @@ router_rebuild_descriptor(int force) strlen(ri->cache_info.signed_descriptor_body), ri->cache_info.signed_descriptor_digest); - routerinfo_set_country(ri); - if (ei) { tor_assert(! routerinfo_incompatible_with_extrainfo(ri, ei, NULL, NULL)); } @@ -2024,6 +2023,12 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, tor_free(bandwidth_usage); smartlist_add(chunks, pre); + if (geoip_is_loaded()) { + char *chunk=NULL; + tor_asprintf(&chunk, "geoip-db-digest %s\n", geoip_db_digest()); + smartlist_add(chunks, chunk); + } + if (options->ExtraInfoStatistics && write_stats_to_extrainfo) { log_info(LD_GENERAL, "Adding stats to extra-info descriptor."); if (options->DirReqStatistics && @@ -2046,6 +2051,11 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, "exit-stats-end", now, &contents) > 0) { smartlist_add(chunks, contents); } + if (options->ConnDirectionStatistics && + load_stats_file("stats"PATH_SEPARATOR"conn-stats", + "conn-bi-direct", now, &contents) > 0) { + smartlist_add(chunks, contents); + } } if (should_record_bridge_info(options) && write_stats_to_extrainfo) { @@ -2184,10 +2194,15 @@ is_legal_hexdigest(const char *s) void router_get_verbose_nickname(char *buf, const routerinfo_t *router) { + const char *good_digest = networkstatus_get_router_digest_by_nickname( + router->nickname); + int is_named = good_digest && !memcmp(good_digest, + router->cache_info.identity_digest, + DIGEST_LEN); buf[0] = '$'; base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, DIGEST_LEN); - buf[1+HEX_DIGEST_LEN] = router->is_named ? '=' : '~'; + buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); } diff --git a/src/or/router.h b/src/or/router.h index 5e021f6fed..e58b1ed539 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -66,12 +66,12 @@ void router_new_address_suggestion(const char *suggestion, const dir_connection_t *d_conn); int router_compare_to_my_exit_policy(edge_connection_t *conn); int router_my_exit_policy_is_reject_star(void); -routerinfo_t *router_get_my_routerinfo(void); +const routerinfo_t *router_get_my_routerinfo(void); extrainfo_t *router_get_my_extrainfo(void); const char *router_get_my_descriptor(void); int router_digest_is_me(const char *digest); int router_extrainfo_digest_is_me(const char *digest); -int router_is_me(routerinfo_t *router); +int router_is_me(const routerinfo_t *router); int router_fingerprint_is_me(const char *fp); int router_pick_published_address(or_options_t *options, uint32_t *addr); int router_rebuild_descriptor(int force); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 253b787217..539e540585 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -22,7 +22,9 @@ #include "geoip.h" #include "hibernate.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "rendcommon.h" @@ -37,20 +39,21 @@ /****************************************************************************/ /* static function prototypes */ -static routerstatus_t *router_pick_directory_server_impl( +static const routerstatus_t *router_pick_directory_server_impl( authority_type_t auth, int flags); -static routerstatus_t *router_pick_trusteddirserver_impl( +static const routerstatus_t *router_pick_trusteddirserver_impl( authority_type_t auth, int flags, int *n_busy_out); static void mark_all_trusteddirservers_up(void); -static int router_nickname_matches(routerinfo_t *router, const char *nickname); +static int router_nickname_matches(const routerinfo_t *router, + const char *nickname); +static int node_nickname_matches(const node_t *router, + const char *nickname); static void trusted_dir_server_free(trusted_dir_server_t *ds); -static void launch_router_descriptor_downloads(smartlist_t *downloadable, - routerstatus_t *source, - time_t now); static int signed_desc_digest_is_recognized(signed_descriptor_t *desc); static void update_router_have_minimum_dir_info(void); -static const char *signed_descriptor_get_body_impl(signed_descriptor_t *desc, - int with_annotations); +static const char *signed_descriptor_get_body_impl( + const signed_descriptor_t *desc, + int with_annotations); static void list_pending_downloads(digestmap_t *result, int purpose, const char *prefix); @@ -312,6 +315,7 @@ trusted_dirs_remove_old_certs(void) time_t now = time(NULL); #define DEAD_CERT_LIFETIME (2*24*60*60) #define OLD_CERT_LIFETIME (7*24*60*60) +#define CERT_EXPIRY_SKEW (60*60) if (!trusted_dir_certs) return; @@ -328,7 +332,7 @@ trusted_dirs_remove_old_certs(void) time_t cert_published; if (newest == cert) continue; - expired = ftime_definitely_after(now, cert->expires); + expired = time_definitely_after(now, cert->expires, CERT_EXPIRY_SKEW); cert_published = cert->cache_info.published_on; /* Store expired certs for 48 hours after a newer arrives; */ @@ -520,7 +524,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) continue; cl = get_cert_list(ds->v3_identity_digest); SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, { - if (!ftime_definitely_after(now, cert->expires)) { + if (! time_definitely_after(now, cert->expires, CERT_EXPIRY_SKEW)) { /* It's not expired, and we weren't looking for something to * verify a consensus with. Call it done. */ download_status_reset(&cl->dl_status); @@ -600,7 +604,7 @@ router_should_rebuild_store(desc_store_t *store) /** Return the desc_store_t in <b>rl</b> that should be used to store * <b>sd</b>. */ static INLINE desc_store_t * -desc_get_store(routerlist_t *rl, signed_descriptor_t *sd) +desc_get_store(routerlist_t *rl, const signed_descriptor_t *sd) { if (sd->is_extrainfo) return &rl->extrainfo_store; @@ -926,10 +930,10 @@ router_get_trusted_dir_servers(void) * Don't pick an authority if any non-authority is viable; try to avoid using * servers that have returned 503 recently. */ -routerstatus_t * +const routerstatus_t * router_pick_directory_server(authority_type_t type, int flags) { - routerstatus_t *choice; + const routerstatus_t *choice; if (get_options()->PreferTunneledDirConns) flags |= _PDS_PREFER_TUNNELED_DIR_CONNS; @@ -958,8 +962,8 @@ int router_get_my_share_of_directory_requests(double *v2_share_out, double *v3_share_out) { - routerinfo_t *me = router_get_my_routerinfo(); - routerstatus_t *rs; + const routerinfo_t *me = router_get_my_routerinfo(); + const routerstatus_t *rs; const int pds_flags = PDS_ALLOW_SELF|PDS_IGNORE_FASCISTFIREWALL; *v2_share_out = *v3_share_out = 0.0; if (!me) @@ -1032,10 +1036,10 @@ trusteddirserver_get_by_v3_auth_digest(const char *digest) /** Try to find a running trusted dirserver. Flags are as for * router_pick_directory_server. */ -routerstatus_t * +const routerstatus_t * router_pick_trusteddirserver(authority_type_t type, int flags) { - routerstatus_t *choice; + const routerstatus_t *choice; int busy = 0; if (get_options()->PreferTunneledDirConns) flags |= _PDS_PREFER_TUNNELED_DIR_CONNS; @@ -1047,7 +1051,8 @@ router_pick_trusteddirserver(authority_type_t type, int flags) /* If the reason that we got no server is that servers are "busy", * we must be excluding good servers because we already have serverdesc * fetches with them. Do not mark down servers up because of this. */ - tor_assert((flags & PDS_NO_EXISTING_SERVERDESC_FETCH)); + tor_assert((flags & (PDS_NO_EXISTING_SERVERDESC_FETCH| + PDS_NO_EXISTING_MICRODESC_FETCH))); return NULL; } @@ -1067,10 +1072,10 @@ router_pick_trusteddirserver(authority_type_t type, int flags) * If the _PDS_PREFER_TUNNELED_DIR_CONNS flag is set, prefer directory servers * that we can use with BEGINDIR. */ -static routerstatus_t * +static const routerstatus_t * router_pick_directory_server_impl(authority_type_t type, int flags) { - routerstatus_t *result; + const node_t *result; smartlist_t *direct, *tunnel; smartlist_t *trusted_direct, *trusted_tunnel; smartlist_t *overloaded_direct, *overloaded_tunnel; @@ -1091,49 +1096,54 @@ router_pick_directory_server_impl(authority_type_t type, int flags) overloaded_tunnel = smartlist_create(); /* Find all the running dirservers we know about. */ - SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, routerstatus_t *, - status) { + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { int is_trusted; - int is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now; + int is_overloaded; tor_addr_t addr; - if (!status->is_running || !status->dir_port || !status->is_valid) + const routerstatus_t *status = node->rs; + if (!status) continue; - if (status->is_bad_directory) + + if (!node->is_running || !status->dir_port || !node->is_valid) + continue; + if (node->is_bad_directory) continue; - if (requireother && router_digest_is_me(status->identity_digest)) + if (requireother && router_digest_is_me(node->identity)) continue; if (type & V3_AUTHORITY) { if (!(status->version_supports_v3_dir || - router_digest_is_trusted_dir_type(status->identity_digest, + router_digest_is_trusted_dir_type(node->identity, V3_AUTHORITY))) continue; } - is_trusted = router_digest_is_trusted_dir(status->identity_digest); - if ((type & V2_AUTHORITY) && !(status->is_v2_dir || is_trusted)) + is_trusted = router_digest_is_trusted_dir(node->identity); + if ((type & V2_AUTHORITY) && !(node->rs->is_v2_dir || is_trusted)) continue; if ((type & EXTRAINFO_CACHE) && - !router_supports_extrainfo(status->identity_digest, 0)) + !router_supports_extrainfo(node->identity, 0)) continue; /* XXXX IP6 proposal 118 */ - tor_addr_from_ipv4h(&addr, status->addr); + tor_addr_from_ipv4h(&addr, node->rs->addr); + + is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now; if (prefer_tunnel && status->version_supports_begindir && (!fascistfirewall || fascist_firewall_allows_address_or(&addr, status->or_port))) smartlist_add(is_trusted ? trusted_tunnel : - is_overloaded ? overloaded_tunnel : tunnel, status); + is_overloaded ? overloaded_tunnel : tunnel, (void*)node); else if (!fascistfirewall || fascist_firewall_allows_address_dir(&addr, status->dir_port)) smartlist_add(is_trusted ? trusted_direct : - is_overloaded ? overloaded_direct : direct, status); - } SMARTLIST_FOREACH_END(status); + is_overloaded ? overloaded_direct : direct, (void*)node); + } SMARTLIST_FOREACH_END(node); if (smartlist_len(tunnel)) { - result = routerstatus_sl_choose_by_bandwidth(tunnel, WEIGHT_FOR_DIR); + result = node_sl_choose_by_bandwidth(tunnel, WEIGHT_FOR_DIR); } else if (smartlist_len(overloaded_tunnel)) { - result = routerstatus_sl_choose_by_bandwidth(overloaded_tunnel, + result = node_sl_choose_by_bandwidth(overloaded_tunnel, WEIGHT_FOR_DIR); } else if (smartlist_len(trusted_tunnel)) { /* FFFF We don't distinguish between trusteds and overloaded trusteds @@ -1142,10 +1152,10 @@ router_pick_directory_server_impl(authority_type_t type, int flags) * is a feature, but it could easily be a bug. -RD */ result = smartlist_choose(trusted_tunnel); } else if (smartlist_len(direct)) { - result = routerstatus_sl_choose_by_bandwidth(direct, WEIGHT_FOR_DIR); + result = node_sl_choose_by_bandwidth(direct, WEIGHT_FOR_DIR); } else if (smartlist_len(overloaded_direct)) { - result = routerstatus_sl_choose_by_bandwidth(overloaded_direct, - WEIGHT_FOR_DIR); + result = node_sl_choose_by_bandwidth(overloaded_direct, + WEIGHT_FOR_DIR); } else { result = smartlist_choose(trusted_direct); } @@ -1155,25 +1165,26 @@ router_pick_directory_server_impl(authority_type_t type, int flags) smartlist_free(trusted_tunnel); smartlist_free(overloaded_direct); smartlist_free(overloaded_tunnel); - return result; + return result ? result->rs : NULL; } /** Choose randomly from among the trusted dirservers that are up. Flags * are as for router_pick_directory_server_impl(). */ -static routerstatus_t * +static const routerstatus_t * router_pick_trusteddirserver_impl(authority_type_t type, int flags, int *n_busy_out) { smartlist_t *direct, *tunnel; smartlist_t *overloaded_direct, *overloaded_tunnel; - routerinfo_t *me = router_get_my_routerinfo(); - routerstatus_t *result; + const routerinfo_t *me = router_get_my_routerinfo(); + const routerstatus_t *result; time_t now = time(NULL); const int requireother = ! (flags & PDS_ALLOW_SELF); const int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL); const int prefer_tunnel = (flags & _PDS_PREFER_TUNNELED_DIR_CONNS); const int no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH); + const int no_microdesc_fetching =(flags & PDS_NO_EXISTING_MICRODESC_FETCH); int n_busy = 0; if (!trusted_dir_servers) @@ -1212,6 +1223,13 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, continue; } } + if (no_microdesc_fetching) { + if (connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &addr, d->dir_port, DIR_PURPOSE_FETCH_MICRODESC)) { + ++n_busy; + continue; + } + } if (prefer_tunnel && d->or_port && @@ -1250,22 +1268,18 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, static void mark_all_trusteddirservers_up(void) { - if (routerlist) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - if (router_digest_is_trusted_dir(router->cache_info.identity_digest) && - router->dir_port > 0) { - router->is_running = 1; - }); - } + SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, { + if (router_digest_is_trusted_dir(node->identity)) + node->is_running = 1; + }); if (trusted_dir_servers) { SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, dir, { routerstatus_t *rs; dir->is_running = 1; download_status_reset(&dir->v2_ns_dl_status); - rs = router_get_consensus_status_by_id(dir->digest); - if (rs && !rs->is_running) { - rs->is_running = 1; + rs = router_get_mutable_consensus_status_by_id(dir->digest); + if (rs) { rs->last_dir_503_at = 0; control_event_networkstatus_changed_single(rs); } @@ -1289,25 +1303,14 @@ router_reset_status_download_failures(void) mark_all_trusteddirservers_up(); } -/** Return true iff router1 and router2 have the same /16 network. */ +/** Return true iff router1 and router2 have similar enough network addresses + * that we should treat them as being in the same family */ static INLINE int -routers_in_same_network_family(routerinfo_t *r1, routerinfo_t *r2) -{ - return (r1->addr & 0xffff0000) == (r2->addr & 0xffff0000); -} - -/** Look through the routerlist and identify routers that - * advertise the same /16 network address as <b>router</b>. - * Add each of them to <b>sl</b>. - */ -static void -routerlist_add_network_family(smartlist_t *sl, routerinfo_t *router) +addrs_in_same_network_family(const tor_addr_t *a1, + const tor_addr_t *a2) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, r, - { - if (router != r && routers_in_same_network_family(router, r)) - smartlist_add(sl, r); - }); + /* XXXX MOVE ? */ + return 0 == tor_addr_compare_masked(a1, a2, 16, CMP_SEMANTIC); } /** Add all the family of <b>router</b> to the smartlist <b>sl</b>. @@ -1315,122 +1318,132 @@ routerlist_add_network_family(smartlist_t *sl, routerinfo_t *router) * or pick more than one relay from a family for our entry guard list. */ void -routerlist_add_family(smartlist_t *sl, routerinfo_t *router) +nodelist_add_node_family(smartlist_t *sl, const node_t *node) { - routerinfo_t *r; - config_line_t *cl; + /* XXXX MOVE */ + const smartlist_t *all_nodes = nodelist_get_list(); + const smartlist_t *declared_family; or_options_t *options = get_options(); - /* First, add any routers with similar network addresses. */ - if (options->EnforceDistinctSubnets) - routerlist_add_network_family(sl, router); + tor_assert(node); - if (router->declared_family) { - /* Add every r such that router declares familyness with r, and r + declared_family = node_get_declared_family(node); + + /* First, add any nodes with similar network addresses. */ + if (options->EnforceDistinctSubnets) { + tor_addr_t node_addr; + node_get_addr(node, &node_addr); + + SMARTLIST_FOREACH_BEGIN(all_nodes, const node_t *, node2) { + tor_addr_t a; + node_get_addr(node2, &a); + if (addrs_in_same_network_family(&a, &node_addr)) + smartlist_add(sl, (void*)node2); + } SMARTLIST_FOREACH_END(node2); + } + + /* Now, add all nodes in the declared_family of this node, if they + * also declare this node to be in their family. */ + if (declared_family) { + /* Add every r such that router declares familyness with node, and node * declares familyhood with router. */ - SMARTLIST_FOREACH(router->declared_family, const char *, n, - { - if (!(r = router_get_by_nickname(n, 0))) - continue; - if (!r->declared_family) - continue; - SMARTLIST_FOREACH(r->declared_family, const char *, n2, - { - if (router_nickname_matches(router, n2)) - smartlist_add(sl, r); - }); - }); + SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { + const node_t *node2; + const smartlist_t *family2; + if (!(node2 = node_get_by_nickname(name, 0))) + continue; + if (!(family2 = node_get_declared_family(node2))) + continue; + SMARTLIST_FOREACH_BEGIN(family2, const char *, name2) { + if (node_nickname_matches(node, name2)) { + smartlist_add(sl, (void*)node2); + break; + } + } SMARTLIST_FOREACH_END(name2); + } SMARTLIST_FOREACH_END(name); } /* If the user declared any families locally, honor those too. */ - for (cl = options->NodeFamilies; cl; cl = cl->next) { - if (router_nickname_is_in_list(router, cl->value)) { - add_nickname_list_to_smartlist(sl, cl->value, 0); - } + if (options->NodeFamilySets) { + SMARTLIST_FOREACH(options->NodeFamilySets, const routerset_t *, rs, { + if (routerset_contains_node(rs, node)) { + routerset_get_all_nodes(sl, rs, 0); + } + }); + } +} + +/** Given a <b>router</b>, add every node_t in its family to <b>sl</b>. + * + * Note the type mismatch: This function takes a routerinfo, but adds nodes + * to the smartlist! + */ +static void +routerlist_add_nodes_in_family(smartlist_t *sl, const routerinfo_t *router) +{ + /* XXXX MOVE ? */ + node_t fake_node; + const node_t *node = node_get_by_id(router->cache_info.identity_digest);; + if (node == NULL) { + memset(&fake_node, 0, sizeof(fake_node)); + fake_node.ri = (routerinfo_t *)router; + memcpy(fake_node.identity, router->cache_info.identity_digest, DIGEST_LEN); + node = &fake_node; } + nodelist_add_node_family(sl, node); } -/** Return true iff r is named by some nickname in <b>lst</b>. */ +/** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ static INLINE int -router_in_nickname_smartlist(smartlist_t *lst, routerinfo_t *r) +node_in_nickname_smartlist(const smartlist_t *lst, const node_t *node) { + /* XXXX MOVE */ if (!lst) return 0; - SMARTLIST_FOREACH(lst, const char *, name, - if (router_nickname_matches(r, name)) - return 1;); + SMARTLIST_FOREACH(lst, const char *, name, { + if (node_nickname_matches(node, name)) + return 1; + }); return 0; } /** Return true iff r1 and r2 are in the same family, but not the same * router. */ int -routers_in_same_family(routerinfo_t *r1, routerinfo_t *r2) +nodes_in_same_family(const node_t *node1, const node_t *node2) { + /* XXXX MOVE */ or_options_t *options = get_options(); - config_line_t *cl; - - if (options->EnforceDistinctSubnets && routers_in_same_network_family(r1,r2)) - return 1; - if (router_in_nickname_smartlist(r1->declared_family, r2) && - router_in_nickname_smartlist(r2->declared_family, r1)) - return 1; - - for (cl = options->NodeFamilies; cl; cl = cl->next) { - if (router_nickname_is_in_list(r1, cl->value) && - router_nickname_is_in_list(r2, cl->value)) + /* Are they in the same family because of their addresses? */ + if (options->EnforceDistinctSubnets) { + tor_addr_t a1, a2; + node_get_addr(node1, &a1); + node_get_addr(node2, &a2); + if (addrs_in_same_network_family(&a1, &a2)) return 1; } - return 0; -} -/** Given a (possibly NULL) comma-and-whitespace separated list of nicknames, - * see which nicknames in <b>list</b> name routers in our routerlist, and add - * the routerinfos for those routers to <b>sl</b>. If <b>must_be_running</b>, - * only include routers that we think are running. - * Warn if any non-Named routers are specified by nickname. - */ -void -add_nickname_list_to_smartlist(smartlist_t *sl, const char *list, - int must_be_running) -{ - routerinfo_t *router; - smartlist_t *nickname_list; - int have_dir_info = router_have_minimum_dir_info(); - - if (!list) - return; /* nothing to do */ - tor_assert(sl); - - nickname_list = smartlist_create(); - if (!warned_nicknames) - warned_nicknames = smartlist_create(); + /* Are they in the same family because the agree they are? */ + { + const smartlist_t *f1, *f2; + f1 = node_get_declared_family(node1); + f2 = node_get_declared_family(node2); + if (f1 && f2 && + node_in_nickname_smartlist(f1, node2) && + node_in_nickname_smartlist(f2, node1)) + return 1; + } - smartlist_split_string(nickname_list, list, ",", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + /* Are they in the same option because the user says they are? */ + if (options->NodeFamilySets) { + SMARTLIST_FOREACH(options->NodeFamilySets, const routerset_t *, rs, { + if (routerset_contains_node(rs, node1) && + routerset_contains_node(rs, node2)) + return 1; + }); + } - SMARTLIST_FOREACH(nickname_list, const char *, nick, { - int warned; - if (!is_legal_nickname_or_hexdigest(nick)) { - log_warn(LD_CONFIG, "Nickname '%s' is misformed; skipping", nick); - continue; - } - router = router_get_by_nickname(nick, 1); - warned = smartlist_string_isin(warned_nicknames, nick); - if (router) { - if (!must_be_running || router->is_running) { - smartlist_add(sl,router); - } - } else if (!router_get_consensus_status_by_nickname(nick,1)) { - if (!warned) { - log_fn(have_dir_info ? LOG_WARN : LOG_INFO, LD_CONFIG, - "Nickname list includes '%s' which isn't a known router.",nick); - smartlist_add(warned_nicknames, tor_strdup(nick)); - } - } - }); - SMARTLIST_FOREACH(nickname_list, char *, nick, tor_free(nick)); - smartlist_free(nickname_list); + return 0; } /** Return 1 iff any member of the (possibly NULL) comma-separated list @@ -1438,7 +1451,7 @@ add_nickname_list_to_smartlist(smartlist_t *sl, const char *list, * return 0. */ int -router_nickname_is_in_list(routerinfo_t *router, const char *list) +router_nickname_is_in_list(const routerinfo_t *router, const char *list) { smartlist_t *nickname_list; int v = 0; @@ -1457,34 +1470,32 @@ router_nickname_is_in_list(routerinfo_t *router, const char *list) return v; } -/** Add every suitable router from our routerlist to <b>sl</b>, so that +/** Add every suitable node from our nodelist to <b>sl</b>, so that * we can pick a node for a circuit. */ static void -router_add_running_routers_to_smartlist(smartlist_t *sl, int allow_invalid, - int need_uptime, int need_capacity, - int need_guard) -{ - if (!routerlist) - return; +router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, + int need_uptime, int need_capacity, + int need_guard, int need_desc) +{ /* XXXX MOVE */ + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { + if (!node->is_running || + (!node->is_valid && !allow_invalid)) + continue; + if (need_desc && !(node->ri || (node->rs && node->md))) + continue; + if (node->ri && node->ri->purpose != ROUTER_PURPOSE_GENERAL) + continue; + if (node_is_unreliable(node, need_uptime, need_capacity, need_guard)) + continue; - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { - if (router->is_running && - router->purpose == ROUTER_PURPOSE_GENERAL && - (router->is_valid || allow_invalid) && - !router_is_unreliable(router, need_uptime, - need_capacity, need_guard)) { - /* If it's running, and it's suitable according to the - * other flags we had in mind */ - smartlist_add(sl, router); - } - }); + smartlist_add(sl, (void *)node); + } SMARTLIST_FOREACH_END(node); } /** Look through the routerlist until we find a router that has my key. Return it. */ -routerinfo_t * +const routerinfo_t * routerlist_find_my_routerinfo(void) { if (!routerlist) @@ -1502,9 +1513,9 @@ routerlist_find_my_routerinfo(void) * that allows exit to this address:port, or return NULL if there * isn't a good one. */ -routerinfo_t * +const node_t * router_find_exact_exit_enclave(const char *address, uint16_t port) -{ +{/*XXXX MOVE*/ uint32_t addr; struct in_addr in; tor_addr_t a; @@ -1515,13 +1526,12 @@ router_find_exact_exit_enclave(const char *address, uint16_t port) tor_addr_from_ipv4h(&a, addr); - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { - if (router->addr == addr && - router->is_running && - compare_tor_addr_to_addr_policy(&a, port, router->exit_policy) == + SMARTLIST_FOREACH(nodelist_get_list(), const node_t *, node, { + if (node_get_addr_ipv4h(node) == addr && + node->is_running && + compare_tor_addr_to_node_policy(&a, port, node) == ADDR_POLICY_ACCEPTED) - return router; + return node; }); return NULL; } @@ -1533,14 +1543,14 @@ router_find_exact_exit_enclave(const char *address, uint16_t port) * If <b>need_guard</b>, we require that the router is a possible entry guard. */ int -router_is_unreliable(routerinfo_t *router, int need_uptime, - int need_capacity, int need_guard) +node_is_unreliable(const node_t *node, int need_uptime, + int need_capacity, int need_guard) { - if (need_uptime && !router->is_stable) + if (need_uptime && !node->is_stable) return 1; - if (need_capacity && !router->is_fast) + if (need_capacity && !node->is_fast) return 1; - if (need_guard && !router->is_possible_guard) + if (need_guard && !node->is_possible_guard) return 1; return 0; } @@ -1548,7 +1558,7 @@ router_is_unreliable(routerinfo_t *router, int need_uptime, /** Return the smaller of the router's configured BandwidthRate * and its advertised capacity. */ uint32_t -router_get_advertised_bandwidth(routerinfo_t *router) +router_get_advertised_bandwidth(const routerinfo_t *router) { if (router->bandwidthcapacity < router->bandwidthrate) return router->bandwidthcapacity; @@ -1562,7 +1572,7 @@ router_get_advertised_bandwidth(routerinfo_t *router) /** Return the smaller of the router's configured BandwidthRate * and its advertised capacity, capped by max-believe-bw. */ uint32_t -router_get_advertised_bandwidth_capped(routerinfo_t *router) +router_get_advertised_bandwidth_capped(const routerinfo_t *router) { uint32_t result = router->bandwidthcapacity; if (result > router->bandwidthrate) @@ -1604,13 +1614,10 @@ kb_to_bytes(uint32_t bw) } /** Helper function: - * choose a random element of smartlist <b>sl</b>, weighted by + * choose a random element of smartlist <b>sl</b> of nodes, weighted by * the advertised bandwidth of each element using the consensus * bandwidth weights. * - * If <b>statuses</b> is zero, then <b>sl</b> is a list of - * routerinfo_t's. Otherwise it's a list of routerstatus_t's. - * * If <b>rule</b>==WEIGHT_FOR_EXIT. we're picking an exit node: consider all * nodes' bandwidth equally regardless of their Exit status, since there may * be some in the list because they exit to obscure ports. If @@ -1620,10 +1627,9 @@ kb_to_bytes(uint32_t bw) * guard node: consider all guard's bandwidth equally. Otherwise, weight * guards proportionally less. */ -static void * -smartlist_choose_by_bandwidth_weights(smartlist_t *sl, - bandwidth_weight_rule_t rule, - int statuses) +static const node_t * +smartlist_choose_node_by_bandwidth_weights(smartlist_t *sl, + bandwidth_weight_rule_t rule) { int64_t weight_scale; int64_t rand_bw; @@ -1718,15 +1724,14 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, bandwidths = tor_malloc_zero(sizeof(double)*smartlist_len(sl)); // Cycle through smartlist and total the bandwidth. - for (i = 0; i < (unsigned)smartlist_len(sl); ++i) { + SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { int is_exit = 0, is_guard = 0, is_dir = 0, this_bw = 0, is_me = 0; double weight = 1; - if (statuses) { - routerstatus_t *status = smartlist_get(sl, i); - is_exit = status->is_exit; - is_guard = status->is_possible_guard; - is_dir = (status->dir_port != 0); - if (!status->has_bandwidth) { + is_exit = node->is_exit; + is_guard = node->is_possible_guard; + is_dir = node_is_dir(node); + if (node->rs) { + if (!node->rs->has_bandwidth) { tor_free(bandwidths); /* This should never happen, unless all the authorites downgrade * to 0.2.0 or rogue routerstatuses get inserted into our consensus. */ @@ -1735,26 +1740,17 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, "old router selection algorithm."); return NULL; } - this_bw = kb_to_bytes(status->bandwidth); - if (router_digest_is_me(status->identity_digest)) - is_me = 1; + this_bw = kb_to_bytes(node->rs->bandwidth); + } else if (node->ri) { + /* bridge or other descriptor not in our consensus */ + this_bw = bridge_get_advertised_bandwidth_bounded(node->ri); + have_unknown = 1; } else { - routerstatus_t *rs; - routerinfo_t *router = smartlist_get(sl, i); - rs = router_get_consensus_status_by_id( - router->cache_info.identity_digest); - is_exit = router->is_exit; - is_guard = router->is_possible_guard; - is_dir = (router->dir_port != 0); - if (rs && rs->has_bandwidth) { - this_bw = kb_to_bytes(rs->bandwidth); - } else { /* bridge or other descriptor not in our consensus */ - this_bw = bridge_get_advertised_bandwidth_bounded(router); - have_unknown = 1; - } - if (router_digest_is_me(router->cache_info.identity_digest)) - is_me = 1; + /* We can't use this one. */ + continue; } + is_me = router_digest_is_me(node->identity); + if (is_guard && is_exit) { weight = (is_dir ? Wdb*Wd : Wd); } else if (is_guard) { @@ -1765,11 +1761,11 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, weight = (is_dir ? Wmb*Wm : Wm); } - bandwidths[i] = weight*this_bw; + bandwidths[node_sl_idx] = weight*this_bw; weighted_bw += weight*this_bw; if (is_me) sl_last_weighted_bw_of_me = weight*this_bw; - } + } SMARTLIST_FOREACH_END(node); /* XXXX022 this is a kludge to expose these values. */ sl_last_total_weighted_bw = weighted_bw; @@ -1817,12 +1813,9 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, } /** Helper function: - * choose a random element of smartlist <b>sl</b>, weighted by + * choose a random node_t element of smartlist <b>sl</b>, weighted by * the advertised bandwidth of each element. * - * If <b>statuses</b> is zero, then <b>sl</b> is a list of - * routerinfo_t's. Otherwise it's a list of routerstatus_t's. - * * If <b>rule</b>==WEIGHT_FOR_EXIT. we're picking an exit node: consider all * nodes' bandwidth equally regardless of their Exit status, since there may * be some in the list because they exit to obscure ports. If @@ -1832,13 +1825,11 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, * guard node: consider all guard's bandwidth equally. Otherwise, weight * guards proportionally less. */ -static void * -smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, - int statuses) +static const node_t * +smartlist_choose_node_by_bandwidth(smartlist_t *sl, + bandwidth_weight_rule_t rule) { - unsigned int i; - routerinfo_t *router; - routerstatus_t *status=NULL; + unsigned i; int32_t *bandwidths; int is_exit; int is_guard; @@ -1879,49 +1870,34 @@ smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, guard_bits = bitarray_init_zero(smartlist_len(sl)); /* Iterate over all the routerinfo_t or routerstatus_t, and */ - for (i = 0; i < (unsigned)smartlist_len(sl); ++i) { + SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { /* first, learn what bandwidth we think i has */ int is_known = 1; int32_t flags = 0; uint32_t this_bw = 0; - if (statuses) { - status = smartlist_get(sl, i); - if (router_digest_is_me(status->identity_digest)) - me_idx = i; - router = router_get_by_digest(status->identity_digest); - is_exit = status->is_exit; - is_guard = status->is_possible_guard; - if (status->has_bandwidth) { - this_bw = kb_to_bytes(status->bandwidth); + i = node_sl_idx; + + if (router_digest_is_me(node->identity)) + me_idx = node_sl_idx; + + is_exit = node->is_exit; + is_guard = node->is_possible_guard; + if (node->rs) { + if (node->rs->has_bandwidth) { + this_bw = kb_to_bytes(node->rs->bandwidth); } else { /* guess */ /* XXX022 once consensuses always list bandwidths, we can take * this guessing business out. -RD */ is_known = 0; - flags = status->is_fast ? 1 : 0; + flags = node->rs->is_fast ? 1 : 0; flags |= is_exit ? 2 : 0; flags |= is_guard ? 4 : 0; } - } else { - routerstatus_t *rs; - router = smartlist_get(sl, i); - rs = router_get_consensus_status_by_id( - router->cache_info.identity_digest); - if (router_digest_is_me(router->cache_info.identity_digest)) - me_idx = i; - is_exit = router->is_exit; - is_guard = router->is_possible_guard; - if (rs && rs->has_bandwidth) { - this_bw = kb_to_bytes(rs->bandwidth); - } else if (rs) { /* guess; don't trust the descriptor */ - /* XXX022 once consensuses always list bandwidths, we can take - * this guessing business out. -RD */ - is_known = 0; - flags = router->is_fast ? 1 : 0; - flags |= is_exit ? 2 : 0; - flags |= is_guard ? 4 : 0; - } else /* bridge or other descriptor not in our consensus */ - this_bw = bridge_get_advertised_bandwidth_bounded(router); + } else if (node->ri) { + /* Must be a bridge if we're willing to use it */ + this_bw = bridge_get_advertised_bandwidth_bounded(node->ri); } + if (is_exit) bitarray_set(exit_bits, i); if (is_guard) @@ -1941,9 +1917,9 @@ smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, total_nonexit_bw += this_bw; } else { ++n_unknown; - bandwidths[i] = -flags; + bandwidths[node_sl_idx] = -flags; } - } + } SMARTLIST_FOREACH_END(node); /* Now, fill in the unknown values. */ if (n_unknown) { @@ -2085,40 +2061,23 @@ smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, return smartlist_get(sl, i); } -/** Choose a random element of router list <b>sl</b>, weighted by - * the advertised bandwidth of each router. - */ -routerinfo_t * -routerlist_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule) -{ - routerinfo_t *ret; - if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 0))) { - return ret; - } else { - return smartlist_choose_by_bandwidth(sl, rule, 0); - } -} - /** Choose a random element of status list <b>sl</b>, weighted by - * the advertised bandwidth of each status. - */ -routerstatus_t * -routerstatus_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule) -{ - /* We are choosing neither exit nor guard here. Weight accordingly. */ - routerstatus_t *ret; - if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 1))) { + * the advertised bandwidth of each node */ +const node_t * +node_sl_choose_by_bandwidth(smartlist_t *sl, + bandwidth_weight_rule_t rule) +{ /*XXXX MOVE */ + const node_t *ret; + if ((ret = smartlist_choose_node_by_bandwidth_weights(sl, rule))) { return ret; } else { - return smartlist_choose_by_bandwidth(sl, rule, 1); + return smartlist_choose_node_by_bandwidth(sl, rule); } } -/** Return a random running router from the routerlist. Never - * pick a node whose routerinfo is in - * <b>excludedsmartlist</b>, or whose routerinfo matches <b>excludedset</b>, +/** Return a random running node from the nodelist. Never + * pick a node that is in + * <b>excludedsmartlist</b>, or which matches <b>excludedset</b>, * even if they are the only nodes available. * If <b>CRN_NEED_UPTIME</b> is set in flags and any router has more than * a minimum uptime, return one of those. @@ -2130,21 +2089,26 @@ routerstatus_sl_choose_by_bandwidth(smartlist_t *sl, * If <b>CRN_WEIGHT_AS_EXIT</b> is set in flags, we weight bandwidths as if * picking an exit node, otherwise we weight bandwidths for picking a relay * node (that is, possibly discounting exit nodes). + * If <b>CRN_NEED_DESC</b> is set in flags, we only consider nodes that + * have a routerinfo or microdescriptor -- that is, enough info to be + * used to build a circuit. */ -routerinfo_t * +const node_t * router_choose_random_node(smartlist_t *excludedsmartlist, routerset_t *excludedset, router_crn_flags_t flags) -{ +{ /* XXXX MOVE */ const int need_uptime = (flags & CRN_NEED_UPTIME) != 0; const int need_capacity = (flags & CRN_NEED_CAPACITY) != 0; const int need_guard = (flags & CRN_NEED_GUARD) != 0; const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0; const int weight_for_exit = (flags & CRN_WEIGHT_AS_EXIT) != 0; + const int need_desc = (flags & CRN_NEED_DESC) != 0; smartlist_t *sl=smartlist_create(), - *excludednodes=smartlist_create(); - routerinfo_t *choice = NULL, *r; + *excludednodes=smartlist_create(); + const node_t *choice = NULL; + const routerinfo_t *r; bandwidth_weight_rule_t rule; tor_assert(!(weight_for_exit && need_guard)); @@ -2154,29 +2118,30 @@ router_choose_random_node(smartlist_t *excludedsmartlist, /* Exclude relays that allow single hop exit circuits, if the user * wants to (such relays might be risky) */ if (get_options()->ExcludeSingleHopRelays) { - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, - if (r->allow_single_hop_exits) { - smartlist_add(excludednodes, r); + SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + if (node_allows_single_hop_exits(node)) { + smartlist_add(excludednodes, node); }); } if ((r = routerlist_find_my_routerinfo())) { - smartlist_add(excludednodes, r); - routerlist_add_family(excludednodes, r); + const node_t *me = node_get_by_id(r->cache_info.identity_digest); + if (me) + smartlist_add(excludednodes, (void *)me); + routerlist_add_nodes_in_family(excludednodes, r); } - router_add_running_routers_to_smartlist(sl, allow_invalid, - need_uptime, need_capacity, - need_guard); + router_add_running_nodes_to_smartlist(sl, allow_invalid, + need_uptime, need_capacity, + need_guard, need_desc); smartlist_subtract(sl,excludednodes); if (excludedsmartlist) smartlist_subtract(sl,excludedsmartlist); if (excludedset) - routerset_subtract_routers(sl,excludedset); + routerset_subtract_nodes(sl,excludedset); // Always weight by bandwidth - choice = routerlist_sl_choose_by_bandwidth(sl, rule); + choice = node_sl_choose_by_bandwidth(sl, rule); smartlist_free(sl); if (!choice && (need_uptime || need_capacity || need_guard)) { @@ -2199,35 +2164,88 @@ router_choose_random_node(smartlist_t *excludedsmartlist, return choice; } -/** Helper: Return true iff the <b>identity_digest</b> and <b>nickname</b> - * combination of a router, 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 -hex_digest_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named) +/** Helper: given an extended nickname in <b>hexdigest</b> try to decode it. + * Return 0 on success, -1 on failure. Store the result into the + * DIGEST_LEN-byte buffer at <b>digest_out</b>, the single character at + * <b>nickname_qualifier_char_out</b>, and the MAXNICKNAME_LEN+1-byte buffer + * at <b>nickname_out</b>. + * + * The recognized format is: + * HexName = Dollar? HexDigest NamePart? + * Dollar = '?' + * HexDigest = HexChar*20 + * HexChar = 'a'..'f' | 'A'..'F' | '0'..'9' + * NamePart = QualChar Name + * QualChar = '=' | '~' + * Name = NameChar*(1..MAX_NICKNAME_LEN) + * NameChar = Any ASCII alphanumeric character + */ +int +hex_digest_nickname_decode(const char *hexdigest, + char *digest_out, + char *nickname_qualifier_char_out, + char *nickname_out) { - char digest[DIGEST_LEN]; size_t len; + tor_assert(hexdigest); if (hexdigest[0] == '$') ++hexdigest; len = strlen(hexdigest); - if (len < HEX_DIGEST_LEN) + if (len < HEX_DIGEST_LEN) { + return -1; + } else if (len > HEX_DIGEST_LEN && (hexdigest[HEX_DIGEST_LEN] == '=' || + hexdigest[HEX_DIGEST_LEN] == '~') && + len <= HEX_DIGEST_LEN+1+MAX_NICKNAME_LEN) { + *nickname_qualifier_char_out = hexdigest[HEX_DIGEST_LEN]; + strlcpy(nickname_out, hexdigest+HEX_DIGEST_LEN+1 , MAX_NICKNAME_LEN+1); + } else if (len == HEX_DIGEST_LEN) { + ; + } else { + return -1; + } + + if (base16_decode(digest_out, DIGEST_LEN, hexdigest, HEX_DIGEST_LEN)<0) + return -1; + return 0; +} + +/** Helper: Return true iff the <b>identity_digest</b> and <b>nickname</b> + * combination of a router, 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 int +hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, + const char *nickname, int is_named) +{ + char digest[DIGEST_LEN]; + char nn_char='\0'; + char nn_buf[MAX_NICKNAME_LEN+1]; + + if (hex_digest_nickname_decode(hexdigest, digest, &nn_char, nn_buf) == -1) return 0; - else if (len > HEX_DIGEST_LEN && - (hexdigest[HEX_DIGEST_LEN] == '=' || - hexdigest[HEX_DIGEST_LEN] == '~')) { - if (strcasecmp(hexdigest+HEX_DIGEST_LEN+1, nickname)) + + if (nn_char == '=' || nn_char == '~') { + if (strcasecmp(nn_buf, nickname)) return 0; - if (hexdigest[HEX_DIGEST_LEN] == '=' && !is_named) + if (nn_char == '=' && !is_named) return 0; } - if (base16_decode(digest, DIGEST_LEN, hexdigest, HEX_DIGEST_LEN)<0) - return 0; - return (!memcmp(digest, identity_digest, DIGEST_LEN)); + return !memcmp(digest, identity_digest, DIGEST_LEN); +} + +/* Return true iff <b>router</b> is listed as named in the current + * consensus. */ +static int +router_is_named(const routerinfo_t *router) +{ + const char *digest = + networkstatus_get_router_digest_by_nickname(router->nickname); + + return (digest && + !memcmp(digest, router->cache_info.identity_digest, DIGEST_LEN)); } /** Return true iff the digest of <b>router</b>'s identity key, @@ -2235,10 +2253,12 @@ hex_digest_matches(const char *hexdigest, const char *identity_digest, * 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(routerinfo_t *router, const char *hexdigest) +router_hex_digest_matches(const routerinfo_t *router, const char *hexdigest) { - return hex_digest_matches(hexdigest, router->cache_info.identity_digest, - router->nickname, router->is_named); + return hex_digest_nickname_matches(hexdigest, + router->cache_info.identity_digest, + router->nickname, + router_is_named(router)); } /** Return true if <b>router</b>'s nickname matches <b>nickname</b> @@ -2246,20 +2266,43 @@ router_hex_digest_matches(routerinfo_t *router, const char *hexdigest) * matches a hexadecimal value stored in <b>nickname</b>. Return * false otherwise. */ static int -router_nickname_matches(routerinfo_t *router, const char *nickname) +router_nickname_matches(const routerinfo_t *router, const char *nickname) { if (nickname[0]!='$' && !strcasecmp(router->nickname, nickname)) return 1; return router_hex_digest_matches(router, nickname); } +/** Return true if <b>node</b>'s nickname matches <b>nickname</b> + * (case-insensitive), or if <b>node's</b> identity key digest + * matches a hexadecimal value stored in <b>nickname</b>. Return + * false otherwise. */ +static int +node_nickname_matches(const node_t *node, const char *nickname) +{ + const char *n = node_get_nickname(node); + if (n && nickname[0]!='$' && !strcasecmp(n, nickname)) + return 1; + return hex_digest_nickname_matches(nickname, + node->identity, + n, + node_is_named(node)); +} + /** Return the router in our routerlist whose (case-insensitive) * nickname or (case-sensitive) hexadecimal key digest is * <b>nickname</b>. Return NULL if no such router is known. */ -routerinfo_t * +const routerinfo_t * router_get_by_nickname(const char *nickname, int warn_if_unnamed) { +#if 1 + const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); + if (node) + return node->ri; + else + return NULL; +#else int maybedigest; char digest[DIGEST_LEN]; routerinfo_t *best_match=NULL; @@ -2305,15 +2348,14 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) if (warn_if_unnamed && n_matches > 1) { smartlist_t *fps = smartlist_create(); int any_unwarned = 0; - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { + SMARTLIST_FOREACH_BEGIN(routerlist->routers, routerinfo_t *, router) { routerstatus_t *rs; char *desc; size_t dlen; char fp[HEX_DIGEST_LEN+1]; if (strcasecmp(router->nickname, nickname)) continue; - rs = router_get_consensus_status_by_id( + rs = router_get_mutable_consensus_status_by_id( router->cache_info.identity_digest); if (rs && !rs->name_lookup_warned) { rs->name_lookup_warned = 1; @@ -2326,7 +2368,7 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) tor_snprintf(desc, dlen, "\"$%s\" for the one at %s:%d", fp, router->address, router->or_port); smartlist_add(fps, desc); - }); + } SMARTLIST_FOREACH_END(router); if (any_unwarned) { char *alternatives = smartlist_join_strings(fps, "; ",0,NULL); log_warn(LD_CONFIG, @@ -2339,7 +2381,7 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) SMARTLIST_FOREACH(fps, char *, cp, tor_free(cp)); smartlist_free(fps); } else if (warn_if_unnamed) { - routerstatus_t *rs = router_get_consensus_status_by_id( + routerstatus_t *rs = router_get_mutable_consensus_status_by_id( best_match->cache_info.identity_digest); if (rs && !rs->name_lookup_warned) { char fp[HEX_DIGEST_LEN+1]; @@ -2355,8 +2397,8 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) } return best_match; } - return NULL; +#endif } /** Try to find a routerinfo for <b>digest</b>. If we don't have one, @@ -2365,7 +2407,7 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) int router_digest_version_as_new_as(const char *digest, const char *cutoff) { - routerinfo_t *router = router_get_by_digest(digest); + const routerinfo_t *router = router_get_by_id_digest(digest); if (!router) return 1; return tor_version_as_new_as(router->platform, cutoff); @@ -2419,44 +2461,20 @@ hexdigest_to_digest(const char *hexdigest, char *digest) /** Return the router in our routerlist whose hexadecimal key digest * is <b>hexdigest</b>. Return NULL if no such router is known. */ -routerinfo_t * +const routerinfo_t * router_get_by_hexdigest(const char *hexdigest) { - char digest[DIGEST_LEN]; - size_t len; - routerinfo_t *ri; - - tor_assert(hexdigest); - if (!routerlist) - return NULL; - if (hexdigest[0]=='$') - ++hexdigest; - len = strlen(hexdigest); - if (hexdigest_to_digest(hexdigest, digest) < 0) + if (is_legal_nickname(hexdigest)) return NULL; - ri = router_get_by_digest(digest); - - if (ri && len > HEX_DIGEST_LEN) { - if (hexdigest[HEX_DIGEST_LEN] == '=') { - if (strcasecmp(ri->nickname, hexdigest+HEX_DIGEST_LEN+1) || - !ri->is_named) - return NULL; - } else if (hexdigest[HEX_DIGEST_LEN] == '~') { - if (strcasecmp(ri->nickname, hexdigest+HEX_DIGEST_LEN+1)) - return NULL; - } else { - return NULL; - } - } - - return ri; + /* It's not a legal nickname, so it must be a hexdigest or nothing. */ + return router_get_by_nickname(hexdigest, 1); } -/** Return the router in our routerlist whose 20-byte key digest - * is <b>digest</b>. Return NULL if no such router is known. */ +/** As router_get_by_id_digest,but return a pointer that you're allowed to + * modify */ routerinfo_t * -router_get_by_digest(const char *digest) +router_get_mutable_by_digest(const char *digest) { tor_assert(digest); @@ -2467,6 +2485,14 @@ router_get_by_digest(const char *digest) return rimap_get(routerlist->identity_map, digest); } +/** Return the router in our routerlist whose 20-byte key digest + * is <b>digest</b>. Return NULL if no such router is known. */ +const routerinfo_t * +router_get_by_id_digest(const char *digest) +{ + return router_get_mutable_by_digest(digest); +} + /** Return the router in our routerlist whose 20-byte descriptor * is <b>digest</b>. Return NULL if no such router is known. */ signed_descriptor_t * @@ -2517,7 +2543,7 @@ extrainfo_get_by_descriptor_digest(const char *digest) * The caller must not free the string returned. */ static const char * -signed_descriptor_get_body_impl(signed_descriptor_t *desc, +signed_descriptor_get_body_impl(const signed_descriptor_t *desc, int with_annotations) { const char *r = NULL; @@ -2566,7 +2592,7 @@ signed_descriptor_get_body_impl(signed_descriptor_t *desc, * The caller must not free the string returned. */ const char * -signed_descriptor_get_body(signed_descriptor_t *desc) +signed_descriptor_get_body(const signed_descriptor_t *desc) { return signed_descriptor_get_body_impl(desc, 0); } @@ -2574,7 +2600,7 @@ signed_descriptor_get_body(signed_descriptor_t *desc) /** As signed_descriptor_get_body(), but points to the beginning of the * annotations section rather than the beginning of the descriptor. */ const char * -signed_descriptor_get_annotations(signed_descriptor_t *desc) +signed_descriptor_get_annotations(const signed_descriptor_t *desc) { return signed_descriptor_get_body_impl(desc, 1); } @@ -2627,7 +2653,6 @@ routerinfo_free(routerinfo_t *router) } addr_policy_list_free(router->exit_policy); - /* XXXX Remove if this turns out to affect performance. */ memset(router, 77, sizeof(routerinfo_t)); tor_free(router); @@ -2642,7 +2667,6 @@ extrainfo_free(extrainfo_t *extrainfo) tor_free(extrainfo->cache_info.signed_descriptor_body); tor_free(extrainfo->pending_sig); - /* XXXX remove this if it turns out to slow us down. */ memset(extrainfo, 88, sizeof(extrainfo_t)); /* debug bad memory usage */ tor_free(extrainfo); } @@ -2656,7 +2680,6 @@ signed_descriptor_free(signed_descriptor_t *sd) tor_free(sd->signed_descriptor_body); - /* XXXX remove this once more bugs go away. */ memset(sd, 99, sizeof(signed_descriptor_t)); /* Debug bad mem usage */ tor_free(sd); } @@ -2762,8 +2785,7 @@ routerlist_insert(routerlist_t *rl, routerinfo_t *ri) routerinfo_t *ri_old; signed_descriptor_t *sd_old; { - /* XXXX Remove if this slows us down. */ - routerinfo_t *ri_generated = router_get_my_routerinfo(); + const routerinfo_t *ri_generated = router_get_my_routerinfo(); tor_assert(ri_generated != ri); } tor_assert(ri->cache_info.routerlist_index == -1); @@ -2785,6 +2807,7 @@ routerlist_insert(routerlist_t *rl, routerinfo_t *ri) &ri->cache_info); smartlist_add(rl->routers, ri); ri->cache_info.routerlist_index = smartlist_len(rl->routers) - 1; + nodelist_add_routerinfo(ri); router_dir_info_changed(); #ifdef DEBUG_ROUTERLIST routerlist_assert_ok(rl); @@ -2805,7 +2828,6 @@ extrainfo_insert(routerlist_t *rl, extrainfo_t *ei) extrainfo_t *ei_tmp; { - /* XXXX remove this code if it slows us down. */ extrainfo_t *ei_generated = router_get_my_extrainfo(); tor_assert(ei_generated != ei); } @@ -2851,8 +2873,7 @@ static void routerlist_insert_old(routerlist_t *rl, routerinfo_t *ri) { { - /* XXXX remove this code if it slows us down. */ - routerinfo_t *ri_generated = router_get_my_routerinfo(); + const routerinfo_t *ri_generated = router_get_my_routerinfo(); tor_assert(ri_generated != ri); } tor_assert(ri->cache_info.routerlist_index == -1); @@ -2892,6 +2913,8 @@ routerlist_remove(routerlist_t *rl, routerinfo_t *ri, int make_old, time_t now) tor_assert(0 <= idx && idx < smartlist_len(rl->routers)); tor_assert(smartlist_get(rl->routers, idx) == ri); + nodelist_remove_routerinfo(ri); + /* make sure the rephist module knows that it's not running */ rep_hist_note_router_unreachable(ri->cache_info.identity_digest, now); @@ -3003,8 +3026,7 @@ routerlist_replace(routerlist_t *rl, routerinfo_t *ri_old, routerinfo_t *ri_tmp; extrainfo_t *ei_tmp; { - /* XXXX Remove this if it turns out to slow us down. */ - routerinfo_t *ri_generated = router_get_my_routerinfo(); + const routerinfo_t *ri_generated = router_get_my_routerinfo(); tor_assert(ri_generated != ri_new); } tor_assert(ri_old != ri_new); @@ -3014,6 +3036,9 @@ routerlist_replace(routerlist_t *rl, routerinfo_t *ri_old, tor_assert(0 <= idx && idx < smartlist_len(rl->routers)); tor_assert(smartlist_get(rl->routers, idx) == ri_old); + nodelist_remove_routerinfo(ri_old); + nodelist_add_routerinfo(ri_new); + router_dir_info_changed(); if (idx >= 0) { smartlist_set(rl->routers, idx, ri_new); @@ -3160,28 +3185,27 @@ routerlist_reset_warnings(void) void router_set_status(const char *digest, int up) { - routerinfo_t *router; - routerstatus_t *status; + node_t *node; tor_assert(digest); SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, d, if (!memcmp(d->digest, digest, DIGEST_LEN)) d->is_running = up); - router = router_get_by_digest(digest); - if (router) { - log_debug(LD_DIR,"Marking router '%s/%s' as %s.", - router->nickname, router->address, up ? "up" : "down"); - if (!up && router_is_me(router) && !we_are_hibernating()) + node = node_get_mutable_by_id(digest); + if (node) { +#if 0 + char buf[MAX_VERBOSE_NICKNAME_LEN+1]; + node_get_verbose_nickname(node,buf); + log_debug(LD_DIR,"Marking router %s as %s.", + buf, up ? "up" : "down"); +#endif + if (!up && node_is_me(node) && !we_are_hibernating()) log_warn(LD_NET, "We just marked ourself as down. Are your external " "addresses reachable?"); - router->is_running = up; - } - status = router_get_consensus_status_by_id(digest); - if (status && status->is_running != up) { - status->is_running = up; - control_event_networkstatus_changed_single(status); + node->is_running = up; } + router_dir_info_changed(); } @@ -3224,7 +3248,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, id_digest = router->cache_info.identity_digest; - old_router = router_get_by_digest(id_digest); + old_router = router_get_mutable_by_digest(id_digest); /* Make sure that we haven't already got this exact descriptor. */ if (sdmap_get(routerlist->desc_digest_map, @@ -3255,12 +3279,12 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, if (authdir) { if (authdir_wants_to_reject_router(router, msg, - !from_cache && !from_fetch)) { + !from_cache && !from_fetch, + &authdir_believes_valid)) { tor_assert(*msg); routerinfo_free(router); return ROUTER_AUTHDIR_REJECTS; } - authdir_believes_valid = router->is_valid; } else if (from_fetch) { /* Only check the descriptor digest against the network statuses when * we are receiving in response to a fetch. */ @@ -3287,14 +3311,15 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, { routerstatus_t *rs = - networkstatus_v2_find_entry(ns, id_digest); + networkstatus_v2_find_mutable_entry(ns, id_digest); if (rs && !memcmp(rs->descriptor_digest, router->cache_info.signed_descriptor_digest, DIGEST_LEN)) rs->need_to_mirror = 0; }); if (consensus) { - routerstatus_t *rs = networkstatus_vote_find_entry(consensus, id_digest); + routerstatus_t *rs = networkstatus_vote_find_mutable_entry( + consensus, id_digest); if (rs && !memcmp(rs->descriptor_digest, router->cache_info.signed_descriptor_digest, DIGEST_LEN)) { @@ -3894,7 +3919,7 @@ router_load_extrainfo_from_string(const char *s, const char *eos, static int signed_desc_digest_is_recognized(signed_descriptor_t *desc) { - routerstatus_t *rs; + const routerstatus_t *rs; networkstatus_t *consensus = networkstatus_get_latest_consensus(); int caches = directory_caches_dir_info(get_options()); const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); @@ -3927,33 +3952,34 @@ routerlist_retry_directory_downloads(time_t now) router_reset_descriptor_download_failures(); update_networkstatus_downloads(now); update_router_descriptor_downloads(now); + update_microdesc_downloads(now); } -/** Return 1 if all running sufficiently-stable routers will reject +/** Return 1 if all running sufficiently-stable routers we can use will reject * addr:port, return 0 if any might accept it. */ int -router_exit_policy_all_routers_reject(uint32_t addr, uint16_t port, - int need_uptime) -{ +router_exit_policy_all_nodes_reject(uint32_t addr, uint16_t port, + int need_uptime) +{ /* XXXX MOVE */ addr_policy_result_t r; - if (!routerlist) return 1; - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { - if (router->is_running && - !router_is_unreliable(router, need_uptime, 0, 0)) { - r = compare_addr_to_addr_policy(addr, port, router->exit_policy); + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { + if (node->is_running && + !node_is_unreliable(node, need_uptime, 0, 0)) { + + r = compare_addr_to_node_policy(addr, port, node); + if (r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED) return 0; /* this one could be ok. good enough. */ } - }); + } SMARTLIST_FOREACH_END(node); return 1; /* all will reject. */ } /** Return true iff <b>router</b> does not permit exit streams. */ int -router_exit_policy_rejects_all(routerinfo_t *router) +router_exit_policy_rejects_all(const routerinfo_t *router) { return router->policy_is_reject_star; } @@ -4088,7 +4114,9 @@ any_trusted_dir_is_v1_authority(void) /** For every current directory connection whose purpose is <b>purpose</b>, * and where the resource being downloaded begins with <b>prefix</b>, split * rest of the resource into base16 fingerprints, decode them, and set the - * corresponding elements of <b>result</b> to a nonzero value. */ + * corresponding elements of <b>result</b> to a nonzero value. + * DOCDOC purpose==microdesc + */ static void list_pending_downloads(digestmap_t *result, int purpose, const char *prefix) @@ -4096,20 +4124,23 @@ list_pending_downloads(digestmap_t *result, const size_t p_len = strlen(prefix); smartlist_t *tmp = smartlist_create(); smartlist_t *conns = get_connection_array(); + int flags = DSR_HEX; + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) + flags = DSR_DIGEST256|DSR_BASE64; tor_assert(result); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (conn->type == CONN_TYPE_DIR && conn->purpose == purpose && !conn->marked_for_close) { const char *resource = TO_DIR_CONN(conn)->requested_resource; if (!strcmpstart(resource, prefix)) dir_split_resource_into_fingerprints(resource + p_len, - tmp, NULL, DSR_HEX); + tmp, NULL, flags); } - }); + } SMARTLIST_FOREACH_END(conn); + SMARTLIST_FOREACH(tmp, char *, d, { digestmap_set(result, d, (void*)1); @@ -4129,13 +4160,21 @@ list_pending_descriptor_downloads(digestmap_t *result, int extrainfo) list_pending_downloads(result, purpose, "d/"); } -/** Launch downloads for all the descriptors whose digests are listed - * as digests[i] for lo <= i < hi. (Lo and hi may be out of range.) - * If <b>source</b> is given, download from <b>source</b>; otherwise, - * download from an appropriate random directory server. +/** DOCDOC */ +/*XXXX NM should use digest256, if one comes into being. */ +void +list_pending_microdesc_downloads(digestmap_t *result) +{ + list_pending_downloads(result, DIR_PURPOSE_FETCH_MICRODESC, "d/"); +} + +/** Launch downloads for all the descriptors whose digests or digests256 + * are listed as digests[i] for lo <= i < hi. (Lo and hi may be out of + * range.) If <b>source</b> is given, download from <b>source</b>; + * otherwise, download from an appropriate random directory server. */ static void -initiate_descriptor_downloads(routerstatus_t *source, +initiate_descriptor_downloads(const routerstatus_t *source, int purpose, smartlist_t *digests, int lo, int hi, int pds_flags) @@ -4143,6 +4182,20 @@ initiate_descriptor_downloads(routerstatus_t *source, int i, n = hi-lo; char *resource, *cp; size_t r_len; + + int digest_len = DIGEST_LEN, enc_digest_len = HEX_DIGEST_LEN; + char sep = '+'; + int b64_256 = 0; + + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { + /* Microdescriptors are downloaded by "-"-separated base64-encoded + * 256-bit digests. */ + digest_len = DIGEST256_LEN; + enc_digest_len = BASE64_DIGEST256_LEN; + sep = '-'; + b64_256 = 1; + } + if (n <= 0) return; if (lo < 0) @@ -4150,15 +4203,19 @@ initiate_descriptor_downloads(routerstatus_t *source, if (hi > smartlist_len(digests)) hi = smartlist_len(digests); - r_len = 8 + (HEX_DIGEST_LEN+1)*n; + r_len = 8 + (enc_digest_len+1)*n; cp = resource = tor_malloc(r_len); memcpy(cp, "d/", 2); cp += 2; for (i = lo; i < hi; ++i) { - base16_encode(cp, r_len-(cp-resource), - smartlist_get(digests,i), DIGEST_LEN); - cp += HEX_DIGEST_LEN; - *cp++ = '+'; + if (b64_256) { + digest256_to_base64(cp, smartlist_get(digests, i)); + } else { + base16_encode(cp, r_len-(cp-resource), + smartlist_get(digests,i), digest_len); + } + cp += enc_digest_len; + *cp++ = sep; } memcpy(cp-1, ".z", 3); @@ -4181,7 +4238,7 @@ initiate_descriptor_downloads(routerstatus_t *source, static INLINE int client_would_use_router(routerstatus_t *rs, time_t now, or_options_t *options) { - if (!rs->is_running && !options->FetchUselessDescriptors) { + if (!rs->is_flagged_running && !options->FetchUselessDescriptors) { /* If we had this router descriptor, we wouldn't even bother using it. * But, if we want to have a complete list, fetch it anyway. */ return 0; @@ -4205,6 +4262,7 @@ client_would_use_router(routerstatus_t *rs, time_t now, or_options_t *options) * So use 96 because it's a nice number. */ #define MAX_DL_PER_REQUEST 96 +#define MAX_MICRODESC_DL_PER_REQUEST 92 /** Don't split our requests so finely that we are requesting fewer than * this number per server. */ #define MIN_DL_PER_REQUEST 4 @@ -4219,21 +4277,33 @@ client_would_use_router(routerstatus_t *rs, time_t now, or_options_t *options) * them until they have more, or until this amount of time has passed. */ #define MAX_CLIENT_INTERVAL_WITHOUT_REQUEST (10*60) -/** Given a list of router descriptor digests in <b>downloadable</b>, decide - * whether to delay fetching until we have more. If we don't want to delay, - * launch one or more requests to the appropriate directory authorities. */ -static void -launch_router_descriptor_downloads(smartlist_t *downloadable, - routerstatus_t *source, time_t now) +/** Given a <b>purpose</b> (FETCH_MICRODESC or FETCH_SERVERDESC) and a list of + * router descriptor digests or microdescriptor digest256s in + * <b>downloadable</b>, decide whether to delay fetching until we have more. + * If we don't want to delay, launch one or more requests to the appropriate + * directory authorities. + */ +void +launch_descriptor_downloads(int purpose, + smartlist_t *downloadable, + const routerstatus_t *source, time_t now) { int should_delay = 0, n_downloadable; or_options_t *options = get_options(); + const char *descname; + + tor_assert(purpose == DIR_PURPOSE_FETCH_SERVERDESC || + purpose == DIR_PURPOSE_FETCH_MICRODESC); + + descname = (purpose == DIR_PURPOSE_FETCH_SERVERDESC) ? + "routerdesc" : "microdesc"; n_downloadable = smartlist_len(downloadable); if (!directory_fetches_dir_info_early(options)) { if (n_downloadable >= MAX_DL_TO_DELAY) { log_debug(LD_DIR, - "There are enough downloadable routerdescs to launch requests."); + "There are enough downloadable %ss to launch requests.", + descname); should_delay = 0; } else { should_delay = (last_routerdesc_download_attempted + @@ -4241,13 +4311,15 @@ launch_router_descriptor_downloads(smartlist_t *downloadable, if (!should_delay && n_downloadable) { if (last_routerdesc_download_attempted) { log_info(LD_DIR, - "There are not many downloadable routerdescs, but we've " + "There are not many downloadable %ss, but we've " "been waiting long enough (%d seconds). Downloading.", + descname, (int)(now-last_routerdesc_download_attempted)); } else { log_info(LD_DIR, - "There are not many downloadable routerdescs, but we haven't " - "tried downloading descriptors recently. Downloading."); + "There are not many downloadable %ss, but we haven't " + "tried downloading descriptors recently. Downloading.", + descname); } } } @@ -4274,12 +4346,19 @@ launch_router_descriptor_downloads(smartlist_t *downloadable, * update_router_descriptor_downloads() later on, once the connections * have succeeded or failed. */ - pds_flags |= PDS_NO_EXISTING_SERVERDESC_FETCH; + pds_flags |= (purpose == DIR_PURPOSE_FETCH_MICRODESC) ? + PDS_NO_EXISTING_MICRODESC_FETCH : + PDS_NO_EXISTING_SERVERDESC_FETCH; } n_per_request = CEIL_DIV(n_downloadable, MIN_REQUESTS); - if (n_per_request > MAX_DL_PER_REQUEST) - n_per_request = MAX_DL_PER_REQUEST; + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { + if (n_per_request > MAX_MICRODESC_DL_PER_REQUEST) + n_per_request = MAX_MICRODESC_DL_PER_REQUEST; + } else { + if (n_per_request > MAX_DL_PER_REQUEST) + n_per_request = MAX_DL_PER_REQUEST; + } if (n_per_request < MIN_DL_PER_REQUEST) n_per_request = MIN_DL_PER_REQUEST; @@ -4294,7 +4373,7 @@ launch_router_descriptor_downloads(smartlist_t *downloadable, req_plural, n_downloadable, rtr_plural, n_per_request); smartlist_sort_digests(downloadable); for (i=0; i < n_downloadable; i += n_per_request) { - initiate_descriptor_downloads(source, DIR_PURPOSE_FETCH_SERVERDESC, + initiate_descriptor_downloads(source, purpose, downloadable, i, i+n_per_request, pds_flags); } @@ -4484,15 +4563,14 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, map = digestmap_new(); list_pending_descriptor_downloads(map, 0); - SMARTLIST_FOREACH(consensus->routerstatus_list, void *, rsp, - { + SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, void *, rsp) { routerstatus_t *rs = is_vote ? &(((vote_routerstatus_t *)rsp)->status) : rsp; signed_descriptor_t *sd; if ((sd = router_get_by_descriptor_digest(rs->descriptor_digest))) { - routerinfo_t *ri; + const routerinfo_t *ri; ++n_have; - if (!(ri = router_get_by_digest(rs->identity_digest)) || + if (!(ri = router_get_by_id_digest(rs->identity_digest)) || memcmp(ri->cache_info.signed_descriptor_digest, sd->signed_descriptor_digest, DIGEST_LEN)) { /* We have a descriptor with this digest, but either there is no @@ -4525,7 +4603,8 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, if (is_vote && source) { char time_bufnew[ISO_TIME_LEN+1]; char time_bufold[ISO_TIME_LEN+1]; - routerinfo_t *oldrouter = router_get_by_digest(rs->identity_digest); + const routerinfo_t *oldrouter; + oldrouter = router_get_by_id_digest(rs->identity_digest); format_iso_time(time_bufnew, rs->published_on); if (oldrouter) format_iso_time(time_bufold, oldrouter->cache_info.published_on); @@ -4535,7 +4614,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, source->nickname, oldrouter ? "known" : "unknown"); } smartlist_add(downloadable, rs->descriptor_digest); - }); + } SMARTLIST_FOREACH_END(rsp); if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL) && smartlist_len(no_longer_old)) { @@ -4567,7 +4646,8 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, smartlist_len(downloadable), n_delayed, n_have, n_in_oldrouters, n_would_reject, n_wouldnt_use, n_inprogress); - launch_router_descriptor_downloads(downloadable, source, now); + launch_descriptor_downloads(DIR_PURPOSE_FETCH_SERVERDESC, + downloadable, source, now); digestmap_free(map, NULL); done: @@ -4592,10 +4672,13 @@ update_router_descriptor_downloads(time_t now) if (directory_fetches_dir_info_early(options)) { update_router_descriptor_cache_downloads_v2(now); } + update_consensus_router_descriptor_downloads(now, 0, - networkstatus_get_reasonably_live_consensus(now)); + networkstatus_get_reasonably_live_consensus(now, FLAV_NS)); /* XXXX021 we could be smarter here; see notes on bug 652. */ + /* XXXX NM Microdescs: if we're not fetching microdescriptors, we need + * to make something else invoke this. */ /* If we're a server that doesn't have a configured address, we rely on * directory fetches to learn when our address changes. So if we haven't * tried to get any routerdescs in a long time, try a dummy fetch now. */ @@ -4642,7 +4725,7 @@ update_extrainfo_downloads(time_t now) sd = &((routerinfo_t*)smartlist_get(lst, i))->cache_info; if (sd->is_extrainfo) continue; /* This should never happen. */ - if (old_routers && !router_get_by_digest(sd->identity_digest)) + if (old_routers && !router_get_by_id_digest(sd->identity_digest)) continue; /* Couldn't check the signature if we got it. */ if (sd->extrainfo_is_bogus) continue; @@ -4743,7 +4826,7 @@ count_usable_descriptors(int *num_present, int *num_usable, SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs, { - if (in_set && ! routerset_contains_routerstatus(in_set, rs)) + if (in_set && ! routerset_contains_routerstatus(in_set, rs, -1)) continue; if (client_would_use_router(rs, now, options)) { ++*num_usable; /* the consensus says we want it. */ @@ -4766,7 +4849,7 @@ count_loading_descriptors_progress(void) int num_present = 0, num_usable=0; time_t now = time(NULL); const networkstatus_t *consensus = - networkstatus_get_reasonably_live_consensus(now); + networkstatus_get_reasonably_live_consensus(now, FLAV_NS); double fraction; if (!consensus) @@ -4796,7 +4879,7 @@ update_router_have_minimum_dir_info(void) int res; or_options_t *options = get_options(); const networkstatus_t *consensus = - networkstatus_get_reasonably_live_consensus(now); + networkstatus_get_reasonably_live_consensus(now, FLAV_NS); if (!consensus) { if (!networkstatus_get_latest_consensus()) @@ -4906,7 +4989,7 @@ router_reset_descriptor_download_failures(void) * would not cause a recent (post 0.1.1.6) dirserver to republish. */ int -router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) +router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2) { time_t r1pub, r2pub; long time_difference; @@ -4914,7 +4997,7 @@ router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) /* r1 should be the one that was published first. */ if (r1->cache_info.published_on > r2->cache_info.published_on) { - routerinfo_t *ri_tmp = r2; + const routerinfo_t *ri_tmp = r2; r2 = r1; r1 = ri_tmp; } @@ -4933,7 +5016,6 @@ router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) (r1->contact_info && r2->contact_info && strcasecmp(r1->contact_info, r2->contact_info)) || r1->is_hibernating != r2->is_hibernating || - r1->has_old_dnsworkers != r2->has_old_dnsworkers || cmp_addr_policies(r1->exit_policy, r2->exit_policy)) return 0; if ((r1->declared_family == NULL) != (r2->declared_family == NULL)) @@ -4988,7 +5070,8 @@ router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) * incompatibility (if any). **/ int -routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, +routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri, + extrainfo_t *ei, signed_descriptor_t *sd, const char **msg) { @@ -4996,7 +5079,7 @@ routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, tor_assert(ri); tor_assert(ei); if (!sd) - sd = &ri->cache_info; + sd = (signed_descriptor_t*)&ri->cache_info; if (ei->bad_sig) { if (msg) *msg = "Extrainfo signature was bad, or signed with wrong key."; @@ -5059,7 +5142,7 @@ routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, /** Assert that the internal representation of <b>rl</b> is * self-consistent. */ void -routerlist_assert_ok(routerlist_t *rl) +routerlist_assert_ok(const routerlist_t *rl) { routerinfo_t *r2; signed_descriptor_t *sd2; @@ -5149,7 +5232,7 @@ routerlist_assert_ok(routerlist_t *rl) * If <b>router</b> is NULL, it just frees its internal memory and returns. */ const char * -esc_router_info(routerinfo_t *router) +esc_router_info(const routerinfo_t *router) { static char *info=NULL; char *esc_contact, *esc_platform; @@ -5251,38 +5334,6 @@ routerset_get_countryname(const char *c) return country; } -#if 0 -/** Add the GeoIP database's integer index (+1) of a valid two-character - * country code to the routerset's <b>countries</b> bitarray. Return the - * integer index if the country code is valid, -1 otherwise.*/ -static int -routerset_add_country(const char *c) -{ - char country[3]; - country_t cc; - - /* XXXX: Country codes must be of the form \{[a-z\?]{2}\} but this accepts - \{[.]{2}\}. Do we need to be strict? -RH */ - /* Nope; if the country code is bad, we'll get 0 when we look it up. */ - - if (!geoip_is_loaded()) { - log(LOG_WARN, LD_CONFIG, "GeoIP database not loaded: Cannot add country" - "entry %s, ignoring.", c); - return -1; - } - - memcpy(country, c+1, 2); - country[2] = '\0'; - tor_strlower(country); - - if ((cc=geoip_get_country(country))==-1) { - log(LOG_WARN, LD_CONFIG, "Country code '%s' is not valid, ignoring.", - country); - } - return cc; -} -#endif - /** Update the routerset's <b>countries</b> bitarray_t. Called whenever * the GeoIP database is reloaded. */ @@ -5384,7 +5435,7 @@ refresh_all_country_info(void) if (options->_ExcludeExitNodesUnion) routerset_refresh_countries(options->_ExcludeExitNodesUnion); - routerlist_refresh_countries(); + nodelist_refresh_countries(); } /** Add all members of the set <b>source</b> to <b>target</b>. */ @@ -5434,11 +5485,10 @@ routerset_is_empty(const routerset_t *set) static int routerset_contains(const routerset_t *set, const tor_addr_t *addr, uint16_t orport, - const char *nickname, const char *id_digest, int is_named, + const char *nickname, const char *id_digest, country_t country) { if (!set || !set->list) return 0; - (void) is_named; /* not supported */ if (nickname && strmap_get_lc(set->names, nickname)) return 4; if (id_digest && digestmap_get(set->digests, id_digest)) @@ -5466,13 +5516,14 @@ routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei) ei->port, ei->nickname, ei->identity_digest, - -1, /*is_named*/ -1 /*country*/); } -/** Return true iff <b>ri</b> is in <b>set</b>. */ +/** Return true iff <b>ri</b> is in <b>set</b>. If country is <b>-1</b>, we + * look up the country. */ int -routerset_contains_router(const routerset_t *set, routerinfo_t *ri) +routerset_contains_router(const routerset_t *set, const routerinfo_t *ri, + country_t country) { tor_addr_t addr; tor_addr_from_ipv4h(&addr, ri->addr); @@ -5481,13 +5532,15 @@ routerset_contains_router(const routerset_t *set, routerinfo_t *ri) ri->or_port, ri->nickname, ri->cache_info.identity_digest, - ri->is_named, - ri->country); + country); } -/** Return true iff <b>rs</b> is in <b>set</b>. */ +/** Return true iff <b>rs</b> is in <b>set</b>. If country is <b>-1</b>, we + * look up the country. */ int -routerset_contains_routerstatus(const routerset_t *set, routerstatus_t *rs) +routerset_contains_routerstatus(const routerset_t *set, + const routerstatus_t *rs, + country_t country) { tor_addr_t addr; tor_addr_from_ipv4h(&addr, rs->addr); @@ -5496,46 +5549,55 @@ routerset_contains_routerstatus(const routerset_t *set, routerstatus_t *rs) rs->or_port, rs->nickname, rs->identity_digest, - rs->is_named, - -1); + country); +} + +/** Return true iff <b>node</b> is in <b>set</b>. */ +int +routerset_contains_node(const routerset_t *set, const node_t *node) +{ + if (node->rs) + return routerset_contains_routerstatus(set, node->rs, node->country); + else if (node->ri) + return routerset_contains_router(set, node->ri, node->country); + else + return 0; } -/** Add every known routerinfo_t that is a member of <b>routerset</b> to +/** Add every known node_t that is a member of <b>routerset</b> to * <b>out</b>. If <b>running_only</b>, only add the running ones. */ void -routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, - int running_only) -{ +routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, + int running_only) +{ /* XXXX MOVE */ tor_assert(out); if (!routerset || !routerset->list) return; - if (!warned_nicknames) - warned_nicknames = smartlist_create(); - if (routerset_is_list(routerset)) { + if (routerset_is_list(routerset)) { /* No routers are specified by type; all are given by name or digest. * we can do a lookup in O(len(list)). */ SMARTLIST_FOREACH(routerset->list, const char *, name, { - routerinfo_t *router = router_get_by_nickname(name, 1); - if (router) { - if (!running_only || router->is_running) - smartlist_add(out, router); + const node_t *node = node_get_by_nickname(name, 1); + if (node) { + if (!running_only || node->is_running) + smartlist_add(out, (void*)node); } }); } else { /* We need to iterate over the routerlist to get all the ones of the * right kind. */ - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, router, { - if (running_only && !router->is_running) + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, const node_t *, node, { + if (running_only && !node->is_running) continue; - if (routerset_contains_router(routerset, router)) - smartlist_add(out, router); + if (routerset_contains_node(routerset, node)) + smartlist_add(out, (void*)node); }); } } -/** Add to <b>target</b> every routerinfo_t from <b>source</b> except: +/** Add to <b>target</b> every node_t from <b>source</b> except: * * 1) Don't add it if <b>include</b> is non-empty and the relay isn't in * <b>include</b>; and @@ -5544,39 +5606,39 @@ routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, * 3) If <b>running_only</b>, don't add non-running routers. */ void -routersets_get_disjunction(smartlist_t *target, +routersets_get_node_disjunction(smartlist_t *target, const smartlist_t *source, const routerset_t *include, const routerset_t *exclude, int running_only) { - SMARTLIST_FOREACH(source, routerinfo_t *, router, { + SMARTLIST_FOREACH(source, const node_t *, node, { int include_result; - if (running_only && !router->is_running) + if (running_only && !node->is_running) continue; if (!routerset_is_empty(include)) - include_result = routerset_contains_router(include, router); + include_result = routerset_contains_node(include, node); else include_result = 1; if (include_result) { - int exclude_result = routerset_contains_router(exclude, router); + int exclude_result = routerset_contains_node(exclude, node); if (include_result >= exclude_result) - smartlist_add(target, router); + smartlist_add(target, (void*)node); } }); } -/** Remove every routerinfo_t from <b>lst</b> that is in <b>routerset</b>. */ +/** Remove every node_t from <b>lst</b> that is in <b>routerset</b>. */ void -routerset_subtract_routers(smartlist_t *lst, const routerset_t *routerset) -{ +routerset_subtract_nodes(smartlist_t *lst, const routerset_t *routerset) +{ /*XXXX MOVE ? */ tor_assert(lst); if (!routerset) return; - SMARTLIST_FOREACH(lst, routerinfo_t *, r, { - if (routerset_contains_router(routerset, r)) { + SMARTLIST_FOREACH(lst, const node_t *, node, { + if (routerset_contains_node(routerset, node)) { //log_debug(LD_DIR, "Subtracting %s",r->nickname); - SMARTLIST_DEL_CURRENT(lst, r); + SMARTLIST_DEL_CURRENT(lst, node); } }); } @@ -5637,18 +5699,23 @@ routerset_free(routerset_t *routerset) /** Refresh the country code of <b>ri</b>. This function MUST be called on * each router when the GeoIP database is reloaded, and on all new routers. */ void -routerinfo_set_country(routerinfo_t *ri) +node_set_country(node_t *node) { - ri->country = geoip_get_country_by_ip(ri->addr); + if (node->rs) + node->country = geoip_get_country_by_ip(node->rs->addr); + else if (node->ri) + node->country = geoip_get_country_by_ip(node->ri->addr); + else + node->country = -1; } /** Set the country code of all routers in the routerlist. */ void -routerlist_refresh_countries(void) +nodelist_refresh_countries(void) /* MOVE */ { - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, - routerinfo_set_country(ri)); + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, node_t *, node, + node_set_country(node)); } /** Determine the routers that are responsible for <b>id</b> (binary) and @@ -5697,9 +5764,9 @@ hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, int hid_serv_acting_as_directory(void) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); networkstatus_t *c; - routerstatus_t *rs; + const routerstatus_t *rs; if (!me) return 0; if (!get_options()->HidServDirectoryV2) { @@ -5731,7 +5798,7 @@ hid_serv_acting_as_directory(void) int hid_serv_responsible_for_desc_id(const char *query) { - routerinfo_t *me; + const routerinfo_t *me; routerstatus_t *last_rs; const char *my_id, *last_id; int result; diff --git a/src/or/routerlist.h b/src/or/routerlist.h index ca428114ed..bb7a098fd3 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -27,37 +27,34 @@ int router_reload_router_list(void); int authority_cert_dl_looks_uncertain(const char *id_digest); smartlist_t *router_get_trusted_dir_servers(void); -routerstatus_t *router_pick_directory_server(authority_type_t type, int flags); +const routerstatus_t *router_pick_directory_server(authority_type_t type, + int flags); trusted_dir_server_t *router_get_trusteddirserver_by_digest(const char *d); trusted_dir_server_t *trusteddirserver_get_by_v3_auth_digest(const char *d); -routerstatus_t *router_pick_trusteddirserver(authority_type_t type, int flags); +const routerstatus_t *router_pick_trusteddirserver(authority_type_t type, + int flags); int router_get_my_share_of_directory_requests(double *v2_share_out, double *v3_share_out); void router_reset_status_download_failures(void); -void routerlist_add_family(smartlist_t *sl, routerinfo_t *router); -int routers_in_same_family(routerinfo_t *r1, routerinfo_t *r2); +void routerlist_add_family(smartlist_t *sl, const routerinfo_t *router); int routers_have_same_or_addr(const routerinfo_t *r1, const routerinfo_t *r2); -void add_nickname_list_to_smartlist(smartlist_t *sl, const char *list, - int must_be_running); -int router_nickname_is_in_list(routerinfo_t *router, const char *list); -routerinfo_t *routerlist_find_my_routerinfo(void); -routerinfo_t *router_find_exact_exit_enclave(const char *address, +int router_nickname_is_in_list(const routerinfo_t *router, const char *list); +const routerinfo_t *routerlist_find_my_routerinfo(void); +const node_t *router_find_exact_exit_enclave(const char *address, uint16_t port); -int router_is_unreliable(routerinfo_t *router, int need_uptime, +int node_is_unreliable(const node_t *router, int need_uptime, int need_capacity, int need_guard); -uint32_t router_get_advertised_bandwidth(routerinfo_t *router); -uint32_t router_get_advertised_bandwidth_capped(routerinfo_t *router); +uint32_t router_get_advertised_bandwidth(const routerinfo_t *router); +uint32_t router_get_advertised_bandwidth_capped(const routerinfo_t *router); -routerinfo_t *routerlist_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule); -routerstatus_t *routerstatus_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule); +const node_t *node_sl_choose_by_bandwidth(smartlist_t *sl, + bandwidth_weight_rule_t rule); -routerinfo_t *router_choose_random_node(smartlist_t *excludedsmartlist, +const node_t *router_choose_random_node(smartlist_t *excludedsmartlist, struct routerset_t *excludedset, router_crn_flags_t flags); -routerinfo_t *router_get_by_nickname(const char *nickname, +const routerinfo_t *router_get_by_nickname(const char *nickname, int warn_if_unnamed); int router_digest_version_as_new_as(const char *digest, const char *cutoff); int router_digest_is_trusted_dir_type(const char *digest, @@ -67,13 +64,14 @@ int router_digest_is_trusted_dir_type(const char *digest, int router_addr_is_trusted_dir(uint32_t addr); int hexdigest_to_digest(const char *hexdigest, char *digest); -routerinfo_t *router_get_by_hexdigest(const char *hexdigest); -routerinfo_t *router_get_by_digest(const char *digest); +const routerinfo_t *router_get_by_hexdigest(const char *hexdigest); +const routerinfo_t *router_get_by_id_digest(const char *digest); +routerinfo_t *router_get_mutable_by_digest(const char *digest); signed_descriptor_t *router_get_by_descriptor_digest(const char *digest); signed_descriptor_t *router_get_by_extrainfo_digest(const char *digest); signed_descriptor_t *extrainfo_get_by_descriptor_digest(const char *digest); -const char *signed_descriptor_get_body(signed_descriptor_t *desc); -const char *signed_descriptor_get_annotations(signed_descriptor_t *desc); +const char *signed_descriptor_get_body(const signed_descriptor_t *desc); +const char *signed_descriptor_get_annotations(const signed_descriptor_t *desc); routerlist_t *router_get_routerlist(void); void routerinfo_free(routerinfo_t *router); void extrainfo_free(extrainfo_t *extrainfo); @@ -132,9 +130,10 @@ void router_load_extrainfo_from_string(const char *s, const char *eos, int descriptor_digests); void routerlist_retry_directory_downloads(time_t now); -int router_exit_policy_all_routers_reject(uint32_t addr, uint16_t port, - int need_uptime); -int router_exit_policy_rejects_all(routerinfo_t *router); +int router_exit_policy_all_nodes_reject(uint32_t addr, uint16_t port, + int need_uptime); + +int router_exit_policy_rejects_all(const routerinfo_t *router); trusted_dir_server_t *add_trusted_dir_server(const char *nickname, const char *address, uint16_t dir_port, uint16_t or_port, @@ -152,39 +151,43 @@ void router_dir_info_changed(void); const char *get_dir_info_status_string(void); int count_loading_descriptors_progress(void); void router_reset_descriptor_download_failures(void); -int router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2); -int routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, +int router_differences_are_cosmetic(const routerinfo_t *r1, + const routerinfo_t *r2); +int routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri, + extrainfo_t *ei, signed_descriptor_t *sd, const char **msg); -void routerlist_assert_ok(routerlist_t *rl); -const char *esc_router_info(routerinfo_t *router); +void routerlist_assert_ok(const routerlist_t *rl); +const char *esc_router_info(const routerinfo_t *router); void routers_sort_by_identity(smartlist_t *routers); routerset_t *routerset_new(void); +void routerset_refresh_countries(routerset_t *rs); int routerset_parse(routerset_t *target, const char *s, const char *description); void routerset_union(routerset_t *target, const routerset_t *source); int routerset_is_list(const routerset_t *set); int routerset_needs_geoip(const routerset_t *set); -int routerset_contains_router(const routerset_t *set, routerinfo_t *ri); +int routerset_contains_router(const routerset_t *set, const routerinfo_t *ri, + country_t country); int routerset_contains_routerstatus(const routerset_t *set, - routerstatus_t *rs); + const routerstatus_t *rs, + country_t country); int routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei); -void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, - int running_only); -void routersets_get_disjunction(smartlist_t *target, const smartlist_t *source, +int routerset_contains_node(const routerset_t *set, const node_t *node); +void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, + int running_only); +void routersets_get_node_disjunction(smartlist_t *target, + const smartlist_t *source, const routerset_t *include, const routerset_t *exclude, int running_only); -void routerset_subtract_routers(smartlist_t *out, - const routerset_t *routerset); +void routerset_subtract_nodes(smartlist_t *out, + const routerset_t *routerset); char *routerset_to_string(const routerset_t *routerset); -void routerset_refresh_countries(routerset_t *target); int routerset_equal(const routerset_t *old, const routerset_t *new); void routerset_free(routerset_t *routerset); -void routerinfo_set_country(routerinfo_t *ri); -void routerlist_refresh_countries(void); void refresh_all_country_info(void); int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, @@ -192,5 +195,16 @@ int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, int hid_serv_acting_as_directory(void); int hid_serv_responsible_for_desc_id(const char *id); +void list_pending_microdesc_downloads(digestmap_t *result); +void launch_descriptor_downloads(int purpose, + smartlist_t *downloadable, + const routerstatus_t *source, + time_t now); + +int hex_digest_nickname_decode(const char *hexdigest, + char *digest_out, + char *nickname_qualifier_out, + char *nickname_out); + #endif diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 691b9beabc..1822517620 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -69,7 +69,6 @@ typedef enum { K_V, K_W, K_M, - K_EVENTDNS, K_EXTRA_INFO, K_EXTRA_INFO_DIGEST, K_CACHES_EXTRA_INFO, @@ -286,7 +285,6 @@ static token_rule_t routerdesc_token_table[] = { T01("family", K_FAMILY, ARGS, NO_OBJ ), T01("caches-extra-info", K_CACHES_EXTRA_INFO, NO_ARGS, NO_OBJ ), - T01("eventdns", K_EVENTDNS, ARGS, NO_OBJ ), T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), T1( "bandwidth", K_BANDWIDTH, GE(3), NO_OBJ ), @@ -1352,7 +1350,6 @@ router_parse_entry_from_string(const char *s, const char *end, tor_assert(tok->n_args >= 5); router = tor_malloc_zero(sizeof(routerinfo_t)); - router->country = -1; router->cache_info.routerlist_index = -1; router->cache_info.annotations_len = s-start_of_annotations + prepend_len; router->cache_info.signed_descriptor_len = end-s; @@ -1493,13 +1490,6 @@ router_parse_entry_from_string(const char *s, const char *end, router->contact_info = tor_strdup(tok->args[0]); } - if ((tok = find_opt_by_keyword(tokens, K_EVENTDNS))) { - router->has_old_dnsworkers = tok->n_args && !strcmp(tok->args[0], "0"); - } else if (router->platform) { - if (! tor_version_as_new_as(router->platform, "0.1.2.2-alpha")) - router->has_old_dnsworkers = 1; - } - exit_policy_tokens = find_all_exitpolicy(tokens); if (!smartlist_len(exit_policy_tokens)) { log_warn(LD_DIR, "No exit policy tokens in descriptor."); @@ -1558,8 +1548,6 @@ router_parse_entry_from_string(const char *s, const char *end, "router descriptor") < 0) goto err; - routerinfo_set_country(router); - if (!router->or_port) { log_warn(LD_DIR,"or_port unreadable or 0. Failing."); goto err; @@ -1949,6 +1937,7 @@ routerstatus_parse_entry_from_string(memarea_t *area, if (!consensus_method) flav = FLAV_NS; + tor_assert(flav == FLAV_NS || flav == FLAV_MICRODESC); eos = find_start_of_next_routerstatus(*s); @@ -1961,15 +1950,16 @@ routerstatus_parse_entry_from_string(memarea_t *area, goto err; } tok = find_by_keyword(tokens, K_R); - tor_assert(tok->n_args >= 7); + tor_assert(tok->n_args >= 7); /* guaranteed by GE(7) in K_R setup */ if (flav == FLAV_NS) { if (tok->n_args < 8) { log_warn(LD_DIR, "Too few arguments to r"); goto err; } - } else { - offset = -1; + } else if (flav == FLAV_MICRODESC) { + offset = -1; /* There is no identity digest */ } + if (vote_rs) { rs = &vote_rs->status; } else { @@ -2043,7 +2033,7 @@ routerstatus_parse_entry_from_string(memarea_t *area, else if (!strcmp(tok->args[i], "Fast")) rs->is_fast = 1; else if (!strcmp(tok->args[i], "Running")) - rs->is_running = 1; + rs->is_flagged_running = 1; else if (!strcmp(tok->args[i], "Named")) rs->is_named = 1; else if (!strcmp(tok->args[i], "Valid")) @@ -2145,6 +2135,16 @@ routerstatus_parse_entry_from_string(memarea_t *area, vote_rs->microdesc = line; } } SMARTLIST_FOREACH_END(t); + } else if (flav == FLAV_MICRODESC) { + tok = find_opt_by_keyword(tokens, K_M); + if (tok) { + tor_assert(tok->n_args); + if (digest256_from_base64(rs->descriptor_digest, tok->args[0])) { + log_warn(LD_DIR, "Error decoding microdescriptor digest %s", + escaped(tok->args[0])); + goto err; + } + } } if (!strcasecmp(rs->nickname, UNNAMED_ROUTER_NICKNAME)) @@ -4311,7 +4311,7 @@ microdescs_parse_from_string(const char *s, const char *eos, } if ((tok = find_opt_by_keyword(tokens, K_P))) { - md->exitsummary = tor_strdup(tok->args[0]); + md->exit_policy = parse_short_policy(tok->args[0]); } crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); diff --git a/src/test/Makefile.am b/src/test/Makefile.am index 546fa2f4b7..174c1af6ef 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -1,6 +1,6 @@ TESTS = test -noinst_PROGRAMS = test +noinst_PROGRAMS = test test-child AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ -DLOCALSTATEDIR="\"$(localstatedir)\"" \ @@ -12,19 +12,32 @@ AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ # matters a lot there, and is quite hard to debug if you forget to do it. test_SOURCES = \ - test_data.c \ test.c \ test_addr.c \ + test_containers.c \ test_crypto.c \ + test_data.c \ test_dir.c \ - test_containers.c \ + test_microdesc.c \ test_util.c \ tinytest.c +if USE_BUFFEREVENTS +levent_openssl_lib = -levent_openssl +else +levent_openssl_lib = +endif + test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ test_LDADD = ../or/libtor.a ../common/libor.a ../common/libor-crypto.a \ ../common/libor-event.a \ - @TOR_ZLIB_LIBS@ -lm @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ + @TOR_ZLIB_LIBS@ -lm @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ $(levent_openssl_lib) + +noinst_HEADERS = \ + tinytest.h \ + tinytest_macros.h \ + test.h + -noinst_HEADERS = tinytest.h tinytest_macros.h test.h diff --git a/src/test/test-child.c b/src/test/test-child.c new file mode 100644 index 0000000000..ca52750c2f --- /dev/null +++ b/src/test/test-child.c @@ -0,0 +1,18 @@ +#include <stdio.h> + +/** Trivial test program which prints out its command line arguments so we can + * check if tor_spawn_background() works */ +int +main(int argc, char **argv) +{ + int i; + + fprintf(stdout, "OUT\n"); + fprintf(stderr, "ERR\n"); + for (i = 1; i < argc; i++) + fprintf(stdout, "%s\n", argv[i]); + fprintf(stdout, "DONE\n"); + + return 0; +} + diff --git a/src/test/test.c b/src/test/test.c index f5b6a222a7..02e3970557 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -113,30 +113,46 @@ get_fname(const char *name) return buf; } -/** Remove all files stored under the temporary directory, and the directory - * itself. Called by atexit(). */ +/* Remove a directory and all of its subdirectories */ static void -remove_directory(void) +rm_rf(const char *dir) { + struct stat st; smartlist_t *elements; - if (getpid() != temp_dir_setup_in_pid) { - /* Only clean out the tempdir when the main process is exiting. */ - return; - } - elements = tor_listdir(temp_dir); + + elements = tor_listdir(dir); if (elements) { SMARTLIST_FOREACH(elements, const char *, cp, { - size_t len = strlen(cp)+strlen(temp_dir)+16; - char *tmp = tor_malloc(len); - tor_snprintf(tmp, len, "%s"PATH_SEPARATOR"%s", temp_dir, cp); - unlink(tmp); + char *tmp = NULL; + tor_asprintf(&tmp, "%s"PATH_SEPARATOR"%s", dir, cp); + if (0 == stat(tmp,&st) && (st.st_mode & S_IFDIR)) { + rm_rf(tmp); + } else { + if (unlink(tmp)) { + fprintf(stderr, "Error removing %s: %s\n", tmp, strerror(errno)); + } + } tor_free(tmp); }); SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); smartlist_free(elements); } - rmdir(temp_dir); + if (rmdir(dir)) + fprintf(stderr, "Error removing directory %s: %s\n", dir, strerror(errno)); +} + +/** Remove all files stored under the temporary directory, and the directory + * itself. Called by atexit(). */ +static void +remove_directory(void) +{ + if (getpid() != temp_dir_setup_in_pid) { + /* Only clean out the tempdir when the main process is exiting. */ + return; + } + + rm_rf(temp_dir); } /** Define this if unit tests spend too much time generating public keys*/ @@ -560,6 +576,7 @@ test_policy_summary_helper(const char *policy_str, smartlist_t *policy = smartlist_create(); char *summary = NULL; int r; + short_policy_t *short_policy = NULL; line.key = (char*)"foo"; line.value = (char *)policy_str; @@ -572,10 +589,14 @@ test_policy_summary_helper(const char *policy_str, test_assert(summary != NULL); test_streq(summary, expected_summary); + short_policy = parse_short_policy(summary); + tt_assert(short_policy); + done: tor_free(summary); if (policy) addr_policy_list_free(policy); + short_policy_free(short_policy); } /** Run unit tests for generating summary lines of exit policies */ @@ -1083,7 +1104,8 @@ test_stats(void) char *s = NULL; int i; - /* We shouldn't collect exit stats without initializing them. */ + /* Start with testing exit port statistics; we shouldn't collect exit + * stats without initializing them. */ rep_hist_note_exit_stream_opened(80); rep_hist_note_exit_bytes(80, 100, 10000); s = rep_hist_format_exit_stats(now + 86400); @@ -1128,7 +1150,7 @@ test_stats(void) test_assert(!s); /* Re-start stats, add some bytes, reset stats, and see what history we - * get when observing no streams or bytes at all. */ + * get when observing no streams or bytes at all. */ rep_hist_exit_stats_init(now); rep_hist_note_exit_stream_opened(80); rep_hist_note_exit_bytes(80, 100, 10000); @@ -1138,6 +1160,41 @@ test_stats(void) "exit-kibibytes-written other=0\n" "exit-kibibytes-read other=0\n" "exit-streams-opened other=0\n", s); + tor_free(s); + + /* Continue with testing connection statistics; we shouldn't collect + * conn stats without initializing them. */ + rep_hist_note_or_conn_bytes(1, 20, 400, now); + s = rep_hist_format_conn_stats(now + 86400); + test_assert(!s); + + /* Initialize stats, note bytes, and generate history string. */ + rep_hist_conn_stats_init(now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now + 5); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 10); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 15); + s = rep_hist_format_conn_stats(now + 86400); + test_streq("conn-bi-direct 2010-08-12 13:27:30 (86400 s) 0,0,1,0\n", s); + tor_free(s); + + /* Stop collecting stats, add some bytes, and ensure we don't generate + * a history string. */ + rep_hist_conn_stats_term(); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 15); + s = rep_hist_format_conn_stats(now + 86400); + test_assert(!s); + + /* Re-start stats, add some bytes, reset stats, and see what history we + * get when observing no bytes at all. */ + rep_hist_conn_stats_init(now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now + 5); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 10); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 15); + rep_hist_reset_conn_stats(now); + s = rep_hist_format_conn_stats(now + 86400); + test_streq("conn-bi-direct 2010-08-12 13:27:30 (86400 s) 0,0,0,0\n", s); done: tor_free(s); @@ -1197,6 +1254,7 @@ extern struct testcase_t crypto_tests[]; extern struct testcase_t container_tests[]; extern struct testcase_t util_tests[]; extern struct testcase_t dir_tests[]; +extern struct testcase_t microdesc_tests[]; static struct testgroup_t testgroups[] = { { "", test_array }, @@ -1205,6 +1263,7 @@ static struct testgroup_t testgroups[] = { { "container/", container_tests }, { "util/", util_tests }, { "dir/", dir_tests }, + { "dir/md/", microdesc_tests }, END_OF_GROUPS }; diff --git a/src/test/test_dir.c b/src/test/test_dir.c index c7c5a0b72b..74ea6a55ae 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -800,7 +800,7 @@ test_dir_v3_networkstatus(void) rs->or_port = 443; rs->dir_port = 8000; /* all flags but running cleared */ - rs->is_running = 1; + rs->is_flagged_running = 1; smartlist_add(vote->routerstatus_list, vrs); test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); @@ -815,7 +815,7 @@ test_dir_v3_networkstatus(void) rs->addr = 0x99009901; rs->or_port = 443; rs->dir_port = 0; - rs->is_exit = rs->is_stable = rs->is_fast = rs->is_running = + rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1; smartlist_add(vote->routerstatus_list, vrs); test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); @@ -832,7 +832,8 @@ test_dir_v3_networkstatus(void) rs->or_port = 400; rs->dir_port = 9999; rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = - rs->is_running = rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1; + rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = + rs->is_possible_guard = 1; smartlist_add(vote->routerstatus_list, vrs); test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); @@ -1072,7 +1073,8 @@ test_dir_v3_networkstatus(void) test_assert(!rs->is_fast); test_assert(!rs->is_possible_guard); test_assert(!rs->is_stable); - test_assert(rs->is_running); /* If it wasn't running it wouldn't be here */ + /* (If it wasn't running it wouldn't be here) */ + test_assert(rs->is_flagged_running); test_assert(!rs->is_v2_dir); test_assert(!rs->is_valid); test_assert(!rs->is_named); @@ -1094,7 +1096,7 @@ test_dir_v3_networkstatus(void) test_assert(rs->is_fast); test_assert(rs->is_possible_guard); test_assert(rs->is_stable); - test_assert(rs->is_running); + test_assert(rs->is_flagged_running); test_assert(rs->is_v2_dir); test_assert(rs->is_valid); test_assert(!rs->is_named); diff --git a/src/test/test_microdesc.c b/src/test/test_microdesc.c new file mode 100644 index 0000000000..5b14cdb3ed --- /dev/null +++ b/src/test/test_microdesc.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "or.h" + +#include "config.h" +#include "microdesc.h" + +#include "test.h" + +#ifdef MS_WINDOWS +/* For mkdir() */ +#include <direct.h> +#else +#include <dirent.h> +#endif + +static const char test_md1[] = + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAMjlHH/daN43cSVRaHBwgUfnszzAhg98EvivJ9Qxfv51mvQUxPjQ07es\n" + "gV/3n8fyh3Kqr/ehi9jxkdgSRfSnmF7giaHL1SLZ29kA7KtST+pBvmTpDtHa3ykX\n" + "Xorc7hJvIyTZoc1HU+5XSynj3gsBE5IGK1ZRzrNS688LnuZMVp1tAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n"; + +static const char test_md2[] = + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAMIixIowh2DyPmDNMDwBX2DHcYcqdcH1zdIQJZkyV6c6rQHnvbcaDoSg\n" + "jgFSLJKpnGmh71FVRqep+yVB0zI1JY43kuEnXry2HbZCD9UDo3d3n7t015X5S7ON\n" + "bSSYtQGPwOr6Epf96IF6DoQxy4iDnPUAlejuhAG51s1y6/rZQ3zxAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n"; + +static const char test_md3[] = + "@last-listed 2009-06-22\n" + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAMH3340d4ENNGrqx7UxT+lB7x6DNUKOdPEOn4teceE11xlMyZ9TPv41c\n" + "qj2fRZzfxlc88G/tmiaHshmdtEpklZ740OFqaaJVj4LjPMKFNE+J7Xc1142BE9Ci\n" + "KgsbjGYe2RY261aADRWLetJ8T9QDMm+JngL4288hc8pq1uB/3TAbAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n" + "p accept 1-700,800-1000\n" + "family nodeX nodeY nodeZ\n"; + +static void +test_md_cache(void *data) +{ + or_options_t *options = NULL; + microdesc_cache_t *mc = NULL ; + smartlist_t *added = NULL, *wanted = NULL; + microdesc_t *md1, *md2, *md3; + char d1[DIGEST256_LEN], d2[DIGEST256_LEN], d3[DIGEST256_LEN]; + const char *test_md3_noannotation = strchr(test_md3, '\n')+1; + time_t time1, time2, time3; + char *fn = NULL, *s = NULL; + (void)data; + + options = get_options(); + tt_assert(options); + + time1 = time(NULL); + time2 = time(NULL) - 2*24*60*60; + time3 = time(NULL) - 15*24*60*60; + + /* Possibly, turn this into a test setup/cleanup pair */ + tor_free(options->DataDirectory); + options->DataDirectory = tor_strdup(get_fname("md_datadir_test")); +#ifdef MS_WINDOWS + tt_int_op(0, ==, mkdir(options->DataDirectory)); +#else + tt_int_op(0, ==, mkdir(options->DataDirectory, 0700)); +#endif + + tt_assert(!strcmpstart(test_md3_noannotation, "onion-key")); + + crypto_digest256(d1, test_md1, strlen(test_md1), DIGEST_SHA256); + crypto_digest256(d2, test_md2, strlen(test_md1), DIGEST_SHA256); + crypto_digest256(d3, test_md3_noannotation, strlen(test_md3_noannotation), + DIGEST_SHA256); + + mc = get_microdesc_cache(); + + added = microdescs_add_to_cache(mc, test_md1, NULL, SAVED_NOWHERE, 0, + time1, NULL); + tt_int_op(1, ==, smartlist_len(added)); + md1 = smartlist_get(added, 0); + smartlist_free(added); + added = NULL; + + wanted = smartlist_create(); + added = microdescs_add_to_cache(mc, test_md2, NULL, SAVED_NOWHERE, 0, + time2, wanted); + /* Should fail, since we didn't list test_md2's digest in wanted */ + tt_int_op(0, ==, smartlist_len(added)); + smartlist_free(added); + added = NULL; + + smartlist_add(wanted, tor_memdup(d2, DIGEST256_LEN)); + smartlist_add(wanted, tor_memdup(d3, DIGEST256_LEN)); + added = microdescs_add_to_cache(mc, test_md2, NULL, SAVED_NOWHERE, 0, + time2, wanted); + /* Now it can work. md2 should have been added */ + tt_int_op(1, ==, smartlist_len(added)); + md2 = smartlist_get(added, 0); + /* And it should have gotten removed from 'wanted' */ + tt_int_op(smartlist_len(wanted), ==, 1); + test_mem_op(smartlist_get(wanted, 0), ==, d3, DIGEST256_LEN); + smartlist_free(added); + added = NULL; + + added = microdescs_add_to_cache(mc, test_md3, NULL, + SAVED_NOWHERE, 0, -1, NULL); + /* Must fail, since SAVED_NOWHERE precludes annotations */ + tt_int_op(0, ==, smartlist_len(added)); + smartlist_free(added); + added = NULL; + + added = microdescs_add_to_cache(mc, test_md3_noannotation, NULL, + SAVED_NOWHERE, 0, time3, NULL); + /* Now it can work */ + tt_int_op(1, ==, smartlist_len(added)); + md3 = smartlist_get(added, 0); + smartlist_free(added); + added = NULL; + + /* Okay. We added 1...3. Let's poke them to see how they look, and make + * sure they're really in the journal. */ + tt_ptr_op(md1, ==, microdesc_cache_lookup_by_digest256(mc, d1)); + tt_ptr_op(md2, ==, microdesc_cache_lookup_by_digest256(mc, d2)); + tt_ptr_op(md3, ==, microdesc_cache_lookup_by_digest256(mc, d3)); + + tt_int_op(md1->last_listed, ==, time1); + tt_int_op(md2->last_listed, ==, time2); + tt_int_op(md3->last_listed, ==, time3); + + tt_int_op(md1->saved_location, ==, SAVED_IN_JOURNAL); + tt_int_op(md2->saved_location, ==, SAVED_IN_JOURNAL); + tt_int_op(md3->saved_location, ==, SAVED_IN_JOURNAL); + + tt_int_op(md1->bodylen, ==, strlen(test_md1)); + tt_int_op(md2->bodylen, ==, strlen(test_md2)); + tt_int_op(md3->bodylen, ==, strlen(test_md3_noannotation)); + test_mem_op(md1->body, ==, test_md1, strlen(test_md1)); + test_mem_op(md2->body, ==, test_md2, strlen(test_md2)); + test_mem_op(md3->body, ==, test_md3_noannotation, + strlen(test_md3_noannotation)); + + tor_asprintf(&fn, "%s"PATH_SEPARATOR"cached-microdescs.new", + options->DataDirectory); + s = read_file_to_str(fn, RFTS_BIN, NULL); + tt_assert(s); + test_mem_op(md1->body, ==, s + md1->off, md1->bodylen); + test_mem_op(md2->body, ==, s + md2->off, md2->bodylen); + test_mem_op(md3->body, ==, s + md3->off, md3->bodylen); + + tt_ptr_op(md1->family, ==, NULL); + tt_ptr_op(md3->family, !=, NULL); + tt_int_op(smartlist_len(md3->family), ==, 3); + tt_str_op(smartlist_get(md3->family, 0), ==, "nodeX"); + + /* Now rebuild the cache! */ + tt_int_op(microdesc_cache_rebuild(mc), ==, 0); + + tt_int_op(md1->saved_location, ==, SAVED_IN_CACHE); + tt_int_op(md2->saved_location, ==, SAVED_IN_CACHE); + tt_int_op(md3->saved_location, ==, SAVED_IN_CACHE); + + /* The journal should be empty now */ + tor_free(s); + s = read_file_to_str(fn, RFTS_BIN, NULL); + tt_str_op(s, ==, ""); + tor_free(s); + tor_free(fn); + + /* read the cache. */ + tor_asprintf(&fn, "%s"PATH_SEPARATOR"cached-microdescs", + options->DataDirectory); + s = read_file_to_str(fn, RFTS_BIN, NULL); + test_mem_op(md1->body, ==, s + md1->off, strlen(test_md1)); + test_mem_op(md2->body, ==, s + md2->off, strlen(test_md2)); + test_mem_op(md3->body, ==, s + md3->off, strlen(test_md3_noannotation)); + + /* Okay, now we are going to forget about the cache entirely, and reload it + * from the disk. */ + microdesc_free_all(); + mc = get_microdesc_cache(); + md1 = microdesc_cache_lookup_by_digest256(mc, d1); + md2 = microdesc_cache_lookup_by_digest256(mc, d2); + md3 = microdesc_cache_lookup_by_digest256(mc, d3); + test_assert(md1); + test_assert(md2); + test_assert(md3); + test_mem_op(md1->body, ==, s + md1->off, strlen(test_md1)); + test_mem_op(md2->body, ==, s + md2->off, strlen(test_md2)); + test_mem_op(md3->body, ==, s + md3->off, strlen(test_md3_noannotation)); + + tt_int_op(md1->last_listed, ==, time1); + tt_int_op(md2->last_listed, ==, time2); + tt_int_op(md3->last_listed, ==, time3); + + /* Okay, now we are going to clear out everything older than a week old. + * In practice, that means md3 */ + microdesc_cache_clean(mc, time(NULL)-7*24*60*60, 1/*force*/); + tt_ptr_op(md1, ==, microdesc_cache_lookup_by_digest256(mc, d1)); + tt_ptr_op(md2, ==, microdesc_cache_lookup_by_digest256(mc, d2)); + tt_ptr_op(NULL, ==, microdesc_cache_lookup_by_digest256(mc, d3)); + md3 = NULL; /* it's history now! */ + + /* rebuild again, make sure it stays gone. */ + microdesc_cache_rebuild(mc); + tt_ptr_op(md1, ==, microdesc_cache_lookup_by_digest256(mc, d1)); + tt_ptr_op(md2, ==, microdesc_cache_lookup_by_digest256(mc, d2)); + tt_ptr_op(NULL, ==, microdesc_cache_lookup_by_digest256(mc, d3)); + + done: + if (options) + tor_free(options->DataDirectory); + microdesc_free_all(); + + smartlist_free(added); + if (wanted) + SMARTLIST_FOREACH(wanted, char *, cp, tor_free(cp)); + smartlist_free(wanted); + tor_free(s); + tor_free(fn); +} + +struct testcase_t microdesc_tests[] = { + { "cache", test_md_cache, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_util.c b/src/test/test_util.c index b1fafc84b2..25fd51c124 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -6,6 +6,7 @@ #include "orconfig.h" #define CONTROL_PRIVATE #define MEMPOOL_PRIVATE +#define UTIL_PRIVATE #include "or.h" #include "config.h" #include "control.h" @@ -832,6 +833,18 @@ test_util_sscanf(void) test_eq(u2, 3u); test_eq(u3, 99u); + /* %x should work. */ + r = tor_sscanf("1234 02aBcdEf", "%x %x", &u1, &u2); + test_eq(r, 2); + test_eq(u1, 0x1234); + test_eq(u2, 0x2ABCDEF); + /* Width works on %x */ + r = tor_sscanf("f00dcafe444", "%4x%4x%u", &u1, &u2, &u3); + test_eq(r, 3); + test_eq(u1, 0xf00d); + test_eq(u2, 0xcafe); + test_eq(u3, 444); + r = tor_sscanf("99% fresh", "%3u%% fresh", &u1); /* percents are scannable.*/ test_eq(r, 1); test_eq(u1, 99); @@ -1209,6 +1222,202 @@ test_util_load_win_lib(void *ptr) } #endif +static void +clear_hex_errno(char *hex_errno) +{ + memset(hex_errno, '\0', HEX_ERRNO_SIZE + 1); +} + +static void +test_util_exit_status(void *ptr) +{ + /* Leave an extra byte for a \0 so we can do string comparison */ + char hex_errno[HEX_ERRNO_SIZE + 1]; + + (void)ptr; + + clear_hex_errno(hex_errno); + format_helper_exit_status(0, 0, hex_errno); + tt_str_op(hex_errno, ==, " 0/0\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0, 0x7FFFFFFF, hex_errno); + tt_str_op(hex_errno, ==, " 0/7FFFFFFF\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0xFF, -0x80000000, hex_errno); + tt_str_op(hex_errno, ==, "FF/-80000000\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0x7F, 0, hex_errno); + tt_str_op(hex_errno, ==, " 7F/0\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0x08, -0x242, hex_errno); + tt_str_op(hex_errno, ==, " 8/-242\n"); + + done: + ; +} + +#ifndef MS_WINDOWS +/** Check that fgets waits until a full line, and not return a partial line, on + * a EAGAIN with a non-blocking pipe */ +static void +test_util_fgets_eagain(void *ptr) +{ + int test_pipe[2] = {-1, -1}; + int retval; + ssize_t retlen; + char *retptr; + FILE *test_stream = NULL; + char buf[10]; + + (void)ptr; + + /* Set up a pipe to test on */ + retval = pipe(test_pipe); + tt_int_op(retval, >=, 0); + + /* Set up the read-end to be non-blocking */ + retval = fcntl(test_pipe[0], F_SETFL, O_NONBLOCK); + tt_int_op(retval, >=, 0); + + /* Open it as a stdio stream */ + test_stream = fdopen(test_pipe[0], "r"); + tt_ptr_op(test_stream, !=, NULL); + + /* Send in a partial line */ + retlen = write(test_pipe[1], "A", 1); + tt_int_op(retlen, ==, 1); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_want(retptr == NULL); + tt_int_op(errno, ==, EAGAIN); + + /* Send in the rest */ + retlen = write(test_pipe[1], "B\n", 2); + tt_int_op(retlen, ==, 2); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "AB\n"); + + /* Send in a full line */ + retlen = write(test_pipe[1], "CD\n", 3); + tt_int_op(retlen, ==, 3); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "CD\n"); + + /* Send in a partial line */ + retlen = write(test_pipe[1], "E", 1); + tt_int_op(retlen, ==, 1); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, NULL); + tt_int_op(errno, ==, EAGAIN); + + /* Send in the rest */ + retlen = write(test_pipe[1], "F\n", 2); + tt_int_op(retlen, ==, 2); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "EF\n"); + + /* Send in a full line and close */ + retlen = write(test_pipe[1], "GH", 2); + tt_int_op(retlen, ==, 2); + retval = close(test_pipe[1]); + test_pipe[1] = -1; + tt_int_op(retval, ==, 0); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "GH"); + + /* Check for EOF */ + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, NULL); + tt_int_op(feof(test_stream), >, 0); + + done: + if (test_stream != NULL) + fclose(test_stream); + if (test_pipe[0] != -1) + close(test_pipe[0]); + if (test_pipe[1] != -1) + close(test_pipe[1]); +} +#endif + +#ifndef MS_WINDOWS +/** Helper function for testing tor_spawn_background */ +static void +run_util_spawn_background(const char *argv[], const char *expected_out, + const char *expected_err, int expected_exit) +{ + int stdout_pipe=-1, stderr_pipe=-1; + int retval, stat_loc; + pid_t pid; + ssize_t pos; + char stdout_buf[100], stderr_buf[100]; + + /* Start the program */ + retval = tor_spawn_background(argv[0], &stdout_pipe, &stderr_pipe, argv); + tt_int_op(retval, >, 0); + tt_int_op(stdout_pipe, >, 0); + tt_int_op(stderr_pipe, >, 0); + pid = retval; + + /* Check stdout */ + pos = read(stdout_pipe, stdout_buf, sizeof(stdout_buf) - 1); + stdout_buf[pos] = '\0'; + tt_int_op(pos, ==, strlen(expected_out)); + tt_str_op(stdout_buf, ==, expected_out); + + /* Check it terminated correctly */ + retval = waitpid(pid, &stat_loc, 0); + tt_int_op(retval, ==, pid); + tt_assert(WIFEXITED(stat_loc)); + tt_int_op(WEXITSTATUS(stat_loc), ==, expected_exit); + tt_assert(!WIFSIGNALED(stat_loc)); + tt_assert(!WIFSTOPPED(stat_loc)); + + /* Check stderr */ + pos = read(stderr_pipe, stderr_buf, sizeof(stderr_buf) - 1); + stderr_buf[pos] = '\0'; + tt_int_op(pos, ==, strlen(expected_err)); + tt_str_op(stderr_buf, ==, expected_err); + + done: + ; +} + +/** Check that we can launch a process and read the output */ +static void +test_util_spawn_background_ok(void *ptr) +{ + const char *argv[] = {BUILDDIR "/src/test/test-child", "--test", NULL}; + const char *expected_out = "OUT\n--test\nDONE\n"; + const char *expected_err = "ERR\n"; + + (void)ptr; + + run_util_spawn_background(argv, expected_out, expected_err, 0); +} + +/** Check that failing to find the executable works as expected */ +static void +test_util_spawn_background_fail(void *ptr) +{ + const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL}; + const char *expected_out = "ERR: Failed to spawn background process " + "- code 9/2\n"; + const char *expected_err = ""; + + (void)ptr; + + run_util_spawn_background(argv, expected_out, expected_err, 255); +} +#endif + #define UTIL_LEGACY(name) \ { #name, legacy_test_helper, 0, &legacy_setup, test_util_ ## name } @@ -1235,6 +1444,12 @@ struct testcase_t util_tests[] = { #ifdef MS_WINDOWS UTIL_TEST(load_win_lib, 0), #endif + UTIL_TEST(exit_status, 0), +#ifndef MS_WINDOWS + UTIL_TEST(fgets_eagain, TT_SKIP), + UTIL_TEST(spawn_background_ok, 0), + UTIL_TEST(spawn_background_fail, 0), +#endif END_OF_TESTCASES }; diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index 1bb5076849..a9a619757a 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -2,17 +2,19 @@ bin_PROGRAMS = tor-resolve tor-gencert noinst_PROGRAMS = tor-checkkey tor_resolve_SOURCES = tor-resolve.c -tor_resolve_LDFLAGS = @TOR_LDFLAGS_libevent@ -tor_resolve_LDADD = ../common/libor.a -lm @TOR_LIBEVENT_LIBS@ @TOR_LIB_WS32@ +tor_resolve_LDFLAGS = +tor_resolve_LDADD = ../common/libor.a -lm @TOR_LIB_WS32@ tor_gencert_SOURCES = tor-gencert.c -tor_gencert_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ - @TOR_LDFLAGS_libevent@ +tor_gencert_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ tor_gencert_LDADD = ../common/libor.a ../common/libor-crypto.a \ - -lm @TOR_ZLIB_LIBS@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ + -lm @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ tor_checkkey_SOURCES = tor-checkkey.c -tor_checkkey_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ - @TOR_LDFLAGS_libevent@ +tor_checkkey_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ tor_checkkey_LDADD = ../common/libor.a ../common/libor-crypto.a \ - -lm @TOR_ZLIB_LIBS@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ + -lm @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ + +SUBDIRS = tor-fw-helper +DIST_SUBDIRS = tor-fw-helper + diff --git a/src/tools/tor-fw-helper/Makefile.am b/src/tools/tor-fw-helper/Makefile.am new file mode 100644 index 0000000000..75878f2594 --- /dev/null +++ b/src/tools/tor-fw-helper/Makefile.am @@ -0,0 +1,38 @@ +if USE_FW_HELPER +bin_PROGRAMS = tor-fw-helper +else +bin_PROGRAMS = +endif + +tor_fw_helper_SOURCES = \ + tor-fw-helper.c \ + tor-fw-helper-natpmp.c \ + tor-fw-helper-upnp.c +noinst_HEADERS = \ + tor-fw-helper.h \ + tor-fw-helper-natpmp.h \ + tor-fw-helper-upnp.h + +if NAT_PMP +nat_pmp_ldflags = @TOR_LDFLAGS_libnatpmp@ +nat_pmp_ldadd = -lnatpmp +nat_pmp_cppflags = @TOR_CPPFLAGS_libnatpmp@ +else +nat_pmp_ldflags = +nat_pmp_ldadd = +nat_pmp_cppflags = +endif + +if MINIUPNPC +miniupnpc_ldflags = @TOR_LDFLAGS_libminiupnpc@ +miniupnpc_ldadd = -lminiupnpc -lm +miniupnpc_cppflags = @TOR_CPPFLAGS_libminiupnpc@ +else +miniupnpc_ldflags = +miniupnpc_ldadd = +miniupnpc_cppflags = +endif + +tor_fw_helper_LDFLAGS = $(nat_pmp_ldflags) $(miniupnpc_ldflags) +tor_fw_helper_LDADD = $(nat_pmp_ldadd) $(miniupnpc_ldadd) ../../common/libor.a @TOR_LIB_WS32@ +tor_fw_helper_CPPFLAGS = $(nat_pmp_cppflags) $(miniupnpc_cppflags) diff --git a/src/tools/tor-fw-helper/tor-fw-helper-natpmp.c b/src/tools/tor-fw-helper/tor-fw-helper-natpmp.c new file mode 100644 index 0000000000..2630b8f5e0 --- /dev/null +++ b/src/tools/tor-fw-helper/tor-fw-helper-natpmp.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch. + * Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor-fw-helper-natpmp.c + * \brief The implementation of our NAT-PMP firewall helper. + **/ + +#include "orconfig.h" +#ifdef NAT_PMP +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> + +// debugging stuff +#include <assert.h> + +#include "tor-fw-helper.h" +#include "tor-fw-helper-natpmp.h" + +/** This hooks NAT-PMP into our multi-backend API. */ +static tor_fw_backend_t tor_natpmp_backend = { + "natpmp", + sizeof(struct natpmp_state_t), + tor_natpmp_init, + tor_natpmp_cleanup, + tor_natpmp_fetch_public_ip, + tor_natpmp_add_tcp_mapping +}; + +/** Return the backend for NAT-PMP. */ +const tor_fw_backend_t * +tor_fw_get_natpmp_backend(void) +{ + return &tor_natpmp_backend; +} + +/** Initialize the NAT-PMP backend and store the results in + * <b>backend_state</b>.*/ +int +tor_natpmp_init(tor_fw_options_t *tor_fw_options, void *backend_state) +{ + natpmp_state_t *state = (natpmp_state_t *) backend_state; + int r = 0; + + memset(&(state->natpmp), 0, sizeof(natpmp_t)); + memset(&(state->response), 0, sizeof(natpmpresp_t)); + state->init = 0; + state->protocol = NATPMP_PROTOCOL_TCP; + state->lease = NATPMP_DEFAULT_LEASE; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: natpmp init...\n"); + + r = initnatpmp(&(state->natpmp)); + if (r == 0) { + state->init = 1; + fprintf(stdout, "tor-fw-helper: natpmp initialized...\n"); + return r; + } else { + fprintf(stderr, "tor-fw-helper: natpmp failed to initialize...\n"); + return r; + } +} + +/** Tear down the NAT-PMP connection stored in <b>backend_state</b>.*/ +int +tor_natpmp_cleanup(tor_fw_options_t *tor_fw_options, void *backend_state) +{ + natpmp_state_t *state = (natpmp_state_t *) backend_state; + int r = 0; + if (tor_fw_options->verbose) + fprintf(stdout, "V: natpmp cleanup...\n"); + r = closenatpmp(&(state->natpmp)); + if (tor_fw_options->verbose) + fprintf(stdout, "V: closing natpmp socket: %d\n", r); + return r; +} + +/** Use select() to wait until we can read on fd. */ +static int +wait_until_fd_readable(int fd, struct timeval *timeout) +{ + int r; + fd_set fds; + if (fd >= FD_SETSIZE) { + fprintf(stderr, "E: NAT-PMP FD_SETSIZE error %d\n", fd); + return -1; + } + FD_ZERO(&fds); + FD_SET(fd, &fds); + r = select(fd+1, &fds, NULL, NULL, timeout); + if (r == -1) { + fprintf(stdout, "V: select failed in wait_until_fd_readable: %s\n", + strerror(errno)); + return -1; + } + /* XXXX we should really check to see whether fd was readable, or we timed + out. */ + return 0; +} + +/** Add a TCP port mapping for a single port stored in <b>tor_fw_options</b> + * using the <b>natpmp_t</b> stored in <b>backend_state</b>. */ +int +tor_natpmp_add_tcp_mapping(tor_fw_options_t *tor_fw_options, + void *backend_state) +{ + natpmp_state_t *state = (natpmp_state_t *) backend_state; + int r = 0; + int x = 0; + int sav_errno; + + struct timeval timeout; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: sending natpmp portmapping request...\n"); + r = sendnewportmappingrequest(&(state->natpmp), state->protocol, + tor_fw_options->internal_port, + tor_fw_options->external_port, + state->lease); + if (tor_fw_options->verbose) + fprintf(stdout, "tor-fw-helper: NAT-PMP sendnewportmappingrequest " + "returned %d (%s)\n", r, r==12?"SUCCESS":"FAILED"); + + do { + getnatpmprequesttimeout(&(state->natpmp), &timeout); + x = wait_until_fd_readable(state->natpmp.s, &timeout); + if (x == -1) + return -1; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: attempting to readnatpmpreponseorretry...\n"); + r = readnatpmpresponseorretry(&(state->natpmp), &(state->response)); + sav_errno = errno; + + if (r<0 && r!=NATPMP_TRYAGAIN) { + fprintf(stderr, "E: readnatpmpresponseorretry failed %d\n", r); + fprintf(stderr, "E: errno=%d '%s'\n", sav_errno, + strerror(sav_errno)); + } + + } while (r == NATPMP_TRYAGAIN); + + if (r != 0) { + /* XXX TODO: NATPMP_* should be formatted into useful error strings */ + fprintf(stderr, "E: NAT-PMP It appears that something went wrong:" + " %d\n", r); + if (r == -51) + fprintf(stderr, "E: NAT-PMP It appears that the request was " + "unauthorized\n"); + return r; + } + + if (r == NATPMP_SUCCESS) { + fprintf(stdout, "tor-fw-helper: NAT-PMP mapped public port %hu to" + " localport %hu liftime %u\n", + (state->response).pnu.newportmapping.mappedpublicport, + (state->response).pnu.newportmapping.privateport, + (state->response).pnu.newportmapping.lifetime); + } + + tor_fw_options->nat_pmp_status = 1; + + return r; +} + +/** Fetch our likely public IP from our upstream NAT-PMP enabled NAT device. + * Use the connection context stored in <b>backend_state</b>. */ +int +tor_natpmp_fetch_public_ip(tor_fw_options_t *tor_fw_options, + void *backend_state) +{ + int r = 0; + int x = 0; + int sav_errno; + natpmp_state_t *state = (natpmp_state_t *) backend_state; + + struct timeval timeout; + + r = sendpublicaddressrequest(&(state->natpmp)); + fprintf(stdout, "tor-fw-helper: NAT-PMP sendpublicaddressrequest returned" + " %d (%s)\n", r, r==2?"SUCCESS":"FAILED"); + + do { + getnatpmprequesttimeout(&(state->natpmp), &timeout); + + x = wait_until_fd_readable(state->natpmp.s, &timeout); + if (x == -1) + return -1; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: NAT-PMP attempting to read reponse...\n"); + r = readnatpmpresponseorretry(&(state->natpmp), &(state->response)); + sav_errno = errno; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: NAT-PMP readnatpmpresponseorretry returned" + " %d\n", r); + + if ( r < 0 && r != NATPMP_TRYAGAIN) { + fprintf(stderr, "E: NAT-PMP readnatpmpresponseorretry failed %d\n", + r); + fprintf(stderr, "E: NAT-PMP errno=%d '%s'\n", sav_errno, + strerror(sav_errno)); + } + + } while (r == NATPMP_TRYAGAIN ); + + if (r != 0) { + fprintf(stderr, "E: NAT-PMP It appears that something went wrong:" + " %d\n", r); + return r; + } + + fprintf(stdout, "tor-fw-helper: ExternalIPAddress = %s\n", + inet_ntoa((state->response).pnu.publicaddress.addr)); + tor_fw_options->public_ip_status = 1; + + if (tor_fw_options->verbose) { + fprintf(stdout, "V: result = %u\n", r); + fprintf(stdout, "V: type = %u\n", (state->response).type); + fprintf(stdout, "V: resultcode = %u\n", (state->response).resultcode); + fprintf(stdout, "V: epoch = %u\n", (state->response).epoch); + } + + return r; +} +#endif + diff --git a/src/tools/tor-fw-helper/tor-fw-helper-natpmp.h b/src/tools/tor-fw-helper/tor-fw-helper-natpmp.h new file mode 100644 index 0000000000..0190379a23 --- /dev/null +++ b/src/tools/tor-fw-helper/tor-fw-helper-natpmp.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch. + * Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor-fw-helper-natpmp.h + **/ + +#ifdef NAT_PMP +#ifndef _TOR_FW_HELPER_NATPMP_H +#define _TOR_FW_HELPER_NATPMP_H + +#include <natpmp.h> + +/** This is the default NAT-PMP lease time in seconds. */ +#define NATPMP_DEFAULT_LEASE 3600 +/** NAT-PMP has many codes for success; this is one of them. */ +#define NATPMP_SUCCESS 0 + +/** This is our NAT-PMP meta structure - it holds our request data, responses, + * various NAT-PMP parameters, and of course the status of the motion in the + * NAT-PMP ocean. */ +typedef struct natpmp_state_t { + natpmp_t natpmp; + natpmpresp_t response; + int fetch_public_ip; + int status; + int init; /**< Have we been initialized? */ + int protocol; /**< This will only be TCP. */ + int lease; +} natpmp_state_t; + +const tor_fw_backend_t *tor_fw_get_natpmp_backend(void); + +int tor_natpmp_init(tor_fw_options_t *tor_fw_options, void *backend_state); + +int tor_natpmp_cleanup(tor_fw_options_t *tor_fw_options, void *backend_state); + +int tor_natpmp_add_tcp_mapping(tor_fw_options_t *tor_fw_options, + void *backend_state); + +int tor_natpmp_fetch_public_ip(tor_fw_options_t *tor_fw_options, + void *backend_state); + +#endif +#endif + diff --git a/src/tools/tor-fw-helper/tor-fw-helper-upnp.c b/src/tools/tor-fw-helper/tor-fw-helper-upnp.c new file mode 100644 index 0000000000..18ca56394f --- /dev/null +++ b/src/tools/tor-fw-helper/tor-fw-helper-upnp.c @@ -0,0 +1,186 @@ +/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch. + * Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor-fw-helper-upnp.c + * \brief The implementation of our UPnP firewall helper. + **/ + +#include "orconfig.h" +#ifdef MINIUPNPC +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +#include <assert.h> + +#include "compat.h" +#include "tor-fw-helper.h" +#include "tor-fw-helper-upnp.h" + +/** UPnP timeout value. */ +#define UPNP_DISCOVER_TIMEOUT 2000 +/** Description of the port mapping in the UPnP table. */ +#define UPNP_DESC "Tor relay" + +/* XXX TODO: We should print these as a useful user string when we return the + * number to a user */ +/** Magic numbers as miniupnpc return codes. */ +#define UPNP_ERR_SUCCESS 0 +#define UPNP_ERR_NODEVICESFOUND 1 +#define UPNP_ERR_NOIGDFOUND 2 +#define UPNP_ERR_ADDPORTMAPPING 3 +#define UPNP_ERR_GETPORTMAPPING 4 +#define UPNP_ERR_DELPORTMAPPING 5 +#define UPNP_ERR_GETEXTERNALIP 6 +#define UPNP_ERR_INVAL 7 +#define UPNP_ERR_OTHER 8 +#define UPNP_SUCCESS 1 + +/** This hooks miniupnpc into our multi-backend API. */ +static tor_fw_backend_t tor_miniupnp_backend = { + "miniupnp", + sizeof(struct miniupnpc_state_t), + tor_upnp_init, + tor_upnp_cleanup, + tor_upnp_fetch_public_ip, + tor_upnp_add_tcp_mapping +}; + +/** Return the backend for miniupnp. */ +const tor_fw_backend_t * +tor_fw_get_miniupnp_backend(void) +{ + return &tor_miniupnp_backend; +} + +/** Initialize the UPnP backend and store the results in + * <b>backend_state</b>.*/ +int +tor_upnp_init(tor_fw_options_t *options, void *backend_state) +{ + /* + This leaks the user agent from the client to the router - perhaps we don't + want to do that? eg: + + User-Agent: Ubuntu/10.04, UPnP/1.0, MiniUPnPc/1.4 + + */ + miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state; + struct UPNPDev *devlist; + int r; + + memset(&(state->urls), 0, sizeof(struct UPNPUrls)); + memset(&(state->data), 0, sizeof(struct IGDdatas)); + state->init = 0; + + devlist = upnpDiscover(UPNP_DISCOVER_TIMEOUT, NULL, NULL, 0); + if (NULL == devlist) { + fprintf(stderr, "E: upnpDiscover returned: NULL\n"); + return UPNP_ERR_NODEVICESFOUND; + } + + assert(options); + r = UPNP_GetValidIGD(devlist, &(state->urls), &(state->data), + state->lanaddr, UPNP_LANADDR_SZ); + fprintf(stdout, "tor-fw-helper: UPnP GetValidIGD returned: %d (%s)\n", r, + r==UPNP_SUCCESS?"SUCCESS":"FAILED"); + + freeUPNPDevlist(devlist); + + if (r != 1 && r != 2) + return UPNP_ERR_NOIGDFOUND; + + state->init = 1; + return UPNP_ERR_SUCCESS; +} + +/** Tear down the UPnP connection stored in <b>backend_state</b>.*/ +int +tor_upnp_cleanup(tor_fw_options_t *options, void *backend_state) +{ + + miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state; + assert(options); + + if (state->init) + FreeUPNPUrls(&(state->urls)); + state->init = 0; + + return UPNP_ERR_SUCCESS; +} + +/** Fetch our likely public IP from our upstream UPnP IGD enabled NAT device. + * Use the connection context stored in <b>backend_state</b>. */ +int +tor_upnp_fetch_public_ip(tor_fw_options_t *options, void *backend_state) +{ + miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state; + int r; + char externalIPAddress[16]; + + if (!state->init) { + r = tor_upnp_init(options, state); + if (r != UPNP_ERR_SUCCESS) + return r; + } + + r = UPNP_GetExternalIPAddress(state->urls.controlURL, + state->data.first.servicetype, + externalIPAddress); + + if (r != UPNPCOMMAND_SUCCESS) + goto err; + + if (externalIPAddress[0]) { + fprintf(stdout, "tor-fw-helper: ExternalIPAddress = %s\n", + externalIPAddress); tor_upnp_cleanup(options, state); + options->public_ip_status = 1; + return UPNP_ERR_SUCCESS; + } else { + goto err; + } + + err: + tor_upnp_cleanup(options, state); + return UPNP_ERR_GETEXTERNALIP; +} + +/** Add a TCP port mapping for a single port stored in <b>tor_fw_options</b> + * and store the results in <b>backend_state</b>. */ +int +tor_upnp_add_tcp_mapping(tor_fw_options_t *options, void *backend_state) +{ + miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state; + int r; + char internal_port_str[6]; + char external_port_str[6]; + + if (!state->init) { + r = tor_upnp_init(options, state); + if (r != UPNP_ERR_SUCCESS) + return r; + } + + if (options->verbose) + fprintf(stdout, "V: internal port: %d, external port: %d\n", + (int)options->internal_port, (int)options->external_port); + + tor_snprintf(internal_port_str, sizeof(internal_port_str), + "%d", (int)options->internal_port); + tor_snprintf(external_port_str, sizeof(external_port_str), + "%d", (int)options->external_port); + + r = UPNP_AddPortMapping(state->urls.controlURL, + state->data.first.servicetype, + external_port_str, internal_port_str, + state->lanaddr, UPNP_DESC, "TCP", 0); + if (r != UPNPCOMMAND_SUCCESS) + return UPNP_ERR_ADDPORTMAPPING; + + options->upnp_status = 1; + return UPNP_ERR_SUCCESS; +} +#endif + diff --git a/src/tools/tor-fw-helper/tor-fw-helper-upnp.h b/src/tools/tor-fw-helper/tor-fw-helper-upnp.h new file mode 100644 index 0000000000..021a8e0aac --- /dev/null +++ b/src/tools/tor-fw-helper/tor-fw-helper-upnp.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch. + * Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor-fw-helper-upnp.h + * \brief The main header for our firewall helper. + **/ + +#ifdef MINIUPNPC +#ifndef _TOR_FW_HELPER_UPNP_H +#define _TOR_FW_HELPER_UPNP_H + +#include <miniupnpc/miniwget.h> +#include <miniupnpc/miniupnpc.h> +#include <miniupnpc/upnpcommands.h> +#include <miniupnpc/upnperrors.h> + +/** This is a magic number for miniupnpc lan address size. */ +#define UPNP_LANADDR_SZ 64 + +/** This is our miniupnpc meta structure - it holds our request data, + * responses, and various miniupnpc parameters. */ +typedef struct miniupnpc_state_t { + struct UPNPUrls urls; + struct IGDdatas data; + char lanaddr[UPNP_LANADDR_SZ]; + int init; +} miniupnpc_state_t; + +const tor_fw_backend_t *tor_fw_get_miniupnp_backend(void); + +int tor_upnp_init(tor_fw_options_t *options, void *backend_state); + +int tor_upnp_cleanup(tor_fw_options_t *options, void *backend_state); + +int tor_upnp_fetch_public_ip(tor_fw_options_t *options, void *backend_state); + +int tor_upnp_add_tcp_mapping(tor_fw_options_t *options, void *backend_state); + +#endif +#endif + diff --git a/src/tools/tor-fw-helper/tor-fw-helper.c b/src/tools/tor-fw-helper/tor-fw-helper.c new file mode 100644 index 0000000000..20d60d7ba6 --- /dev/null +++ b/src/tools/tor-fw-helper/tor-fw-helper.c @@ -0,0 +1,363 @@ +/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch. + * Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor-fw-helper.c + * \brief The main wrapper around our firewall helper logic. + **/ + +/* + * tor-fw-helper is a tool for opening firewalls with NAT-PMP and UPnP; this + * tool is designed to be called by hand or by Tor by way of a exec() at a + * later date. + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <getopt.h> +#include <time.h> +#include <string.h> + +#include "orconfig.h" +#include "tor-fw-helper.h" +#ifdef NAT_PMP +#include "tor-fw-helper-natpmp.h" +#endif +#ifdef MINIUPNPC +#include "tor-fw-helper-upnp.h" +#endif + +/** This is our meta storage type - it holds information about each helper + including the total number of helper backends, function pointers, and helper + state. */ +typedef struct backends_t { + /** The total number of backends */ + int n_backends; + /** The backend functions as an array */ + tor_fw_backend_t backend_ops[MAX_BACKENDS]; + /** The internal backend state */ + void *backend_state[MAX_BACKENDS]; +} backends_t; + +/** Initalize each backend helper with the user input stored in <b>options</b> + * and put the results in the <b>backends</b> struct. */ +static int +init_backends(tor_fw_options_t *options, backends_t *backends) +{ + int n_available = 0; + int i, r, n; + tor_fw_backend_t *backend_ops_list[MAX_BACKENDS]; + void *data = NULL; + /* First, build a list of the working backends. */ + n = 0; +#ifdef MINIUPNPC + backend_ops_list[n++] = (tor_fw_backend_t *) tor_fw_get_miniupnp_backend(); +#endif +#ifdef NAT_PMP + backend_ops_list[n++] = (tor_fw_backend_t *) tor_fw_get_natpmp_backend(); +#endif + n_available = n; + + /* Now, for each backend that might work, try to initialize it. + * That's how we roll, initialized. + */ + n = 0; + for (i=0; i<n_available; ++i) { + data = calloc(1, backend_ops_list[i]->state_len); + if (!data) { + perror("calloc"); + exit(1); + } + r = backend_ops_list[i]->init(options, data); + if (r == 0) { + backends->backend_ops[n] = *backend_ops_list[i]; + backends->backend_state[n] = data; + n++; + } else { + free(data); + } + } + backends->n_backends = n; + + return n; +} + +/** Return the proper commandline switches when the user needs information. */ +static void +usage(void) +{ + fprintf(stderr, "tor-fw-helper usage:\n" + " [-h|--help]\n" + " [-T|--Test]\n" + " [-v|--verbose]\n" + " [-g|--fetch-public-ip]\n" + " -i|--internal-or-port [TCP port]\n" + " [-e|--external-or-port [TCP port]]\n" + " [-d|--internal-dir-port [TCP port]\n" + " [-p|--external-dir-port [TCP port]]]\n"); +} + +/** Log commandline options to a hardcoded file <b>tor-fw-helper.log</b> in the + * current working directory. */ +static int +log_commandline_options(int argc, char **argv) +{ + int i, retval; + FILE *logfile; + time_t now; + + /* Open the log file */ + logfile = fopen("tor-fw-helper.log", "a"); + if (NULL == logfile) + return -1; + + /* Send all commandline arguments to the file */ + now = time(NULL); + retval = fprintf(logfile, "START: %s\n", ctime(&now)); + for (i = 0; i < argc; i++) { + retval = fprintf(logfile, "ARG: %d: %s\n", i, argv[i]); + if (retval < 0) + goto error; + + retval = fprintf(stdout, "ARG: %d: %s\n", i, argv[i]); + if (retval < 0) + goto error; + } + now = time(NULL); + retval = fprintf(logfile, "END: %s\n", ctime(&now)); + + /* Close and clean up */ + retval = fclose(logfile); + return retval; + + /* If there was an error during writing */ + error: + fclose(logfile); + return -1; +} + +/** Iterate over over each of the supported <b>backends</b> and attempt to + * fetch the public ip. */ +static void +tor_fw_fetch_public_ip(tor_fw_options_t *tor_fw_options, + backends_t *backends) +{ + int i; + int r = 0; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: tor_fw_fetch_public_ip\n"); + + for (i=0; i<backends->n_backends; ++i) { + if (tor_fw_options->verbose) { + fprintf(stdout, "V: running backend_state now: %i\n", i); + fprintf(stdout, "V: size of backend state: %u\n", + (int)(backends->backend_ops)[i].state_len); + fprintf(stdout, "V: backend state name: %s\n", + (char *)(backends->backend_ops)[i].name); + } + r = backends->backend_ops[i].fetch_public_ip(tor_fw_options, + backends->backend_state[i]); + fprintf(stdout, "tor-fw-helper: tor_fw_fetch_public_ip backend %s " + " returned: %i\n", (char *)(backends->backend_ops)[i].name, r); + } +} + +/** Iterate over each of the supported <b>backends</b> and attempt to add a + * port forward for the OR port stored in <b>tor_fw_options</b>. */ +static void +tor_fw_add_or_port(tor_fw_options_t *tor_fw_options, + backends_t *backends) +{ + int i; + int r = 0; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: tor_fw_add_or_port\n"); + + for (i=0; i<backends->n_backends; ++i) { + if (tor_fw_options->verbose) { + fprintf(stdout, "V: running backend_state now: %i\n", i); + fprintf(stdout, "V: size of backend state: %u\n", + (int)(backends->backend_ops)[i].state_len); + fprintf(stdout, "V: backend state name: %s\n", + (const char *) backends->backend_ops[i].name); + } + r = backends->backend_ops[i].add_tcp_mapping(tor_fw_options, + backends->backend_state[i]); + fprintf(stdout, "tor-fw-helper: tor_fw_add_or_port backend %s " + "returned: %i\n", (const char *) backends->backend_ops[i].name, r); + } +} + +/** Iterate over each of the supported <b>backends</b> and attempt to add a + * port forward for the Dir port stored in <b>tor_fw_options</b>. */ +static void +tor_fw_add_dir_port(tor_fw_options_t *tor_fw_options, + backends_t *backends) +{ + int i; + int r = 0; + + if (tor_fw_options->verbose) + fprintf(stdout, "V: tor_fw_add_dir_port\n"); + + for (i=0; i<backends->n_backends; ++i) { + if (tor_fw_options->verbose) { + fprintf(stdout, "V: running backend_state now: %i\n", i); + fprintf(stdout, "V: size of backend state: %u\n", + (int)(backends->backend_ops)[i].state_len); + fprintf(stdout, "V: backend state name: %s\n", + (char *)(backends->backend_ops)[i].name); + } + r = backends->backend_ops[i].add_tcp_mapping(tor_fw_options, + backends->backend_state[i]); + fprintf(stdout, "tor-fw-helper: tor_fw_add_dir_port backend %s " + "returned: %i\n", (const char *)backends->backend_ops[i].name, r); + } +} + +int +main(int argc, char **argv) +{ + int r = 0; + int c = 0; + + tor_fw_options_t tor_fw_options; + backends_t backend_state; + + memset(&tor_fw_options, 0, sizeof(tor_fw_options)); + + while (1) { + int option_index = 0; + static struct option long_options[] = + { + {"verbose", 0, 0, 'v'}, + {"help", 0, 0, 'h'}, + {"internal-or-port", 1, 0, 'i'}, + {"external-or-port", 1, 0, 'e'}, + {"internal-dir-port", 1, 0, 'd'}, + {"external-dir-port", 1, 0, 'p'}, + {"fetch-public-ip", 0, 0, 'g'}, + {"test-commandline", 0, 0, 'T'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "vhi:e:d:p:gT", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'v': tor_fw_options.verbose = 1; break; + case 'h': tor_fw_options.help = 1; usage(); exit(1); break; + case 'i': sscanf(optarg, "%hu", &tor_fw_options.private_or_port); + break; + case 'e': sscanf(optarg, "%hu", &tor_fw_options.public_or_port); + break; + case 'd': sscanf(optarg, "%hu", &tor_fw_options.private_dir_port); + break; + case 'p': sscanf(optarg, "%hu", &tor_fw_options.public_dir_port); + break; + case 'g': tor_fw_options.fetch_public_ip = 1; break; + case 'T': tor_fw_options.test_commandline = 1; break; + case '?': break; + default : fprintf(stderr, "Unknown option!\n"); usage(); exit(1); + } + } + + if (tor_fw_options.verbose) { + fprintf(stderr, "V: tor-fw-helper version %s\n" + "V: We were called with the following arguments:\n" + "V: verbose = %d, help = %d, pub or port = %u, " + "priv or port = %u\n" + "V: pub dir port = %u, priv dir port = %u\n" + "V: fetch_public_ip = %u\n", + tor_fw_version, tor_fw_options.verbose, tor_fw_options.help, + tor_fw_options.private_or_port, tor_fw_options.public_or_port, + tor_fw_options.private_dir_port, tor_fw_options.public_dir_port, + tor_fw_options.fetch_public_ip); + } + + if (tor_fw_options.test_commandline) { + return log_commandline_options(argc, argv); + } + + /* At the very least, we require an ORPort; + Given a private ORPort, we can ask for a mapping that matches the port + externally. + */ + if (!tor_fw_options.private_or_port && !tor_fw_options.fetch_public_ip) { + fprintf(stderr, "E: We require an ORPort or fetch_public_ip" + " request!\n"); + usage(); + exit(1); + } else { + /* When we only have one ORPort, internal/external are + set to be the same.*/ + if (!tor_fw_options.public_or_port && tor_fw_options.private_or_port) { + if (tor_fw_options.verbose) + fprintf(stdout, "V: We're setting public_or_port = " + "private_or_port.\n"); + tor_fw_options.public_or_port = tor_fw_options.private_or_port; + } + } + if (!tor_fw_options.private_dir_port) { + if (tor_fw_options.verbose) + fprintf(stdout, "V: We have no DirPort; no hole punching for " + "DirPorts\n"); + + } else { + /* When we only have one DirPort, internal/external are + set to be the same.*/ + if (!tor_fw_options.public_dir_port && tor_fw_options.private_dir_port) { + if (tor_fw_options.verbose) + fprintf(stdout, "V: We're setting public_or_port = " + "private_or_port.\n"); + + tor_fw_options.public_dir_port = tor_fw_options.private_dir_port; + } + } + + if (tor_fw_options.verbose) { + fprintf(stdout, "V: pub or port = %u, priv or port = %u\n" + "V: pub dir port = %u, priv dir port = %u\n", + tor_fw_options.private_or_port, tor_fw_options.public_or_port, + tor_fw_options.private_dir_port, + tor_fw_options.public_dir_port); + } + + // Initalize the various fw-helper backend helpers + r = init_backends(&tor_fw_options, &backend_state); + if (r) + printf("tor-fw-helper: %i NAT traversal helper(s) loaded\n", r); + + if (tor_fw_options.fetch_public_ip) { + tor_fw_fetch_public_ip(&tor_fw_options, &backend_state); + } + + if (tor_fw_options.private_or_port) { + tor_fw_options.internal_port = tor_fw_options.private_or_port; + tor_fw_options.external_port = tor_fw_options.private_or_port; + tor_fw_add_or_port(&tor_fw_options, &backend_state); + } + + if (tor_fw_options.private_dir_port) { + tor_fw_options.internal_port = tor_fw_options.private_dir_port; + tor_fw_options.external_port = tor_fw_options.private_dir_port; + tor_fw_add_dir_port(&tor_fw_options, &backend_state); + } + + r = (((tor_fw_options.nat_pmp_status | tor_fw_options.upnp_status) + |tor_fw_options.public_ip_status)); + if (r > 0) { + fprintf(stdout, "tor-fw-helper: SUCCESS\n"); + } else { + fprintf(stderr, "tor-fw-helper: FAILURE\n"); + } + + exit(r); +} + diff --git a/src/tools/tor-fw-helper/tor-fw-helper.h b/src/tools/tor-fw-helper/tor-fw-helper.h new file mode 100644 index 0000000000..39d852d212 --- /dev/null +++ b/src/tools/tor-fw-helper/tor-fw-helper.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch. + * Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor-fw-helper.h + * \brief The main header for our firewall helper. + **/ + +#ifndef _TOR_FW_HELPER_H +#define _TOR_FW_HELPER_H + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <time.h> + +/** The current version of tor-fw-helper. */ +#define tor_fw_version "0.1" + +/** This is an arbitrary hard limit - We currently have two (NAT-PMP and UPnP). + We're likely going to add the Intel UPnP library but nothing else comes to + mind at the moment. */ +#define MAX_BACKENDS 23 + +/** This is where we store parsed commandline options. */ +typedef struct { + int verbose; + int help; + int test_commandline; + uint16_t private_dir_port; + uint16_t private_or_port; + uint16_t public_dir_port; + uint16_t public_or_port; + uint16_t internal_port; + uint16_t external_port; + int fetch_public_ip; + int nat_pmp_status; + int upnp_status; + int public_ip_status; +} tor_fw_options_t; + +/** This is our main structure that defines our backend helper API; each helper + * must conform to these public methods if it expects to be handled in a + * non-special way. */ +typedef struct tor_fw_backend_t { + const char *name; + size_t state_len; + int (*init)(tor_fw_options_t *options, void *backend_state); + int (*cleanup)(tor_fw_options_t *options, void *backend_state); + int (*fetch_public_ip)(tor_fw_options_t *options, void *backend_state); + int (*add_tcp_mapping)(tor_fw_options_t *options, void *backend_state); +} tor_fw_backend_t; + +#endif + diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index b3cd1db50b..dc84d78496 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -233,5 +233,4 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.2.2.19-alpha" - +#define VERSION "0.2.3.0-alpha-dev" |