diff options
Diffstat (limited to 'src/or')
115 files changed, 18516 insertions, 11127 deletions
diff --git a/src/or/Makefile.nmake b/src/or/Makefile.nmake index 3b627b1d06..2ac98cd372 100644 --- a/src/or/Makefile.nmake +++ b/src/or/Makefile.nmake @@ -1,6 +1,6 @@ all: tor.exe -CFLAGS = /I ..\win32 /I ..\..\..\build-alpha\include /I ..\common \ +CFLAGS = /O2 /MT /I ..\win32 /I ..\..\..\build-alpha\include /I ..\common \ /I ..\ext LIBS = ..\..\..\build-alpha\lib\libevent.lib \ @@ -15,6 +15,7 @@ LIBTOR_OBJECTS = \ buffers.obj \ channel.obj \ channeltls.obj \ + circpathbias.obj \ circuitbuild.obj \ circuitlist.obj \ circuitmux.obj \ @@ -35,6 +36,7 @@ LIBTOR_OBJECTS = \ dirvote.obj \ dns.obj \ dnsserv.obj \ + ext_orport.obj \ fp_pair.obj \ entrynodes.obj \ geoip.obj \ @@ -61,6 +63,7 @@ LIBTOR_OBJECTS = \ routerlist.obj \ routerparse.obj \ routerset.obj \ + scheduler.obj \ statefile.obj \ status.obj \ transports.obj @@ -69,7 +72,7 @@ libtor.lib: $(LIBTOR_OBJECTS) lib $(LIBTOR_OBJECTS) /out:$@ tor.exe: libtor.lib tor_main.obj - $(CC) $(CFLAGS) $(LIBS) libtor.lib ..\common\*.lib tor_main.obj /Fe$@ + $(CC) $(CFLAGS) $(LIBS) libtor.lib ..\common\*.lib ..\ext\*.lib tor_main.obj /Fe$@ clean: - del $(LIBTOR_OBJECTS) *.lib tor.exe + del $(LIBTOR_OBJECTS) tor_main.obj *.lib tor.exe diff --git a/src/or/addressmap.c b/src/or/addressmap.c index 79e4b7c5e2..9c29fb2acb 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ADDRESSMAP_PRIVATE @@ -45,7 +45,7 @@ typedef struct { char *new_address; time_t expires; - ENUM_BF(addressmap_entry_source_t) source:3; + addressmap_entry_source_bitfield_t source:3; unsigned src_wildcard:1; unsigned dst_wildcard:1; short num_resolve_failures; @@ -94,7 +94,7 @@ addressmap_ent_free(void *_ent) tor_free(ent); } -/** Free storage held by a virtaddress_entry_t* entry in <b>ent</b>. */ +/** Free storage held by a virtaddress_entry_t* entry in <b>_ent</b>. */ static void addressmap_virtaddress_ent_free(void *_ent) { @@ -104,11 +104,13 @@ addressmap_virtaddress_ent_free(void *_ent) ent = _ent; tor_free(ent->ipv4_address); + tor_free(ent->ipv6_address); tor_free(ent->hostname_address); tor_free(ent); } -/** Free storage held by a virtaddress_entry_t* entry in <b>ent</b>. */ +/** Remove <b>address</b> (which must map to <b>ent</b>) from the + * virtual address map. */ static void addressmap_virtaddress_remove(const char *address, addressmap_entry_t *ent) { @@ -120,9 +122,11 @@ addressmap_virtaddress_remove(const char *address, addressmap_entry_t *ent) if (ve) { if (!strcmp(address, ve->ipv4_address)) tor_free(ve->ipv4_address); + if (!strcmp(address, ve->ipv6_address)) + tor_free(ve->ipv6_address); if (!strcmp(address, ve->hostname_address)) tor_free(ve->hostname_address); - if (!ve->ipv4_address && !ve->hostname_address) { + if (!ve->ipv4_address && !ve->ipv6_address && !ve->hostname_address) { tor_free(ve); strmap_remove(virtaddress_reversemap, ent->new_address); } @@ -131,7 +135,7 @@ addressmap_virtaddress_remove(const char *address, addressmap_entry_t *ent) } /** Remove <b>ent</b> (which must be mapped to by <b>address</b>) from the - * client address maps. */ + * client address maps, and then free it. */ static void addressmap_ent_remove(const char *address, addressmap_entry_t *ent) { @@ -226,6 +230,8 @@ addressmap_address_should_automap(const char *address, return 0; SMARTLIST_FOREACH_BEGIN(suffix_list, const char *, suffix) { + if (!strcmp(suffix, ".")) + return 1; if (!strcasecmpend(address, suffix)) return 1; } SMARTLIST_FOREACH_END(suffix); @@ -384,13 +390,35 @@ addressmap_rewrite(char *address, size_t maxlen, goto done; } - if (ent && ent->source == ADDRMAPSRC_DNS) { - sa_family_t f; - tor_addr_t tmp; - f = tor_addr_parse(&tmp, ent->new_address); - if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS)) - goto done; - else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) + switch (ent->source) { + case ADDRMAPSRC_DNS: + { + sa_family_t f; + tor_addr_t tmp; + f = tor_addr_parse(&tmp, ent->new_address); + if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS)) + goto done; + else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) + goto done; + } + break; + case ADDRMAPSRC_CONTROLLER: + case ADDRMAPSRC_TORRC: + if (!(flags & AMR_FLAG_USE_MAPADDRESS)) + goto done; + break; + case ADDRMAPSRC_AUTOMAP: + if (!(flags & AMR_FLAG_USE_AUTOMAP)) + goto done; + break; + case ADDRMAPSRC_TRACKEXIT: + if (!(flags & AMR_FLAG_USE_TRACKEXIT)) + goto done; + break; + case ADDRMAPSRC_NONE: + default: + log_warn(LD_BUG, "Unknown addrmap source value %d. Ignoring it.", + (int) ent->source); goto done; } @@ -425,7 +453,7 @@ addressmap_rewrite(char *address, size_t maxlen, if (exit_source_out) *exit_source_out = exit_source; if (expires_out) - *expires_out = TIME_MAX; + *expires_out = expires; return (rewrites > 0); } @@ -449,6 +477,8 @@ addressmap_rewrite_reverse(char *address, size_t maxlen, unsigned flags, return 0; else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) return 0; + /* FFFF we should reverse-map virtual addresses even if we haven't + * enabled DNS cacheing. */ } tor_asprintf(&s, "REVERSE[%s]", address); @@ -496,7 +526,7 @@ addressmap_have_mapping(const char *address, int update_expiry) * equal to <b>address</b>, or any address ending with a period followed by * <b>address</b>. If <b>wildcard_addr</b> and <b>wildcard_new_addr</b> are * both true, the mapping will rewrite addresses that end with - * ".<b>address</b>" into ones that end with ".<b>new_address</b>." + * ".<b>address</b>" into ones that end with ".<b>new_address</b>". * * If <b>new_address</b> is NULL, or <b>new_address</b> is equal to * <b>address</b> and <b>wildcard_addr</b> is equal to @@ -535,9 +565,9 @@ addressmap_register(const char *address, char *new_address, time_t expires, if (expires > 1) { log_info(LD_APP,"Temporary addressmap ('%s' to '%s') not performed, " "since it's already mapped to '%s'", - safe_str_client(address), - safe_str_client(new_address), - safe_str_client(ent->new_address)); + safe_str_client(address), + safe_str_client(new_address), + safe_str_client(ent->new_address)); tor_free(new_address); return; } @@ -670,10 +700,10 @@ client_dns_set_addressmap(entry_connection_t *for_conn, return; /* If address was an IP address already, don't add a mapping. */ if (tor_addr_family(val) == AF_INET) { - if (! for_conn->cache_ipv4_answers) + if (! for_conn->entry_cfg.cache_ipv4_answers) return; } else if (tor_addr_family(val) == AF_INET6) { - if (! for_conn->cache_ipv6_answers) + if (! for_conn->entry_cfg.cache_ipv6_answers) return; } @@ -702,8 +732,8 @@ client_dns_set_reverse_addressmap(entry_connection_t *for_conn, { tor_addr_t tmp_addr; sa_family_t f = tor_addr_parse(&tmp_addr, address); - if ((f == AF_INET && ! for_conn->cache_ipv4_answers) || - (f == AF_INET6 && ! for_conn->cache_ipv6_answers)) + if ((f == AF_INET && ! for_conn->entry_cfg.cache_ipv4_answers) || + (f == AF_INET6 && ! for_conn->entry_cfg.cache_ipv6_answers)) return; } tor_asprintf(&s, "REVERSE[%s]", address); @@ -738,6 +768,12 @@ parse_virtual_addr_network(const char *val, sa_family_t family, const int max_bits = ipv6 ? 40 : 16; virtual_addr_conf_t *conf = ipv6 ? &virtaddr_conf_ipv6 : &virtaddr_conf_ipv4; + if (!val || val[0] == '\0') { + if (msg) + tor_asprintf(msg, "Value not present (%s) after VirtualAddressNetwork%s", + val?"Empty":"NULL", ipv6?"IPv6":""); + return -1; + } if (tor_addr_parse_mask_ports(val, 0, &addr, &bits, NULL, NULL) < 0) { if (msg) tor_asprintf(msg, "Error parsing VirtualAddressNetwork%s %s", @@ -798,7 +834,7 @@ address_is_in_virtual_range(const char *address) /** Return a random address conforming to the virtual address configuration * in <b>conf</b>. */ -/* private */ void +STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, tor_addr_t *addr_out) { uint8_t tmp[4]; @@ -839,8 +875,8 @@ get_random_virtual_addr(const virtual_addr_conf_t *conf, tor_addr_t *addr_out) } /** Return a newly allocated string holding an address of <b>type</b> - * (one of RESOLVED_TYPE_{IPV4|HOSTNAME}) that has not yet been mapped, - * and that is very unlikely to be the address of any real host. + * (one of RESOLVED_TYPE_{IPV4|IPV6|HOSTNAME}) that has not yet been + * mapped, and that is very unlikely to be the address of any real host. * * May return NULL if we have run out of virtual addresses. */ @@ -888,7 +924,7 @@ addressmap_get_virtual_address(int type) /* XXXX This code is to make sure I didn't add an undecorated version * by mistake. I hope it's needless. */ char tmp[TOR_ADDR_BUF_LEN]; - tor_addr_to_str(buf, &addr, sizeof(tmp), 0); + tor_addr_to_str(tmp, &addr, sizeof(tmp), 0); if (strmap_get(addressmap, tmp)) { log_warn(LD_BUG, "%s wasn't in the addressmap, but %s was.", buf, tmp); @@ -945,7 +981,7 @@ addressmap_register_virtual_address(int type, char *new_address) !strcasecmp(new_address, ent->new_address)) { tor_free(new_address); tor_assert(!vent_needs_to_be_added); - return tor_strdup(*addrp); + return *addrp; } else { log_warn(LD_BUG, "Internal confusion: I thought that '%s' was mapped to by " @@ -969,6 +1005,8 @@ addressmap_register_virtual_address(int type, char *new_address) strmap_set(virtaddress_reversemap, new_address, vent); addressmap_register(*addrp, new_address, 2, ADDRMAPSRC_AUTOMAP, 0, 0); + /* FFFF register corresponding reverse mapping. */ + #if 0 { /* Try to catch possible bugs */ diff --git a/src/or/addressmap.h b/src/or/addressmap.h index 40210ee990..ff108df024 100644 --- a/src/or/addressmap.h +++ b/src/or/addressmap.h @@ -1,12 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_ADDRESSMAP_H #define TOR_ADDRESSMAP_H +#include "testsupport.h" + void addressmap_init(void); void addressmap_clear_excluded_trackexithosts(const or_options_t *options); void addressmap_clear_invalid_automaps(const or_options_t *options); @@ -14,8 +16,11 @@ void addressmap_clean(time_t now); void addressmap_clear_configured(void); void addressmap_clear_transient(void); void addressmap_free_all(void); -#define AMR_FLAG_USE_IPV4_DNS (1u<<0) -#define AMR_FLAG_USE_IPV6_DNS (1u<<1) +#define AMR_FLAG_USE_IPV4_DNS (1u<<0) +#define AMR_FLAG_USE_IPV6_DNS (1u<<1) +#define AMR_FLAG_USE_MAPADDRESS (1u<<2) +#define AMR_FLAG_USE_AUTOMAP (1u<<3) +#define AMR_FLAG_USE_TRACKEXIT (1u<<4) int addressmap_rewrite(char *address, size_t maxlen, unsigned flags, time_t *expires_out, addressmap_entry_source_t *exit_source_out); @@ -52,8 +57,8 @@ typedef struct virtual_addr_conf_t { maskbits_t bits; } virtual_addr_conf_t; -void get_random_virtual_addr(const virtual_addr_conf_t *conf, - tor_addr_t *addr_out); +STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, + tor_addr_t *addr_out); #endif #endif diff --git a/src/or/buffers.c b/src/or/buffers.c index ab3346d9b7..be9974418d 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,6 +19,7 @@ #include "connection_or.h" #include "control.h" #include "reasons.h" +#include "ext_orport.h" #include "../common/util.h" #include "../common/torlog.h" #ifdef HAVE_UNISTD_H @@ -54,6 +55,9 @@ * forever. */ +static void socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason); + 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); @@ -63,16 +67,6 @@ static int parse_socks_client(const uint8_t *data, size_t datalen, /* Chunk manipulation functions */ -/** A single chunk on a buffer or in a freelist. */ -typedef struct chunk_t { - struct chunk_t *next; /**< The next chunk on the buffer or freelist. */ - 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[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in - * this chunk. */ -} chunk_t; - #define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0]) /** Return the number of bytes needed to allocate a chunk to hold @@ -109,107 +103,19 @@ chunk_repack(chunk_t *chunk) chunk->data = &chunk->mem[0]; } -#if defined(ENABLE_BUF_FREELISTS) || defined(RUNNING_DOXYGEN) -/** A freelist of chunks. */ -typedef struct chunk_freelist_t { - size_t alloc_size; /**< What size chunks does this freelist hold? */ - int max_length; /**< Never allow more than this number of chunks in the - * freelist. */ - int slack; /**< When trimming the freelist, leave this number of extra - * chunks beyond lowest_length.*/ - int cur_length; /**< How many chunks on the freelist now? */ - int lowest_length; /**< What's the smallest value of cur_length since the - * last time we cleaned this freelist? */ - uint64_t n_alloc; - uint64_t n_free; - uint64_t n_hit; - chunk_t *head; /**< First chunk on the freelist. */ -} chunk_freelist_t; - -/** Macro to help define freelists. */ -#define FL(a,m,s) { a, m, s, 0, 0, 0, 0, 0, NULL } - -/** Static array of freelists, sorted by alloc_len, terminated by an entry - * with alloc_size of 0. */ -static chunk_freelist_t freelists[] = { - FL(4096, 256, 8), FL(8192, 128, 4), FL(16384, 64, 4), FL(32768, 32, 2), - FL(0, 0, 0) -}; -#undef FL -/** How many times have we looked for a chunk of a size that no freelist - * could help with? */ -static uint64_t n_freelist_miss = 0; - -static void assert_freelist_ok(chunk_freelist_t *fl); - -/** Return the freelist to hold chunks of size <b>alloc</b>, or NULL if - * no freelist exists for that size. */ -static INLINE chunk_freelist_t * -get_freelist(size_t alloc) -{ - int i; - for (i=0; (freelists[i].alloc_size <= alloc && - freelists[i].alloc_size); ++i ) { - if (freelists[i].alloc_size == alloc) { - return &freelists[i]; - } - } - return NULL; -} - -/** Deallocate a chunk or put it on a freelist */ -static void -chunk_free_unchecked(chunk_t *chunk) -{ - size_t alloc; - chunk_freelist_t *freelist; - - alloc = CHUNK_ALLOC_SIZE(chunk->memlen); - freelist = get_freelist(alloc); - if (freelist && freelist->cur_length < freelist->max_length) { - chunk->next = freelist->head; - freelist->head = chunk; - ++freelist->cur_length; - } else { - if (freelist) - ++freelist->n_free; - tor_free(chunk); - } -} - -/** Allocate a new chunk with a given allocation size, or get one from the - * freelist. Note that a chunk with allocation size A can actually hold only - * CHUNK_SIZE_WITH_ALLOC(A) bytes in its mem field. */ -static INLINE chunk_t * -chunk_new_with_alloc_size(size_t alloc) -{ - chunk_t *ch; - chunk_freelist_t *freelist; - tor_assert(alloc >= sizeof(chunk_t)); - freelist = get_freelist(alloc); - if (freelist && freelist->head) { - ch = freelist->head; - freelist->head = ch->next; - if (--freelist->cur_length < freelist->lowest_length) - freelist->lowest_length = freelist->cur_length; - ++freelist->n_hit; - } else { - if (freelist) - ++freelist->n_alloc; - else - ++n_freelist_miss; - ch = tor_malloc(alloc); - } - ch->next = NULL; - ch->datalen = 0; - ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); - ch->data = &ch->mem[0]; - return ch; -} -#else +/** Keep track of total size of allocated chunks for consistency asserts */ +static size_t total_bytes_allocated_in_chunks = 0; static void chunk_free_unchecked(chunk_t *chunk) { + if (!chunk) + return; +#ifdef DEBUG_CHUNK_ALLOC + tor_assert(CHUNK_ALLOC_SIZE(chunk->memlen) == chunk->DBG_alloc); +#endif + tor_assert(total_bytes_allocated_in_chunks >= + CHUNK_ALLOC_SIZE(chunk->memlen)); + total_bytes_allocated_in_chunks -= CHUNK_ALLOC_SIZE(chunk->memlen); tor_free(chunk); } static INLINE chunk_t * @@ -219,11 +125,14 @@ chunk_new_with_alloc_size(size_t alloc) ch = tor_malloc(alloc); ch->next = NULL; ch->datalen = 0; +#ifdef DEBUG_CHUNK_ALLOC + ch->DBG_alloc = alloc; +#endif ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); + total_bytes_allocated_in_chunks += alloc; ch->data = &ch->mem[0]; return ch; } -#endif /** Expand <b>chunk</b> until it can hold <b>sz</b> bytes, and return a * new pointer to <b>chunk</b>. Old pointers are no longer valid. */ @@ -231,11 +140,18 @@ static INLINE chunk_t * chunk_grow(chunk_t *chunk, size_t sz) { off_t offset; + size_t memlen_orig = chunk->memlen; tor_assert(sz > chunk->memlen); offset = chunk->data - chunk->mem; chunk = tor_realloc(chunk, CHUNK_ALLOC_SIZE(sz)); chunk->memlen = sz; chunk->data = chunk->mem + offset; +#ifdef DEBUG_CHUNK_ALLOC + tor_assert(chunk->DBG_alloc == CHUNK_ALLOC_SIZE(memlen_orig)); + chunk->DBG_alloc = CHUNK_ALLOC_SIZE(sz); +#endif + total_bytes_allocated_in_chunks += + CHUNK_ALLOC_SIZE(sz) - CHUNK_ALLOC_SIZE(memlen_orig); return chunk; } @@ -259,125 +175,16 @@ preferred_chunk_size(size_t target) return sz; } -/** Remove from the freelists most chunks that have not been used since the - * last call to buf_shrink_freelists(). */ -void -buf_shrink_freelists(int free_all) -{ -#ifdef ENABLE_BUF_FREELISTS - int i; - disable_control_logging(); - for (i = 0; freelists[i].alloc_size; ++i) { - int slack = freelists[i].slack; - assert_freelist_ok(&freelists[i]); - if (free_all || freelists[i].lowest_length > slack) { - int n_to_free = free_all ? freelists[i].cur_length : - (freelists[i].lowest_length - slack); - int n_to_skip = freelists[i].cur_length - n_to_free; - int orig_length = freelists[i].cur_length; - int orig_n_to_free = n_to_free, n_freed=0; - int orig_n_to_skip = n_to_skip; - int new_length = n_to_skip; - chunk_t **chp = &freelists[i].head; - chunk_t *chunk; - while (n_to_skip) { - if (! (*chp)->next) { - log_warn(LD_BUG, "I wanted to skip %d chunks in the freelist for " - "%d-byte chunks, but only found %d. (Length %d)", - orig_n_to_skip, (int)freelists[i].alloc_size, - orig_n_to_skip-n_to_skip, freelists[i].cur_length); - assert_freelist_ok(&freelists[i]); - goto done; - } - // tor_assert((*chp)->next); - chp = &(*chp)->next; - --n_to_skip; - } - chunk = *chp; - *chp = NULL; - while (chunk) { - chunk_t *next = chunk->next; - tor_free(chunk); - chunk = next; - --n_to_free; - ++n_freed; - ++freelists[i].n_free; - } - if (n_to_free) { - log_warn(LD_BUG, "Freelist length for %d-byte chunks may have been " - "messed up somehow.", (int)freelists[i].alloc_size); - log_warn(LD_BUG, "There were %d chunks at the start. I decided to " - "keep %d. I wanted to free %d. I freed %d. I somehow think " - "I have %d left to free.", - freelists[i].cur_length, n_to_skip, orig_n_to_free, - n_freed, n_to_free); - } - // tor_assert(!n_to_free); - freelists[i].cur_length = new_length; - log_info(LD_MM, "Cleaned freelist for %d-byte chunks: original " - "length %d, kept %d, dropped %d.", - (int)freelists[i].alloc_size, orig_length, - orig_n_to_skip, orig_n_to_free); - } - freelists[i].lowest_length = freelists[i].cur_length; - assert_freelist_ok(&freelists[i]); - } - done: - enable_control_logging(); -#else - (void) free_all; -#endif -} - -/** Describe the current status of the freelists at log level <b>severity</b>. - */ -void -buf_dump_freelist_sizes(int severity) -{ -#ifdef ENABLE_BUF_FREELISTS - int i; - tor_log(severity, LD_MM, "====== Buffer freelists:"); - for (i = 0; freelists[i].alloc_size; ++i) { - uint64_t total = ((uint64_t)freelists[i].cur_length) * - freelists[i].alloc_size; - tor_log(severity, LD_MM, - U64_FORMAT" bytes in %d %d-byte chunks ["U64_FORMAT - " misses; "U64_FORMAT" frees; "U64_FORMAT" hits]", - U64_PRINTF_ARG(total), - freelists[i].cur_length, (int)freelists[i].alloc_size, - U64_PRINTF_ARG(freelists[i].n_alloc), - U64_PRINTF_ARG(freelists[i].n_free), - U64_PRINTF_ARG(freelists[i].n_hit)); - } - tor_log(severity, LD_MM, U64_FORMAT" allocations in non-freelist sizes", - U64_PRINTF_ARG(n_freelist_miss)); -#else - (void)severity; -#endif -} - -/** Magic value for buf_t.magic, to catch pointer errors. */ -#define BUFFER_MAGIC 0xB0FFF312u -/** A resizeable buffer, optimized for reading and writing. */ -struct buf_t { - uint32_t magic; /**< Magic cookie for debugging: Must be set to - * BUFFER_MAGIC. */ - size_t datalen; /**< How many bytes is this buffer holding right now? */ - size_t default_chunk_size; /**< Don't allocate any chunks smaller than - * this for this buffer. */ - chunk_t *head; /**< First chunk in the list, or NULL for none. */ - chunk_t *tail; /**< Last chunk in the list, or NULL for none. */ -}; - /** Collapse data from the first N chunks from <b>buf</b> into buf->head, * growing it as necessary, until buf->head has the first <b>bytes</b> bytes * of data from the buffer, or until buf->head has all the data in <b>buf</b>. * * If <b>nulterminate</b> is true, ensure that there is a 0 byte in * buf->head->mem right after all the data. */ -static void +STATIC void buf_pullup(buf_t *buf, size_t bytes, int nulterminate) { + /* XXXX nothing uses nulterminate; remove it. */ chunk_t *dest, *src; size_t capacity; if (!buf->head) @@ -449,14 +256,19 @@ buf_pullup(buf_t *buf, size_t bytes, int nulterminate) check(); } -/** Resize buf so it won't hold extra memory that we haven't been - * using lately. - */ +#ifdef TOR_UNIT_TESTS void -buf_shrink(buf_t *buf) +buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz) { - (void)buf; + if (!buf || !buf->head) { + *cp = NULL; + *sz = 0; + } else { + *cp = buf->head->data; + *sz = buf->head->datalen; + } } +#endif /** Remove the first <b>n</b> bytes from buf. */ static INLINE void @@ -503,6 +315,12 @@ buf_new(void) return buf; } +size_t +buf_get_default_chunk_size(const buf_t *buf) +{ + return buf->default_chunk_size; +} + /** Remove all data from <b>buf</b>. */ void buf_clear(buf_t *buf) @@ -517,8 +335,8 @@ buf_clear(buf_t *buf) } /** Return the number of bytes stored in <b>buf</b> */ -size_t -buf_datalen(const buf_t *buf) +MOCK_IMPL(size_t, +buf_datalen, (const buf_t *buf)) { return buf->datalen; } @@ -530,7 +348,7 @@ buf_allocation(const buf_t *buf) size_t total = 0; const chunk_t *chunk; for (chunk = buf->head; chunk; chunk = chunk->next) { - total += chunk->memlen; + total += CHUNK_ALLOC_SIZE(chunk->memlen); } return total; } @@ -563,6 +381,10 @@ static chunk_t * chunk_copy(const chunk_t *in_chunk) { chunk_t *newch = tor_memdup(in_chunk, CHUNK_ALLOC_SIZE(in_chunk->memlen)); + total_bytes_allocated_in_chunks += CHUNK_ALLOC_SIZE(in_chunk->memlen); +#ifdef DEBUG_CHUNK_ALLOC + newch->DBG_alloc = CHUNK_ALLOC_SIZE(in_chunk->memlen); +#endif newch->next = NULL; if (in_chunk->data) { off_t offset = in_chunk->data - in_chunk->mem; @@ -598,6 +420,7 @@ static chunk_t * buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) { chunk_t *chunk; + struct timeval now; if (CHUNK_ALLOC_SIZE(capacity) < buf->default_chunk_size) { chunk = chunk_new_with_alloc_size(buf->default_chunk_size); } else if (capped && CHUNK_ALLOC_SIZE(capacity) > MAX_CHUNK_ALLOC) { @@ -605,6 +428,10 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) } else { chunk = chunk_new_with_alloc_size(preferred_chunk_size(capacity)); } + + tor_gettimeofday_cached_monotonic(&now); + chunk->inserted_time = (uint32_t)tv_to_msec(&now); + if (buf->tail) { tor_assert(buf->head); buf->tail->next = chunk; @@ -617,6 +444,26 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) return chunk; } +/** Return the age of the oldest chunk in the buffer <b>buf</b>, in + * milliseconds. Requires the current time, in truncated milliseconds since + * the epoch, as its input <b>now</b>. + */ +uint32_t +buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now) +{ + if (buf->head) { + return now - buf->head->inserted_time; + } else { + return 0; + } +} + +size_t +buf_get_total_allocation(void) +{ + return total_bytes_allocated_in_chunks; +} + /** 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, @@ -1294,7 +1141,7 @@ buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) /** Return the first position in <b>buf</b> at which the <b>n</b>-character * string <b>s</b> occurs, or -1 if it does not occur. */ -/*private*/ int +STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n) { buf_pos_t pos; @@ -1702,6 +1549,79 @@ fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req, } #endif +/** The size of the header of an Extended ORPort message: 2 bytes for + * COMMAND, 2 bytes for BODYLEN */ +#define EXT_OR_CMD_HEADER_SIZE 4 + +/** Read <b>buf</b>, which should contain an Extended ORPort message + * from a transport proxy. If well-formed, create and populate + * <b>out</b> with the Extended ORport message. Return 0 if the + * buffer was incomplete, 1 if it was well-formed and -1 if we + * encountered an error while parsing it. */ +int +fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) +{ + char hdr[EXT_OR_CMD_HEADER_SIZE]; + uint16_t len; + + check(); + if (buf->datalen < EXT_OR_CMD_HEADER_SIZE) + return 0; + peek_from_buf(hdr, sizeof(hdr), buf); + len = ntohs(get_uint16(hdr+2)); + if (buf->datalen < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) + return 0; + *out = ext_or_cmd_new(len); + (*out)->cmd = ntohs(get_uint16(hdr)); + (*out)->len = len; + buf_remove_from_front(buf, EXT_OR_CMD_HEADER_SIZE); + fetch_from_buf((*out)->body, len, buf); + return 1; +} + +#ifdef USE_BUFFEREVENTS +/** Read <b>buf</b>, which should contain an Extended ORPort message + * from a transport proxy. If well-formed, create and populate + * <b>out</b> with the Extended ORport message. Return 0 if the + * buffer was incomplete, 1 if it was well-formed and -1 if we + * encountered an error while parsing it. */ +int +fetch_ext_or_command_from_evbuffer(struct evbuffer *buf, ext_or_cmd_t **out) +{ + char hdr[EXT_OR_CMD_HEADER_SIZE]; + uint16_t len; + size_t buf_len = evbuffer_get_length(buf); + + if (buf_len < EXT_OR_CMD_HEADER_SIZE) + return 0; + evbuffer_copyout(buf, hdr, EXT_OR_CMD_HEADER_SIZE); + len = ntohs(get_uint16(hdr+2)); + if (buf_len < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) + return 0; + *out = ext_or_cmd_new(len); + (*out)->cmd = ntohs(get_uint16(hdr)); + (*out)->len = len; + evbuffer_drain(buf, EXT_OR_CMD_HEADER_SIZE); + evbuffer_remove(buf, (*out)->body, len); + return 1; +} +#endif + +/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and + * have Tor send it as error response to <b>req</b>. + */ +static void +socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason) +{ + req->replylen = 10; + memset(req->reply,0,10); + + req->reply[0] = 0x05; // VER field. + req->reply[1] = reason; // REP field. + req->reply[3] = 0x01; // ATYP field. +} + /** 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 @@ -1765,7 +1685,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, } *drain_out = 2u + usernamelen + 1u + passlen; req->got_auth = 1; - *want_length_out = 7; /* Minimal socks5 sommand. */ + *want_length_out = 7; /* Minimal socks5 command. */ return 0; } else if (req->auth_type == SOCKS_USER_PASS) { /* unknown version byte */ @@ -1837,6 +1757,8 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, req->command != SOCKS_COMMAND_RESOLVE && req->command != SOCKS_COMMAND_RESOLVE_PTR) { /* not a connect or resolve or a resolve_ptr? we don't support it. */ + socks_request_set_socks5_error(req,SOCKS5_COMMAND_NOT_SUPPORTED); + log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", req->command); return -1; @@ -1860,6 +1782,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); log_warn(LD_APP, "socks5 IP takes %d bytes, which doesn't fit in %d. " "Rejecting.", @@ -1872,14 +1795,18 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, if (req->command != SOCKS_COMMAND_RESOLVE_PTR && !addressmap_have_mapping(req->address,0)) { log_unsafe_socks_warning(5, req->address, req->port, safe_socks); - if (safe_socks) + if (safe_socks) { + socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); return -1; + } } return 1; } case 3: /* fqdn */ log_debug(LD_APP,"socks5: fqdn address type"); if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " "hostname type. Rejecting."); return -1; @@ -1890,6 +1817,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, return 0; /* not yet */ } if (len+1 > MAX_SOCKS_ADDR_LEN) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); log_warn(LD_APP, "socks5 hostname is %d bytes, which doesn't fit in " "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); @@ -1899,7 +1827,18 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, req->address[len] = 0; req->port = ntohs(get_uint16(data+5+len)); *drain_out = 5+len+2; - if (!tor_strisprint(req->address) || strchr(req->address,'\"')) { + + if (string_is_valid_ipv4_address(req->address) || + string_is_valid_ipv6_address(req->address)) { + log_unsafe_socks_warning(5,req->address,req->port,safe_socks); + + if (safe_socks) { + socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); + return -1; + } + } else if (!string_is_valid_hostname(req->address)) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + log_warn(LD_PROTOCOL, "Your application (using socks5 to port %d) gave Tor " "a malformed hostname: %s. Rejecting the connection.", @@ -1913,6 +1852,8 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, "necessary. This is good.", req->port); return 1; default: /* unsupported */ + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", (int) *(data+3)); return -1; @@ -2348,6 +2289,7 @@ write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state, char *next; size_t old_avail, avail; int over = 0; + do { int need_new_chunk = 0; if (!buf->tail || ! CHUNK_REMAINING_CAPACITY(buf->tail)) { @@ -2510,23 +2452,3 @@ assert_buf_ok(buf_t *buf) } } -#ifdef ENABLE_BUF_FREELISTS -/** Log an error and exit if <b>fl</b> is corrupted. - */ -static void -assert_freelist_ok(chunk_freelist_t *fl) -{ - chunk_t *ch; - int n; - tor_assert(fl->alloc_size > 0); - n = 0; - for (ch = fl->head; ch; ch = ch->next) { - tor_assert(CHUNK_ALLOC_SIZE(ch->memlen) == fl->alloc_size); - ++n; - } - tor_assert(n == fl->cur_length); - tor_assert(n >= fl->lowest_length); - tor_assert(n <= fl->max_length); -} -#endif - diff --git a/src/or/buffers.h b/src/or/buffers.h index c947f0ba98..6d0c68500b 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,19 +12,22 @@ #ifndef TOR_BUFFERS_H #define TOR_BUFFERS_H +#include "testsupport.h" + buf_t *buf_new(void); buf_t *buf_new_with_capacity(size_t size); +size_t buf_get_default_chunk_size(const buf_t *buf); void buf_free(buf_t *buf); void buf_clear(buf_t *buf); buf_t *buf_copy(const buf_t *buf); -void buf_shrink(buf_t *buf); -void buf_shrink_freelists(int free_all); -void buf_dump_freelist_sizes(int severity); -size_t buf_datalen(const buf_t *buf); +MOCK_DECL(size_t, buf_datalen, (const buf_t *buf)); size_t buf_allocation(const buf_t *buf); size_t buf_slack(const buf_t *buf); +uint32_t buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now); +size_t buf_get_total_allocation(void); + int read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, int *socket_error); int read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf); @@ -51,6 +54,8 @@ int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); int peek_buf_has_control0_command(buf_t *buf); +int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out); + #ifdef USE_BUFFEREVENTS int fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out, int linkproto); @@ -66,6 +71,8 @@ int peek_evbuffer_has_control0_command(struct evbuffer *buf); int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, const char *data, size_t data_len, int done); +int fetch_ext_or_command_from_evbuffer(struct evbuffer *buf, + ext_or_cmd_t **out); #endif #ifdef USE_BUFFEREVENTS @@ -75,6 +82,8 @@ int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, #define generic_buffer_get(b,buf,buflen) evbuffer_remove((b),(buf),(buflen)) #define generic_buffer_clear(b) evbuffer_drain((b), evbuffer_get_length((b))) #define generic_buffer_free(b) evbuffer_free((b)) +#define generic_buffer_fetch_ext_or_cmd(b, out) \ + fetch_ext_or_command_from_evbuffer((b), (out)) #else #define generic_buffer_new() buf_new() #define generic_buffer_len(b) buf_datalen((b)) @@ -82,6 +91,8 @@ int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, #define generic_buffer_get(b,buf,buflen) fetch_from_buf((buf),(buflen),(b)) #define generic_buffer_clear(b) buf_clear((b)) #define generic_buffer_free(b) buf_free((b)) +#define generic_buffer_fetch_ext_or_cmd(b, out) \ + fetch_ext_or_command_from_buf((b), (out)) #endif int generic_buffer_set_to_copy(generic_buffer_t **output, const generic_buffer_t *input); @@ -89,7 +100,38 @@ int generic_buffer_set_to_copy(generic_buffer_t **output, void assert_buf_ok(buf_t *buf); #ifdef BUFFERS_PRIVATE -int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); +STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); +STATIC void buf_pullup(buf_t *buf, size_t bytes, int nulterminate); +void buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz); + +#define DEBUG_CHUNK_ALLOC +/** A single chunk on a buffer. */ +typedef struct chunk_t { + struct chunk_t *next; /**< The next chunk on the buffer. */ + 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>. */ +#ifdef DEBUG_CHUNK_ALLOC + size_t DBG_alloc; +#endif + char *data; /**< A pointer to the first byte of data stored in <b>mem</b>. */ + uint32_t inserted_time; /**< Timestamp in truncated ms since epoch + * when this chunk was inserted. */ + char mem[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in + * this chunk. */ +} chunk_t; + +/** Magic value for buf_t.magic, to catch pointer errors. */ +#define BUFFER_MAGIC 0xB0FFF312u +/** A resizeable buffer, optimized for reading and writing. */ +struct buf_t { + uint32_t magic; /**< Magic cookie for debugging: Must be set to + * BUFFER_MAGIC. */ + size_t datalen; /**< How many bytes is this buffer holding right now? */ + size_t default_chunk_size; /**< Don't allocate any chunks smaller than + * this for this buffer. */ + chunk_t *head; /**< First chunk in the list, or NULL for none. */ + chunk_t *tail; /**< Last chunk in the list, or NULL for none. */ +}; #endif #endif diff --git a/src/or/channel.c b/src/or/channel.c index 1270eace7d..bf0387f10e 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,12 +13,16 @@ #define TOR_CHANNEL_INTERNAL_ +/* This one's for stuff only channel.c and the test suite should see */ +#define CHANNEL_PRIVATE_ + #include "or.h" #include "channel.h" #include "channeltls.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" +#include "config.h" #include "connection_or.h" /* For var_cell_free() */ #include "circuitmux.h" #include "entrynodes.h" @@ -28,29 +32,7 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" - -/* Cell queue structure */ - -typedef struct cell_queue_entry_s cell_queue_entry_t; -struct cell_queue_entry_s { - TOR_SIMPLEQ_ENTRY(cell_queue_entry_s) next; - enum { - CELL_QUEUE_FIXED, - CELL_QUEUE_VAR, - CELL_QUEUE_PACKED - } type; - union { - struct { - cell_t *cell; - } fixed; - struct { - var_cell_t *var_cell; - } var; - struct { - packed_cell_t *packed_cell; - } packed; - } u; -}; +#include "scheduler.h" /* Global lists of channels */ @@ -74,6 +56,59 @@ static smartlist_t *finished_listeners = NULL; /* Counter for ID numbers */ static uint64_t n_channels_allocated = 0; +/* + * Channel global byte/cell counters, for statistics and for scheduler high + * /low-water marks. + */ + +/* + * Total number of cells ever given to any channel with the + * channel_write_*_cell() functions. + */ + +static uint64_t n_channel_cells_queued = 0; + +/* + * Total number of cells ever passed to a channel lower layer with the + * write_*_cell() methods. + */ + +static uint64_t n_channel_cells_passed_to_lower_layer = 0; + +/* + * Current number of cells in all channel queues; should be + * n_channel_cells_queued - n_channel_cells_passed_to_lower_layer. + */ + +static uint64_t n_channel_cells_in_queues = 0; + +/* + * Total number of bytes for all cells ever queued to a channel and + * counted in n_channel_cells_queued. + */ + +static uint64_t n_channel_bytes_queued = 0; + +/* + * Total number of bytes for all cells ever passed to a channel lower layer + * and counted in n_channel_cells_passed_to_lower_layer. + */ + +static uint64_t n_channel_bytes_passed_to_lower_layer = 0; + +/* + * Current number of bytes in all channel queues; should be + * n_channel_bytes_queued - n_channel_bytes_passed_to_lower_layer. + */ + +static uint64_t n_channel_bytes_in_queues = 0; + +/* + * Current total estimated queue size *including lower layer queues and + * transmit overhead* + */ + +STATIC uint64_t estimated_total_queue_size = 0; /* Digest->channel map * @@ -95,12 +130,7 @@ typedef struct channel_idmap_entry_s { static INLINE unsigned channel_idmap_hash(const channel_idmap_entry_t *ent) { - const unsigned *a = (const unsigned *)ent->digest; -#if SIZEOF_INT == 4 - return a[0] ^ a[1] ^ a[2] ^ a[3] ^ a[4]; -#elif SIZEOF_INT == 8 - return a[0] ^ a[1]; -#endif + return (unsigned) siphash24g(ent->digest, DIGEST_LEN); } static INLINE int @@ -112,16 +142,21 @@ channel_idmap_eq(const channel_idmap_entry_t *a, HT_PROTOTYPE(channel_idmap, channel_idmap_entry_s, node, channel_idmap_hash, channel_idmap_eq); -HT_GENERATE(channel_idmap, channel_idmap_entry_s, node, channel_idmap_hash, - channel_idmap_eq, 0.5, tor_malloc, tor_realloc, tor_free_); +HT_GENERATE2(channel_idmap, channel_idmap_entry_s, node, channel_idmap_hash, + channel_idmap_eq, 0.5, tor_reallocarray_, tor_free_); static cell_queue_entry_t * cell_queue_entry_dup(cell_queue_entry_t *q); -static void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off); +#if 0 static int cell_queue_entry_is_padding(cell_queue_entry_t *q); +#endif static cell_queue_entry_t * cell_queue_entry_new_fixed(cell_t *cell); static cell_queue_entry_t * cell_queue_entry_new_var(var_cell_t *var_cell); +static int is_destroy_cell(channel_t *chan, + const cell_queue_entry_t *q, circid_t *circid_out); + +static void channel_assert_counter_consistency(void); /* Functions to maintain the digest map */ static void channel_add_to_digest_map(channel_t *chan); @@ -140,6 +175,8 @@ channel_free_list(smartlist_t *channels, int mark_for_close); static void channel_listener_free_list(smartlist_t *channels, int mark_for_close); static void channel_listener_force_free(channel_listener_t *chan_l); +static size_t channel_get_cell_queue_entry_size(channel_t *chan, + cell_queue_entry_t *q); static void channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q); @@ -378,8 +415,7 @@ channel_register(channel_t *chan) smartlist_add(all_channels, chan); /* Is it finished? */ - if (chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR) { + if (CHANNEL_FINISHED(chan)) { /* Put it in the finished list, creating it if necessary */ if (!finished_channels) finished_channels = smartlist_new(); smartlist_add(finished_channels, chan); @@ -388,7 +424,7 @@ channel_register(channel_t *chan) if (!active_channels) active_channels = smartlist_new(); smartlist_add(active_channels, chan); - if (chan->state != CHANNEL_STATE_CLOSING) { + if (!CHANNEL_IS_CLOSING(chan)) { /* It should have a digest set */ if (!tor_digest_is_zero(chan->identity_digest)) { /* Yeah, we're good, add it to the map */ @@ -423,8 +459,7 @@ channel_unregister(channel_t *chan) if (!(chan->registered)) return; /* Is it finished? */ - if (chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR) { + if (CHANNEL_FINISHED(chan)) { /* Get it out of the finished list */ if (finished_channels) smartlist_remove(finished_channels, chan); } else { @@ -440,9 +475,7 @@ channel_unregister(channel_t *chan) /* Should it be in the digest map? */ if (!tor_digest_is_zero(chan->identity_digest) && - !(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR)) { + !(CHANNEL_CONDEMNED(chan))) { /* Remove it */ channel_remove_from_digest_map(chan); } @@ -542,9 +575,7 @@ channel_add_to_digest_map(channel_t *chan) tor_assert(chan); /* Assert that the state makes sense */ - tor_assert(!(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR)); + tor_assert(!CHANNEL_CONDEMNED(chan)); /* Assert that there is a digest */ tor_assert(!tor_digest_is_zero(chan->identity_digest)); @@ -729,10 +760,10 @@ channel_init(channel_t *chan) chan->global_identifier = n_channels_allocated++; /* Init timestamp */ - chan->timestamp_last_added_nonpadding = time(NULL); + chan->timestamp_last_had_circuits = time(NULL); - /* Init next_circ_id */ - chan->next_circ_id = crypto_rand_int(1 << 15); + /* Warn about exhausted circuit IDs no more than hourly. */ + chan->last_warned_circ_ids_exhausted.rate = 3600; /* Initialize queues. */ TOR_SIMPLEQ_INIT(&chan->incoming_queue); @@ -746,6 +777,9 @@ channel_init(channel_t *chan) /* It hasn't been open yet. */ chan->has_been_open = 0; + + /* Scheduler state is idle */ + chan->scheduler_state = SCHED_CHAN_IDLE; } /** @@ -779,8 +813,8 @@ channel_free(channel_t *chan) if (!chan) return; /* It must be closed or errored */ - tor_assert(chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR); + tor_assert(CHANNEL_FINISHED(chan)); + /* It must be deregistered */ tor_assert(!(chan->registered)); @@ -788,6 +822,9 @@ channel_free(channel_t *chan) "Freeing channel " U64_FORMAT " at %p", U64_PRINTF_ARG(chan->global_identifier), chan); + /* Get this one out of the scheduler */ + scheduler_release_channel(chan); + /* * Get rid of cmux policy before we do anything, so cmux policies don't * see channels in weird half-freed states. @@ -803,7 +840,8 @@ channel_free(channel_t *chan) /* Get rid of cmux */ if (chan->cmux) { - circuitmux_detach_all_circuits(chan->cmux); + circuitmux_detach_all_circuits(chan->cmux, NULL); + circuitmux_mark_destroyed_circids_usable(chan->cmux, chan); circuitmux_free(chan->cmux); chan->cmux = NULL; } @@ -862,6 +900,9 @@ channel_force_free(channel_t *chan) "Force-freeing channel " U64_FORMAT " at %p", U64_PRINTF_ARG(chan->global_identifier), chan); + /* Get this one out of the scheduler */ + scheduler_release_channel(chan); + /* * Get rid of cmux policy before we do anything, so cmux policies don't * see channels in weird half-freed states. @@ -987,9 +1028,7 @@ channel_get_cell_handler(channel_t *chan) { tor_assert(chan); - if (chan->state == CHANNEL_STATE_OPENING || - chan->state == CHANNEL_STATE_OPEN || - chan->state == CHANNEL_STATE_MAINT) + if (CHANNEL_CAN_HANDLE_CELLS(chan)) return chan->cell_handler; return NULL; @@ -1007,9 +1046,7 @@ channel_get_var_cell_handler(channel_t *chan) { tor_assert(chan); - if (chan->state == CHANNEL_STATE_OPENING || - chan->state == CHANNEL_STATE_OPEN || - chan->state == CHANNEL_STATE_MAINT) + if (CHANNEL_CAN_HANDLE_CELLS(chan)) return chan->var_cell_handler; return NULL; @@ -1032,9 +1069,7 @@ channel_set_cell_handlers(channel_t *chan, int try_again = 0; tor_assert(chan); - tor_assert(chan->state == CHANNEL_STATE_OPENING || - chan->state == CHANNEL_STATE_OPEN || - chan->state == CHANNEL_STATE_MAINT); + tor_assert(CHANNEL_CAN_HANDLE_CELLS(chan)); log_debug(LD_CHANNEL, "Setting cell_handler callback for channel %p to %p", @@ -1088,9 +1123,8 @@ channel_mark_for_close(channel_t *chan) tor_assert(chan->close != NULL); /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ - if (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR) return; + if (CHANNEL_CONDEMNED(chan)) + return; log_debug(LD_CHANNEL, "Closing channel %p (global ID " U64_FORMAT ") " @@ -1169,9 +1203,8 @@ channel_close_from_lower_layer(channel_t *chan) tor_assert(chan != NULL); /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ - if (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR) return; + if (CHANNEL_CONDEMNED(chan)) + return; log_debug(LD_CHANNEL, "Closing channel %p (global ID " U64_FORMAT ") " @@ -1229,9 +1262,8 @@ channel_close_for_error(channel_t *chan) tor_assert(chan != NULL); /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ - if (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR) return; + if (CHANNEL_CONDEMNED(chan)) + return; log_debug(LD_CHANNEL, "Closing channel %p due to lower-layer error", @@ -1287,18 +1319,16 @@ void channel_closed(channel_t *chan) { tor_assert(chan); - tor_assert(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR); + tor_assert(CHANNEL_CONDEMNED(chan)); /* No-op if already inactive */ - if (chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR) return; + if (CHANNEL_FINISHED(chan)) + return; /* Inform any pending (not attached) circs that they should * give up. */ if (! chan->has_been_open) - circuit_n_chan_done(chan, 0); + circuit_n_chan_done(chan, 0, 0); /* Now close all the attached circuits on it. */ circuit_unlink_all_from_channel(chan, END_CIRC_REASON_CHANNEL_CLOSED); @@ -1356,10 +1386,7 @@ channel_clear_identity_digest(channel_t *chan) "global ID " U64_FORMAT, chan, U64_PRINTF_ARG(chan->global_identifier)); - state_not_in_map = - (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR); + state_not_in_map = CHANNEL_CONDEMNED(chan); if (!state_not_in_map && chan->registered && !tor_digest_is_zero(chan->identity_digest)) @@ -1392,10 +1419,8 @@ channel_set_identity_digest(channel_t *chan, identity_digest ? hex_str(identity_digest, DIGEST_LEN) : "(null)"); - state_not_in_map = - (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR); + state_not_in_map = CHANNEL_CONDEMNED(chan); + was_in_digest_map = !state_not_in_map && chan->registered && @@ -1445,10 +1470,7 @@ channel_clear_remote_end(channel_t *chan) "global ID " U64_FORMAT, chan, U64_PRINTF_ARG(chan->global_identifier)); - state_not_in_map = - (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR); + state_not_in_map = CHANNEL_CONDEMNED(chan); if (!state_not_in_map && chan->registered && !tor_digest_is_zero(chan->identity_digest)) @@ -1484,10 +1506,8 @@ channel_set_remote_end(channel_t *chan, identity_digest ? hex_str(identity_digest, DIGEST_LEN) : "(null)"); - state_not_in_map = - (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR); + state_not_in_map = CHANNEL_CONDEMNED(chan); + was_in_digest_map = !state_not_in_map && chan->registered && @@ -1547,7 +1567,7 @@ cell_queue_entry_dup(cell_queue_entry_t *q) * them) or not (we should free). */ -static void +STATIC void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off) { if (!q) return; @@ -1597,6 +1617,7 @@ cell_queue_entry_free(cell_queue_entry_t *q, int handed_off) tor_free(q); } +#if 0 /** * Check whether a cell queue entry is padding; this is a helper function * for channel_write_cell_queue_entry() @@ -1625,6 +1646,7 @@ cell_queue_entry_is_padding(cell_queue_entry_t *q) return 0; } +#endif /** * Allocate a new cell queue entry for a fixed-size cell @@ -1663,6 +1685,36 @@ cell_queue_entry_new_var(var_cell_t *var_cell) } /** + * Ask how big the cell contained in a cell_queue_entry_t is + */ + +static size_t +channel_get_cell_queue_entry_size(channel_t *chan, cell_queue_entry_t *q) +{ + size_t rv = 0; + + tor_assert(chan); + tor_assert(q); + + switch (q->type) { + case CELL_QUEUE_FIXED: + rv = get_cell_network_size(chan->wide_circ_ids); + break; + case CELL_QUEUE_VAR: + rv = get_var_cell_header_size(chan->wide_circ_ids) + + (q->u.var.var_cell ? q->u.var.var_cell->payload_len : 0); + break; + case CELL_QUEUE_PACKED: + rv = get_cell_network_size(chan->wide_circ_ids); + break; + default: + tor_assert(1); + } + + return rv; +} + +/** * Write to a channel based on a cell_queue_entry_t * * Given a cell_queue_entry_t filled out by the caller, try to send the cell @@ -1674,23 +1726,27 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) { int result = 0, sent = 0; cell_queue_entry_t *tmp = NULL; + size_t cell_bytes; tor_assert(chan); tor_assert(q); /* Assert that the state makes sense for a cell write */ - tor_assert(chan->state == CHANNEL_STATE_OPENING || - chan->state == CHANNEL_STATE_OPEN || - chan->state == CHANNEL_STATE_MAINT); + tor_assert(CHANNEL_CAN_HANDLE_CELLS(chan)); - /* Increment the timestamp unless it's padding */ - if (!cell_queue_entry_is_padding(q)) { - chan->timestamp_last_added_nonpadding = approx_time(); + { + circid_t circ_id; + if (is_destroy_cell(chan, q, &circ_id)) { + channel_note_destroy_not_pending(chan, circ_id); + } } + /* For statistical purposes, figure out how big this cell is */ + cell_bytes = channel_get_cell_queue_entry_size(chan, q); + /* Can we send it right out? If so, try */ if (TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue) && - chan->state == CHANNEL_STATE_OPEN) { + CHANNEL_IS_OPEN(chan)) { /* Pick the right write function for this cell type and save the result */ switch (q->type) { case CELL_QUEUE_FIXED: @@ -1721,6 +1777,13 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) channel_timestamp_drained(chan); /* Update the counter */ ++(chan->n_cells_xmitted); + chan->n_bytes_xmitted += cell_bytes; + /* Update global counters */ + ++n_channel_cells_queued; + ++n_channel_cells_passed_to_lower_layer; + n_channel_bytes_queued += cell_bytes; + n_channel_bytes_passed_to_lower_layer += cell_bytes; + channel_assert_counter_consistency(); } } @@ -1732,8 +1795,16 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) */ tmp = cell_queue_entry_dup(q); TOR_SIMPLEQ_INSERT_TAIL(&chan->outgoing_queue, tmp, next); + /* Update global counters */ + ++n_channel_cells_queued; + ++n_channel_cells_in_queues; + n_channel_bytes_queued += cell_bytes; + n_channel_bytes_in_queues += cell_bytes; + channel_assert_counter_consistency(); + /* Update channel queue size */ + chan->bytes_in_queue += cell_bytes; /* Try to process the queue? */ - if (chan->state == CHANNEL_STATE_OPEN) channel_flush_cells(chan); + if (CHANNEL_IS_OPEN(chan)) channel_flush_cells(chan); } } @@ -1754,7 +1825,7 @@ channel_write_cell(channel_t *chan, cell_t *cell) tor_assert(chan); tor_assert(cell); - if (chan->state == CHANNEL_STATE_CLOSING) { + if (CHANNEL_IS_CLOSING(chan)) { log_debug(LD_CHANNEL, "Discarding cell_t %p on closing channel %p with " "global ID "U64_FORMAT, cell, chan, U64_PRINTF_ARG(chan->global_identifier)); @@ -1770,6 +1841,9 @@ channel_write_cell(channel_t *chan, cell_t *cell) q.type = CELL_QUEUE_FIXED; q.u.fixed.cell = cell; channel_write_cell_queue_entry(chan, &q); + + /* Update the queue size estimate */ + channel_update_xmit_queue_size(chan); } /** @@ -1788,7 +1862,7 @@ channel_write_packed_cell(channel_t *chan, packed_cell_t *packed_cell) tor_assert(chan); tor_assert(packed_cell); - if (chan->state == CHANNEL_STATE_CLOSING) { + if (CHANNEL_IS_CLOSING(chan)) { log_debug(LD_CHANNEL, "Discarding packed_cell_t %p on closing channel %p " "with global ID "U64_FORMAT, packed_cell, chan, U64_PRINTF_ARG(chan->global_identifier)); @@ -1805,6 +1879,9 @@ channel_write_packed_cell(channel_t *chan, packed_cell_t *packed_cell) q.type = CELL_QUEUE_PACKED; q.u.packed.packed_cell = packed_cell; channel_write_cell_queue_entry(chan, &q); + + /* Update the queue size estimate */ + channel_update_xmit_queue_size(chan); } /** @@ -1824,7 +1901,7 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) tor_assert(chan); tor_assert(var_cell); - if (chan->state == CHANNEL_STATE_CLOSING) { + if (CHANNEL_IS_CLOSING(chan)) { log_debug(LD_CHANNEL, "Discarding var_cell_t %p on closing channel %p " "with global ID "U64_FORMAT, var_cell, chan, U64_PRINTF_ARG(chan->global_identifier)); @@ -1841,6 +1918,9 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) q.type = CELL_QUEUE_VAR; q.u.var.var_cell = var_cell; channel_write_cell_queue_entry(chan, &q); + + /* Update the queue size estimate */ + channel_update_xmit_queue_size(chan); } /** @@ -1936,6 +2016,41 @@ channel_change_state(channel_t *chan, channel_state_t to_state) } } + /* + * If we're going to a closed/closing state, we don't need scheduling any + * more; in CHANNEL_STATE_MAINT we can't accept writes. + */ + if (to_state == CHANNEL_STATE_CLOSING || + to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) { + scheduler_release_channel(chan); + } else if (to_state == CHANNEL_STATE_MAINT) { + scheduler_channel_doesnt_want_writes(chan); + } + + /* + * If we're closing, this channel no longer counts toward the global + * estimated queue size; if we're open, it now does. + */ + if ((to_state == CHANNEL_STATE_CLOSING || + to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) && + (from_state == CHANNEL_STATE_OPEN || + from_state == CHANNEL_STATE_MAINT)) { + estimated_total_queue_size -= chan->bytes_in_queue; + } + + /* + * If we're opening, this channel now does count toward the global + * estimated queue size. + */ + if ((to_state == CHANNEL_STATE_OPEN || + to_state == CHANNEL_STATE_MAINT) && + !(from_state == CHANNEL_STATE_OPEN || + from_state == CHANNEL_STATE_MAINT)) { + estimated_total_queue_size += chan->bytes_in_queue; + } + /* Tell circuits if we opened and stuff */ if (to_state == CHANNEL_STATE_OPEN) { channel_do_open_actions(chan); @@ -2051,12 +2166,13 @@ channel_listener_change_state(channel_listener_t *chan_l, #define MAX_CELLS_TO_GET_FROM_CIRCUITS_FOR_UNLIMITED 256 -ssize_t -channel_flush_some_cells(channel_t *chan, ssize_t num_cells) +MOCK_IMPL(ssize_t, +channel_flush_some_cells, (channel_t *chan, ssize_t num_cells)) { unsigned int unlimited = 0; ssize_t flushed = 0; int num_cells_from_circs, clamped_num_cells; + int q_len_before, q_len_after; tor_assert(chan); @@ -2064,7 +2180,7 @@ channel_flush_some_cells(channel_t *chan, ssize_t num_cells) if (!unlimited && num_cells <= flushed) goto done; /* If we aren't in CHANNEL_STATE_OPEN, nothing goes through */ - if (chan->state == CHANNEL_STATE_OPEN) { + if (CHANNEL_IS_OPEN(chan)) { /* Try to flush as much as we can that's already queued */ flushed += channel_flush_some_cells_from_outgoing_queue(chan, (unlimited ? -1 : num_cells - flushed)); @@ -2082,14 +2198,45 @@ channel_flush_some_cells(channel_t *chan, ssize_t num_cells) clamped_num_cells = (int)(num_cells - flushed); } } + + /* + * Keep track of the change in queue size; we have to count cells + * channel_flush_from_first_active_circuit() writes out directly, + * but not double-count ones we might get later in + * channel_flush_some_cells_from_outgoing_queue() + */ + q_len_before = chan_cell_queue_len(&(chan->outgoing_queue)); + /* Try to get more cells from any active circuits */ num_cells_from_circs = channel_flush_from_first_active_circuit( chan, clamped_num_cells); - /* If it claims we got some, process the queue again */ + q_len_after = chan_cell_queue_len(&(chan->outgoing_queue)); + + /* + * If it claims we got some, adjust the flushed counter and consider + * processing the queue again + */ if (num_cells_from_circs > 0) { - flushed += channel_flush_some_cells_from_outgoing_queue(chan, - (unlimited ? -1 : num_cells - flushed)); + /* + * Adjust flushed by the number of cells counted in + * num_cells_from_circs that didn't go to the cell queue. + */ + + if (q_len_after > q_len_before) { + num_cells_from_circs -= (q_len_after - q_len_before); + if (num_cells_from_circs < 0) num_cells_from_circs = 0; + } + + flushed += num_cells_from_circs; + + /* Now process the queue if necessary */ + + if ((q_len_after > q_len_before) && + (unlimited || (flushed < num_cells))) { + flushed += channel_flush_some_cells_from_outgoing_queue(chan, + (unlimited ? -1 : num_cells - flushed)); + } } } } @@ -2112,6 +2259,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, unsigned int unlimited = 0; ssize_t flushed = 0; cell_queue_entry_t *q = NULL; + size_t cell_size; + int free_q = 0, handed_off = 0; tor_assert(chan); tor_assert(chan->write_cell); @@ -2122,11 +2271,15 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, if (!unlimited && num_cells <= flushed) return 0; /* If we aren't in CHANNEL_STATE_OPEN, nothing goes through */ - if (chan->state == CHANNEL_STATE_OPEN) { + if (CHANNEL_IS_OPEN(chan)) { while ((unlimited || num_cells > flushed) && NULL != (q = TOR_SIMPLEQ_FIRST(&chan->outgoing_queue))) { + free_q = 0; + handed_off = 0; if (1) { + /* Figure out how big it is for statistical purposes */ + cell_size = channel_get_cell_queue_entry_size(chan, q); /* * Okay, we have a good queue entry, try to give it to the lower * layer. @@ -2139,8 +2292,9 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, ++flushed; channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); - cell_queue_entry_free(q, 1); - q = NULL; + chan->n_bytes_xmitted += cell_size; + free_q = 1; + handed_off = 1; } /* Else couldn't write it; leave it on the queue */ } else { @@ -2151,8 +2305,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT ").", chan, U64_PRINTF_ARG(chan->global_identifier)); /* Throw it away */ - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } break; case CELL_QUEUE_PACKED: @@ -2162,8 +2316,9 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, ++flushed; channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); - cell_queue_entry_free(q, 1); - q = NULL; + chan->n_bytes_xmitted += cell_size; + free_q = 1; + handed_off = 1; } /* Else couldn't write it; leave it on the queue */ } else { @@ -2174,8 +2329,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT ").", chan, U64_PRINTF_ARG(chan->global_identifier)); /* Throw it away */ - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } break; case CELL_QUEUE_VAR: @@ -2185,8 +2340,9 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, ++flushed; channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); - cell_queue_entry_free(q, 1); - q = NULL; + chan->n_bytes_xmitted += cell_size; + free_q = 1; + handed_off = 1; } /* Else couldn't write it; leave it on the queue */ } else { @@ -2197,8 +2353,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT ").", chan, U64_PRINTF_ARG(chan->global_identifier)); /* Throw it away */ - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } break; default: @@ -2208,12 +2364,32 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT "; ignoring it." " Someone should fix this.", q->type, chan, U64_PRINTF_ARG(chan->global_identifier)); - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } - /* if q got NULLed out, we used it and should remove the queue entry */ - if (!q) TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next); + /* + * if free_q is set, we used it and should remove the queue entry; + * we have to do the free down here so TOR_SIMPLEQ_REMOVE_HEAD isn't + * accessing freed memory + */ + if (free_q) { + TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next); + /* + * ...and we handed a cell off to the lower layer, so we should + * update the counters. + */ + ++n_channel_cells_passed_to_lower_layer; + --n_channel_cells_in_queues; + n_channel_bytes_passed_to_lower_layer += cell_size; + n_channel_bytes_in_queues -= cell_size; + channel_assert_counter_consistency(); + /* Update the channel's queue size too */ + chan->bytes_in_queue -= cell_size; + /* Finally, free q */ + cell_queue_entry_free(q, handed_off); + q = NULL; + } /* No cell removed from list, so we can't go on any further */ else break; } @@ -2225,6 +2401,9 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, channel_timestamp_drained(chan); } + /* Update the estimate queue size */ + channel_update_xmit_queue_size(chan); + return flushed; } @@ -2347,15 +2526,16 @@ void channel_do_open_actions(channel_t *chan) { tor_addr_t remote_addr; - int started_here, not_using = 0; + int started_here; time_t now = time(NULL); + int close_origin_circuits = 0; tor_assert(chan); started_here = channel_is_outgoing(chan); if (started_here) { - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); rep_hist_note_connect_succeeded(chan->identity_digest, now); if (entry_guard_register_connect_status( chan->identity_digest, 1, 0, now) < 0) { @@ -2365,22 +2545,27 @@ channel_do_open_actions(channel_t *chan) log_debug(LD_OR, "New entry guard was reachable, but closing this " "connection so we can retry the earlier entry guards."); - circuit_n_chan_done(chan, 0); - not_using = 1; + close_origin_circuits = 1; } router_set_status(chan->identity_digest, 1); } else { /* only report it to the geoip module if it's not a known router */ if (!router_get_by_id_digest(chan->identity_digest)) { if (channel_get_addr_if_possible(chan, &remote_addr)) { - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &remote_addr, + char *transport_name = NULL; + if (chan->get_transport_name(chan, &transport_name) < 0) + transport_name = NULL; + + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, + &remote_addr, transport_name, now); + tor_free(transport_name); } /* Otherwise the underlying transport can't tell us this, so skip it */ } } - if (!not_using) circuit_n_chan_done(chan, 1); + circuit_n_chan_done(chan, 1, close_origin_circuits); } /** @@ -2451,9 +2636,8 @@ channel_process_cells(channel_t *chan) { cell_queue_entry_t *q; tor_assert(chan); - tor_assert(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_MAINT || - chan->state == CHANNEL_STATE_OPEN); + tor_assert(CHANNEL_IS_CLOSING(chan) || CHANNEL_IS_MAINT(chan) || + CHANNEL_IS_OPEN(chan)); log_debug(LD_CHANNEL, "Processing as many incoming cells as we can for channel %p", @@ -2520,7 +2704,7 @@ channel_queue_cell(channel_t *chan, cell_t *cell) tor_assert(chan); tor_assert(cell); - tor_assert(chan->state == CHANNEL_STATE_OPEN); + tor_assert(CHANNEL_IS_OPEN(chan)); /* Do we need to queue it, or can we just call the handler right away? */ if (!(chan->cell_handler)) need_to_queue = 1; @@ -2530,8 +2714,9 @@ channel_queue_cell(channel_t *chan, cell_t *cell) /* Timestamp for receiving */ channel_timestamp_recv(chan); - /* Update the counter */ + /* Update the counters */ ++(chan->n_cells_recved); + chan->n_bytes_recved += get_cell_network_size(chan->wide_circ_ids); /* If we don't need to queue we can just call cell_handler */ if (!need_to_queue) { @@ -2573,7 +2758,7 @@ channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) tor_assert(chan); tor_assert(var_cell); - tor_assert(chan->state == CHANNEL_STATE_OPEN); + tor_assert(CHANNEL_IS_OPEN(chan)); /* Do we need to queue it, or can we just call the handler right away? */ if (!(chan->var_cell_handler)) need_to_queue = 1; @@ -2585,6 +2770,8 @@ channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) /* Update the counter */ ++(chan->n_cells_recved); + chan->n_bytes_recved += get_var_cell_header_size(chan->wide_circ_ids) + + var_cell->payload_len; /* If we don't need to queue we can just call cell_handler */ if (!need_to_queue) { @@ -2611,6 +2798,67 @@ channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) } } +/** If <b>packed_cell</b> on <b>chan</b> is a destroy cell, then set + * *<b>circid_out</b> to its circuit ID, and return true. Otherwise, return + * false. */ +/* XXXX Move this function. */ +int +packed_cell_is_destroy(channel_t *chan, + const packed_cell_t *packed_cell, + circid_t *circid_out) +{ + if (chan->wide_circ_ids) { + if (packed_cell->body[4] == CELL_DESTROY) { + *circid_out = ntohl(get_uint32(packed_cell->body)); + return 1; + } + } else { + if (packed_cell->body[2] == CELL_DESTROY) { + *circid_out = ntohs(get_uint16(packed_cell->body)); + return 1; + } + } + return 0; +} + +/** + * Assert that the global channel stats counters are internally consistent + */ + +static void +channel_assert_counter_consistency(void) +{ + tor_assert(n_channel_cells_queued == + (n_channel_cells_in_queues + n_channel_cells_passed_to_lower_layer)); + tor_assert(n_channel_bytes_queued == + (n_channel_bytes_in_queues + n_channel_bytes_passed_to_lower_layer)); +} + +/** DOCDOC */ +static int +is_destroy_cell(channel_t *chan, + const cell_queue_entry_t *q, circid_t *circid_out) +{ + *circid_out = 0; + switch (q->type) { + case CELL_QUEUE_FIXED: + if (q->u.fixed.cell->command == CELL_DESTROY) { + *circid_out = q->u.fixed.cell->circ_id; + return 1; + } + break; + case CELL_QUEUE_VAR: + if (q->u.var.var_cell->command == CELL_DESTROY) { + *circid_out = q->u.var.var_cell->circ_id; + return 1; + } + break; + case CELL_QUEUE_PACKED: + return packed_cell_is_destroy(chan, q->u.packed.packed_cell, circid_out); + } + return 0; +} + /** * Send destroy cell on a channel * @@ -2622,25 +2870,25 @@ channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) int channel_send_destroy(circid_t circ_id, channel_t *chan, int reason) { - cell_t cell; - tor_assert(chan); + if (circ_id == 0) { + log_warn(LD_BUG, "Attempted to send a destroy cell for circID 0 " + "on a channel " U64_FORMAT " at %p in state %s (%d)", + U64_PRINTF_ARG(chan->global_identifier), + chan, channel_state_to_string(chan->state), + chan->state); + return 0; + } /* Check to make sure we can send on this channel first */ - if (!(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR)) { - memset(&cell, 0, sizeof(cell_t)); - cell.circ_id = circ_id; - cell.command = CELL_DESTROY; - cell.payload[0] = (uint8_t) reason; + if (!CHANNEL_CONDEMNED(chan) && chan->cmux) { + channel_note_destroy_pending(chan, circ_id); + circuitmux_append_destroy_cell(chan, chan->cmux, circ_id, reason); log_debug(LD_OR, "Sending destroy (circID %u) on channel %p " "(global ID " U64_FORMAT ")", (unsigned)circ_id, chan, U64_PRINTF_ARG(chan->global_identifier)); - - channel_write_cell(chan, &cell); } else { log_warn(LD_BUG, "Someone called channel_send_destroy() for circID %u " @@ -2665,6 +2913,19 @@ channel_dumpstats(int severity) { if (all_channels && smartlist_len(all_channels) > 0) { tor_log(severity, LD_GENERAL, + "Channels have queued " U64_FORMAT " bytes in " U64_FORMAT " cells, " + "and handed " U64_FORMAT " bytes in " U64_FORMAT " cells to the lower" + " layer.", + U64_PRINTF_ARG(n_channel_bytes_queued), + U64_PRINTF_ARG(n_channel_cells_queued), + U64_PRINTF_ARG(n_channel_bytes_passed_to_lower_layer), + U64_PRINTF_ARG(n_channel_cells_passed_to_lower_layer)); + tor_log(severity, LD_GENERAL, + "There are currently " U64_FORMAT " bytes in " U64_FORMAT " cells " + "in channel queues.", + U64_PRINTF_ARG(n_channel_bytes_in_queues), + U64_PRINTF_ARG(n_channel_cells_in_queues)); + tor_log(severity, LD_GENERAL, "Dumping statistics about %d channels:", smartlist_len(all_channels)); tor_log(severity, LD_GENERAL, @@ -2806,13 +3067,11 @@ channel_free_list(smartlist_t *channels, int mark_for_close) channel_state_to_string(curr->state), curr->state); /* Detach circuits early so they can find the channel */ if (curr->cmux) { - circuitmux_detach_all_circuits(curr->cmux); + circuitmux_detach_all_circuits(curr->cmux, NULL); } channel_unregister(curr); if (mark_for_close) { - if (!(curr->state == CHANNEL_STATE_CLOSING || - curr->state == CHANNEL_STATE_CLOSED || - curr->state == CHANNEL_STATE_ERROR)) { + if (!CHANNEL_CONDEMNED(curr)) { channel_mark_for_close(curr); } channel_force_free(curr); @@ -3026,9 +3285,7 @@ channel_get_for_extend(const char *digest, tor_assert(tor_memeq(chan->identity_digest, digest, DIGEST_LEN)); - if (chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR) + if (CHANNEL_CONDEMNED(chan)) continue; /* Never return a channel on which the other end appears to be @@ -3038,7 +3295,7 @@ channel_get_for_extend(const char *digest, } /* Never return a non-open connection. */ - if (chan->state != CHANNEL_STATE_OPEN) { + if (!CHANNEL_IS_OPEN(chan)) { /* If the address matches, don't launch a new connection for this * circuit. */ if (channel_matches_target_addr_for_extend(chan, target_addr)) @@ -3138,7 +3395,7 @@ channel_listener_describe_transport(channel_listener_t *chan_l) /** * Return the number of entries in <b>queue</b> */ -static int +STATIC int chan_cell_queue_len(const chan_cell_queue_t *queue) { int r = 0; @@ -3154,8 +3411,8 @@ chan_cell_queue_len(const chan_cell_queue_t *queue) * Dump statistics for one channel to the log */ -void -channel_dump_statistics(channel_t *chan, int severity) +MOCK_IMPL(void, +channel_dump_statistics, (channel_t *chan, int severity)) { double avg, interval, age; time_t now = time(NULL); @@ -3224,9 +3481,9 @@ channel_dump_statistics(channel_t *chan, int severity) " is %s, and gives a canonical description of \"%s\" and an " "actual description of \"%s\"", U64_PRINTF_ARG(chan->global_identifier), - remote_addr_str, - channel_get_canonical_remote_descr(chan), - actual); + safe_str(remote_addr_str), + safe_str(channel_get_canonical_remote_descr(chan)), + safe_str(actual)); tor_free(remote_addr_str); tor_free(actual); } else { @@ -3298,7 +3555,7 @@ channel_dump_statistics(channel_t *chan, int severity) U64_PRINTF_ARG(chan->timestamp_recv), U64_PRINTF_ARG(now - chan->timestamp_recv)); tor_log(severity, LD_GENERAL, - " * Channel " U64_FORMAT " last trasmitted a cell " + " * Channel " U64_FORMAT " last transmitted a cell " "at " U64_FORMAT " (" U64_FORMAT " seconds ago)", U64_PRINTF_ARG(chan->global_identifier), U64_PRINTF_ARG(chan->timestamp_xmit), @@ -3307,12 +3564,22 @@ channel_dump_statistics(channel_t *chan, int severity) /* Describe counters and rates */ tor_log(severity, LD_GENERAL, " * Channel " U64_FORMAT " has received " - U64_FORMAT " cells and transmitted " U64_FORMAT, + U64_FORMAT " bytes in " U64_FORMAT " cells and transmitted " + U64_FORMAT " bytes in " U64_FORMAT " cells", U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->n_bytes_recved), U64_PRINTF_ARG(chan->n_cells_recved), + U64_PRINTF_ARG(chan->n_bytes_xmitted), U64_PRINTF_ARG(chan->n_cells_xmitted)); if (now > chan->timestamp_created && chan->timestamp_created > 0) { + if (chan->n_bytes_recved > 0) { + avg = (double)(chan->n_bytes_recved) / age; + tor_log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "bytes received per second", + U64_PRINTF_ARG(chan->global_identifier), avg); + } if (chan->n_cells_recved > 0) { avg = (double)(chan->n_cells_recved) / age; if (avg >= 1.0) { @@ -3328,6 +3595,13 @@ channel_dump_statistics(channel_t *chan, int severity) U64_PRINTF_ARG(chan->global_identifier), interval); } } + if (chan->n_bytes_xmitted > 0) { + avg = (double)(chan->n_bytes_xmitted) / age; + tor_log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "bytes transmitted per second", + U64_PRINTF_ARG(chan->global_identifier), avg); + } if (chan->n_cells_xmitted > 0) { avg = (double)(chan->n_cells_xmitted) / age; if (avg >= 1.0) { @@ -3698,6 +3972,23 @@ channel_mark_local(channel_t *chan) } /** + * Mark a channel as remote + * + * This internal-only function should be called by the lower layer if the + * channel is not to a local address but has previously been marked local. + * See channel_is_local() above or the description of the is_local bit in + * channel.h + */ + +void +channel_mark_remote(channel_t *chan) +{ + tor_assert(chan); + + chan->is_local = 0; +} + +/** * Test outgoing flag * * This function gets the outgoing flag; this is the inverse of the incoming @@ -3728,6 +4019,50 @@ channel_mark_outgoing(channel_t *chan) chan->is_incoming = 0; } +/************************ + * Flow control queries * + ***********************/ + +/* + * Get the latest estimate for the total queue size of all open channels + */ + +uint64_t +channel_get_global_queue_estimate(void) +{ + return estimated_total_queue_size; +} + +/* + * Estimate the number of writeable cells + * + * Ask the lower layer for an estimate of how many cells it can accept, and + * then subtract the length of our outgoing_queue, if any, to produce an + * estimate of the number of cells this channel can accept for writes. + */ + +int +channel_num_cells_writeable(channel_t *chan) +{ + int result; + + tor_assert(chan); + tor_assert(chan->num_cells_writeable); + + if (chan->state == CHANNEL_STATE_OPEN) { + /* Query lower layer */ + result = chan->num_cells_writeable(chan); + /* Subtract cell queue length, if any */ + result -= chan_cell_queue_len(&chan->outgoing_queue); + if (result < 0) result = 0; + } else { + /* No cells are writeable in any other state */ + result = 0; + } + + return result; +} + /********************* * Timestamp updates * ********************/ @@ -4130,3 +4465,87 @@ channel_set_circid_type(channel_t *chan, } } +/** + * Update the estimated number of bytes queued to transmit for this channel, + * and notify the scheduler. The estimate includes both the channel queue and + * the queue size reported by the lower layer, and an overhead estimate + * optionally provided by the lower layer. + */ + +void +channel_update_xmit_queue_size(channel_t *chan) +{ + uint64_t queued, adj; + double overhead; + + tor_assert(chan); + tor_assert(chan->num_bytes_queued); + + /* + * First, get the number of bytes we have queued without factoring in + * lower-layer overhead. + */ + queued = chan->num_bytes_queued(chan) + chan->bytes_in_queue; + /* Next, adjust by the overhead factor, if any is available */ + if (chan->get_overhead_estimate) { + overhead = chan->get_overhead_estimate(chan); + if (overhead >= 1.0f) { + queued *= overhead; + } else { + /* Ignore silly overhead factors */ + log_notice(LD_CHANNEL, "Ignoring silly overhead factor %f", overhead); + } + } + + /* Now, compare to the previous estimate */ + if (queued > chan->bytes_queued_for_xmit) { + adj = queued - chan->bytes_queued_for_xmit; + log_debug(LD_CHANNEL, + "Increasing queue size for channel " U64_FORMAT " by " U64_FORMAT + " from " U64_FORMAT " to " U64_FORMAT, + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(adj), + U64_PRINTF_ARG(chan->bytes_queued_for_xmit), + U64_PRINTF_ARG(queued)); + /* Update the channel's estimate */ + chan->bytes_queued_for_xmit = queued; + + /* Update the global queue size estimate if appropriate */ + if (chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT) { + estimated_total_queue_size += adj; + log_debug(LD_CHANNEL, + "Increasing global queue size by " U64_FORMAT " for channel " + U64_FORMAT ", new size is " U64_FORMAT, + U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(estimated_total_queue_size)); + /* Tell the scheduler we're increasing the queue size */ + scheduler_adjust_queue_size(chan, 1, adj); + } + } else if (queued < chan->bytes_queued_for_xmit) { + adj = chan->bytes_queued_for_xmit - queued; + log_debug(LD_CHANNEL, + "Decreasing queue size for channel " U64_FORMAT " by " U64_FORMAT + " from " U64_FORMAT " to " U64_FORMAT, + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(adj), + U64_PRINTF_ARG(chan->bytes_queued_for_xmit), + U64_PRINTF_ARG(queued)); + /* Update the channel's estimate */ + chan->bytes_queued_for_xmit = queued; + + /* Update the global queue size estimate if appropriate */ + if (chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT) { + estimated_total_queue_size -= adj; + log_debug(LD_CHANNEL, + "Decreasing global queue size by " U64_FORMAT " for channel " + U64_FORMAT ", new size is " U64_FORMAT, + U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(estimated_total_queue_size)); + /* Tell the scheduler we're decreasing the queue size */ + scheduler_adjust_queue_size(chan, -1, adj); + } + } +} + diff --git a/src/or/channel.h b/src/or/channel.h index 29ba40e326..ecc2a092e4 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,7 +10,6 @@ #define TOR_CHANNEL_H #include "or.h" -#include "tor_queue.h" #include "circuitmux.h" /* Channel handler function pointer typedefs */ @@ -22,7 +21,7 @@ struct cell_queue_entry_s; TOR_SIMPLEQ_HEAD(chan_cell_queue, cell_queue_entry_s) incoming_queue; typedef struct chan_cell_queue chan_cell_queue_t; -/* +/** * Channel struct; see the channel_t typedef in or.h. A channel is an * abstract interface for the OR-to-OR connection, similar to connection_or_t, * but without the strong coupling to the underlying TLS implementation. They @@ -32,18 +31,18 @@ typedef struct chan_cell_queue chan_cell_queue_t; */ struct channel_s { - /* Magic number for type-checking cast macros */ + /** Magic number for type-checking cast macros */ uint32_t magic; - /* Current channel state */ + /** Current channel state */ channel_state_t state; - /* Globally unique ID number for a channel over the lifetime of a Tor + /** Globally unique ID number for a channel over the lifetime of a Tor * process. */ uint64_t global_identifier; - /* Should we expect to see this channel in the channel lists? */ + /** Should we expect to see this channel in the channel lists? */ unsigned char registered:1; /** has this channel ever been open? */ @@ -58,27 +57,58 @@ struct channel_s { CHANNEL_CLOSE_FOR_ERROR } reason_for_closing; - /* Timestamps for both cell channels and listeners */ + /** State variable for use by the scheduler */ + enum { + /* + * The channel is not open, or it has a full output buffer but no queued + * cells. + */ + SCHED_CHAN_IDLE = 0, + /* + * The channel has space on its output buffer to write, but no queued + * cells. + */ + SCHED_CHAN_WAITING_FOR_CELLS, + /* + * The scheduler has queued cells but no output buffer space to write. + */ + SCHED_CHAN_WAITING_TO_WRITE, + /* + * The scheduler has both queued cells and output buffer space, and is + * eligible for the scheduler loop. + */ + SCHED_CHAN_PENDING + } scheduler_state; + + /** Heap index for use by the scheduler */ + int sched_heap_idx; + + /** Timestamps for both cell channels and listeners */ time_t timestamp_created; /* Channel created */ time_t timestamp_active; /* Any activity */ /* Methods implemented by the lower layer */ - /* Free a channel */ + /** Free a channel */ void (*free)(channel_t *); - /* Close an open channel */ + /** Close an open channel */ void (*close)(channel_t *); - /* Describe the transport subclass for this channel */ + /** Describe the transport subclass for this channel */ const char * (*describe_transport)(channel_t *); - /* Optional method to dump transport-specific statistics on the channel */ + /** Optional method to dump transport-specific statistics on the channel */ void (*dumpstats)(channel_t *, int); - /* Registered handlers for incoming cells */ + /** Registered handlers for incoming cells */ channel_cell_handler_fn_ptr cell_handler; channel_var_cell_handler_fn_ptr var_cell_handler; /* Methods implemented by the lower layer */ + /** + * Ask the lower layer for an estimate of the average overhead for + * transmissions on this channel. + */ + double (*get_overhead_estimate)(channel_t *); /* * Ask the underlying transport what the remote endpoint address is, in * a tor_addr_t. This is optional and subclasses may leave this NULL. @@ -87,79 +117,78 @@ struct channel_s { * available. */ int (*get_remote_addr)(channel_t *, tor_addr_t *); + int (*get_transport_name)(channel_t *chan, char **transport_out); + #define GRD_FLAG_ORIGINAL 1 #define GRD_FLAG_ADDR_ONLY 2 - /* + /** * Get a text description of the remote endpoint; canonicalized if the flag * GRD_FLAG_ORIGINAL is not set, or the one we originally connected * to/received from if it is. If GRD_FLAG_ADDR_ONLY is set, we return only * the original address. */ const char * (*get_remote_descr)(channel_t *, int); - /* Check if the lower layer has queued writes */ + /** Check if the lower layer has queued writes */ int (*has_queued_writes)(channel_t *); - /* + /** * If the second param is zero, ask the lower layer if this is * 'canonical', for a transport-specific definition of canonical; if * it is 1, ask if the answer to the preceding query is safe to rely * on. */ int (*is_canonical)(channel_t *, int); - /* Check if this channel matches a specified extend_info_t */ + /** Check if this channel matches a specified extend_info_t */ int (*matches_extend_info)(channel_t *, extend_info_t *); - /* Check if this channel matches a target address when extending */ + /** Check if this channel matches a target address when extending */ int (*matches_target)(channel_t *, const tor_addr_t *); + /* Ask the lower layer how many bytes it has queued but not yet sent */ + size_t (*num_bytes_queued)(channel_t *); + /* Ask the lower layer how many cells can be written */ + int (*num_cells_writeable)(channel_t *); /* Write a cell to an open channel */ int (*write_cell)(channel_t *, cell_t *); - /* Write a packed cell to an open channel */ + /** Write a packed cell to an open channel */ int (*write_packed_cell)(channel_t *, packed_cell_t *); - /* Write a variable-length cell to an open channel */ + /** Write a variable-length cell to an open channel */ int (*write_var_cell)(channel_t *, var_cell_t *); - /* + /** * Hash of the public RSA key for the other side's identity key, or * zeroes if the other side hasn't shown us a valid identity key. */ char identity_digest[DIGEST_LEN]; - /* Nickname of the OR on the other side, or NULL if none. */ + /** Nickname of the OR on the other side, or NULL if none. */ char *nickname; - /* + /** * Linked list of channels with the same identity digest, for the * digest->channel map */ TOR_LIST_ENTRY(channel_s) next_with_same_id; - /* List of incoming cells to handle */ + /** List of incoming cells to handle */ chan_cell_queue_t incoming_queue; - /* List of queued outgoing cells */ + /** List of queued outgoing cells */ chan_cell_queue_t outgoing_queue; - /* Circuit mux for circuits sending on this channel */ + /** Circuit mux for circuits sending on this channel */ circuitmux_t *cmux; - /* Circuit ID generation stuff for use by circuitbuild.c */ + /** Circuit ID generation stuff for use by circuitbuild.c */ - /* + /** * When we send CREATE cells along this connection, which half of the * space should we use? */ - ENUM_BF(circ_id_type_t) circ_id_type:2; + circ_id_type_bitfield_t circ_id_type:2; /** DOCDOC*/ unsigned wide_circ_ids:1; - /** Have we logged a warning about circID exhaustion on this channel? */ - unsigned warned_circ_ids_exhausted:1; - /* - * Which circ_id do we try to use next on this connection? This is - * always in the range 0..1<<15-1. - */ - circid_t next_circ_id; - /* For how many circuits are we n_chan? What about p_chan? */ + /** For how many circuits are we n_chan? What about p_chan? */ unsigned int num_n_circuits, num_p_circuits; - /* + /** * True iff this channel shouldn't get any new circs attached to it, * because the connection is too old, or because there's a better one. * More generally, this flag is used to note an unhealthy connection; @@ -183,14 +212,20 @@ struct channel_s { */ unsigned int is_local:1; + /** Have we logged a warning about circID exhaustion on this channel? + * If so, when? */ + ratelim_t last_warned_circ_ids_exhausted; + /** Channel timestamps for cell channels */ time_t timestamp_client; /* Client used this, according to relay.c */ time_t timestamp_drained; /* Output queue empty */ time_t timestamp_recv; /* Cell received from lower layer */ time_t timestamp_xmit; /* Cell sent to lower layer */ - /* Timestamp for relay.c */ - time_t timestamp_last_added_nonpadding; + /** Timestamp for run_connection_housekeeping(). We update this once a + * second when we run housekeeping and find a circuit on this channel, and + * whenever we add a circuit to the channel. */ + time_t timestamp_last_had_circuits; /** Unique ID for measuring direct network status requests;vtunneled ones * come over a circuit_t, which has a dirreq_id field as well, but is a @@ -198,8 +233,16 @@ struct channel_s { uint64_t dirreq_id; /** Channel counters for cell channels */ - uint64_t n_cells_recved; - uint64_t n_cells_xmitted; + uint64_t n_cells_recved, n_bytes_recved; + uint64_t n_cells_xmitted, n_bytes_xmitted; + + /** Our current contribution to the scheduler's total xmit queue */ + uint64_t bytes_queued_for_xmit; + + /** Number of bytes in this channel's cell queue; does not include + * lower-layer queueing. + */ + uint64_t bytes_in_queue; }; struct channel_listener_s { @@ -211,7 +254,7 @@ struct channel_listener_s { */ uint64_t global_identifier; - /* Should we expect to see this channel in the channel lists? */ + /** Should we expect to see this channel in the channel lists? */ unsigned char registered:1; /** Why did we close? @@ -223,31 +266,31 @@ struct channel_listener_s { CHANNEL_LISTENER_CLOSE_FOR_ERROR } reason_for_closing; - /* Timestamps for both cell channels and listeners */ + /** Timestamps for both cell channels and listeners */ time_t timestamp_created; /* Channel created */ time_t timestamp_active; /* Any activity */ /* Methods implemented by the lower layer */ - /* Free a channel */ + /** Free a channel */ void (*free)(channel_listener_t *); - /* Close an open channel */ + /** Close an open channel */ void (*close)(channel_listener_t *); - /* Describe the transport subclass for this channel */ + /** Describe the transport subclass for this channel */ const char * (*describe_transport)(channel_listener_t *); - /* Optional method to dump transport-specific statistics on the channel */ + /** Optional method to dump transport-specific statistics on the channel */ void (*dumpstats)(channel_listener_t *, int); - /* Registered listen handler to call on incoming connection */ + /** Registered listen handler to call on incoming connection */ channel_listener_fn_ptr listener; - /* List of pending incoming connections */ + /** List of pending incoming connections */ smartlist_t *incoming_list; - /* Timestamps for listeners */ + /** Timestamps for listeners */ time_t timestamp_accepted; - /* Counters for listeners */ + /** Counters for listeners */ uint64_t n_accepted; }; @@ -311,6 +354,36 @@ void channel_set_cmux_policy_everywhere(circuitmux_policy_t *pol); #ifdef TOR_CHANNEL_INTERNAL_ +#ifdef CHANNEL_PRIVATE_ +/* Cell queue structure (here rather than channel.c for test suite use) */ + +typedef struct cell_queue_entry_s cell_queue_entry_t; +struct cell_queue_entry_s { + TOR_SIMPLEQ_ENTRY(cell_queue_entry_s) next; + enum { + CELL_QUEUE_FIXED, + CELL_QUEUE_VAR, + CELL_QUEUE_PACKED + } type; + union { + struct { + cell_t *cell; + } fixed; + struct { + var_cell_t *var_cell; + } var; + struct { + packed_cell_t *packed_cell; + } packed; + } u; +}; + +/* Cell queue functions for benefit of test suite */ +STATIC int chan_cell_queue_len(const chan_cell_queue_t *queue); + +STATIC void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off); +#endif + /* Channel operations for subclasses and internal use only */ /* Initialize a newly allocated channel - do this first in subclass @@ -349,6 +422,7 @@ void channel_clear_remote_end(channel_t *chan); void channel_mark_local(channel_t *chan); void channel_mark_incoming(channel_t *chan); void channel_mark_outgoing(channel_t *chan); +void channel_mark_remote(channel_t *chan); void channel_set_identity_digest(channel_t *chan, const char *identity_digest); void channel_set_remote_end(channel_t *chan, @@ -383,7 +457,8 @@ void channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell); void channel_flush_cells(channel_t *chan); /* Request from lower layer for more cells if available */ -ssize_t channel_flush_some_cells(channel_t *chan, ssize_t num_cells); +MOCK_DECL(ssize_t, channel_flush_some_cells, + (channel_t *chan, ssize_t num_cells)); /* Query if data available on this channel */ int channel_more_to_flush(channel_t *chan); @@ -430,11 +505,44 @@ channel_t * channel_find_by_remote_digest(const char *identity_digest); channel_t * channel_next_with_digest(channel_t *chan); /* + * Helper macros to lookup state of given channel. + */ + +#define CHANNEL_IS_CLOSED(chan) (channel_is_in_state((chan), \ + CHANNEL_STATE_CLOSED)) +#define CHANNEL_IS_OPENING(chan) (channel_is_in_state((chan), \ + CHANNEL_STATE_OPENING)) +#define CHANNEL_IS_OPEN(chan) (channel_is_in_state((chan), \ + CHANNEL_STATE_OPEN)) +#define CHANNEL_IS_MAINT(chan) (channel_is_in_state((chan), \ + CHANNEL_STATE_MAINT)) +#define CHANNEL_IS_CLOSING(chan) (channel_is_in_state((chan), \ + CHANNEL_STATE_CLOSING)) +#define CHANNEL_IS_ERROR(chan) (channel_is_in_state((chan), \ + CHANNEL_STATE_ERROR)) + +#define CHANNEL_FINISHED(chan) (CHANNEL_IS_CLOSED(chan) || \ + CHANNEL_IS_ERROR(chan)) + +#define CHANNEL_CONDEMNED(chan) (CHANNEL_IS_CLOSING(chan) || \ + CHANNEL_FINISHED(chan)) + +#define CHANNEL_CAN_HANDLE_CELLS(chan) (CHANNEL_IS_OPENING(chan) || \ + CHANNEL_IS_OPEN(chan) || \ + CHANNEL_IS_MAINT(chan)) + +static INLINE int +channel_is_in_state(channel_t *chan, channel_state_t state) +{ + return chan->state == state; +} + +/* * Metadata queries/updates */ const char * channel_describe_transport(channel_t *chan); -void channel_dump_statistics(channel_t *chan, int severity); +MOCK_DECL(void, channel_dump_statistics, (channel_t *chan, int severity)); void channel_dump_transport_statistics(channel_t *chan, int severity); const char * channel_get_actual_remote_descr(channel_t *chan); const char * channel_get_actual_remote_address(channel_t *chan); @@ -457,6 +565,7 @@ unsigned int channel_num_circuits(channel_t *chan); void channel_set_circid_type(channel_t *chan, crypto_pk_t *identity_rcvd, int consider_identity); void channel_timestamp_client(channel_t *chan); +void channel_update_xmit_queue_size(channel_t *chan); const char * channel_listener_describe_transport(channel_listener_t *chan_l); void channel_listener_dump_statistics(channel_listener_t *chan_l, @@ -464,6 +573,10 @@ void channel_listener_dump_statistics(channel_listener_t *chan_l, void channel_listener_dump_transport_statistics(channel_listener_t *chan_l, int severity); +/* Flow control queries */ +uint64_t channel_get_global_queue_estimate(void); +int channel_num_cells_writeable(channel_t *chan); + /* Timestamp queries */ time_t channel_when_created(channel_t *chan); time_t channel_when_last_active(channel_t *chan); @@ -482,5 +595,9 @@ uint64_t channel_count_xmitted(channel_t *chan); uint64_t channel_listener_count_accepted(channel_listener_t *chan_l); +int packed_cell_is_destroy(channel_t *chan, + const packed_cell_t *packed_cell, + circid_t *circid_out); + #endif diff --git a/src/or/channeltls.c b/src/or/channeltls.c index d5428c1abd..1cf697ccc5 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -23,8 +23,10 @@ #include "connection_or.h" #include "control.h" #include "relay.h" +#include "rephist.h" #include "router.h" #include "routerlist.h" +#include "scheduler.h" /** How many CELL_PADDING cells have we received, ever? */ uint64_t stats_n_padding_cells_processed = 0; @@ -54,8 +56,11 @@ static void channel_tls_common_init(channel_tls_t *tlschan); static void channel_tls_close_method(channel_t *chan); static const char * channel_tls_describe_transport_method(channel_t *chan); static void channel_tls_free_method(channel_t *chan); +static double channel_tls_get_overhead_estimate_method(channel_t *chan); static int channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out); +static int +channel_tls_get_transport_name_method(channel_t *chan, char **transport_out); static const char * channel_tls_get_remote_descr_method(channel_t *chan, int flags); static int channel_tls_has_queued_writes_method(channel_t *chan); @@ -65,6 +70,8 @@ channel_tls_matches_extend_info_method(channel_t *chan, extend_info_t *extend_info); static int channel_tls_matches_target_method(channel_t *chan, const tor_addr_t *target); +static int channel_tls_num_cells_writeable_method(channel_t *chan); +static size_t channel_tls_num_bytes_queued_method(channel_t *chan); static int channel_tls_write_cell_method(channel_t *chan, cell_t *cell); static int channel_tls_write_packed_cell_method(channel_t *chan, @@ -114,12 +121,16 @@ channel_tls_common_init(channel_tls_t *tlschan) chan->close = channel_tls_close_method; chan->describe_transport = channel_tls_describe_transport_method; chan->free = channel_tls_free_method; + chan->get_overhead_estimate = channel_tls_get_overhead_estimate_method; chan->get_remote_addr = channel_tls_get_remote_addr_method; chan->get_remote_descr = channel_tls_get_remote_descr_method; + chan->get_transport_name = channel_tls_get_transport_name_method; chan->has_queued_writes = channel_tls_has_queued_writes_method; chan->is_canonical = channel_tls_is_canonical_method; chan->matches_extend_info = channel_tls_matches_extend_info_method; chan->matches_target = channel_tls_matches_target_method; + chan->num_bytes_queued = channel_tls_num_bytes_queued_method; + chan->num_cells_writeable = channel_tls_num_cells_writeable_method; chan->write_cell = channel_tls_write_cell_method; chan->write_packed_cell = channel_tls_write_packed_cell_method; chan->write_var_cell = channel_tls_write_var_cell_method; @@ -153,7 +164,18 @@ channel_tls_connect(const tor_addr_t *addr, uint16_t port, tlschan, U64_PRINTF_ARG(chan->global_identifier)); - if (is_local_addr(addr)) channel_mark_local(chan); + if (is_local_addr(addr)) { + log_debug(LD_CHANNEL, + "Marking new outgoing channel " U64_FORMAT " at %p as local", + U64_PRINTF_ARG(chan->global_identifier), chan); + channel_mark_local(chan); + } else { + log_debug(LD_CHANNEL, + "Marking new outgoing channel " U64_FORMAT " at %p as remote", + U64_PRINTF_ARG(chan->global_identifier), chan); + channel_mark_remote(chan); + } + channel_mark_outgoing(chan); /* Set up or_connection stuff */ @@ -283,11 +305,22 @@ channel_tls_handle_incoming(or_connection_t *orconn) tlschan->conn = orconn; orconn->chan = tlschan; - if (is_local_addr(&(TO_CONN(orconn)->addr))) channel_mark_local(chan); + if (is_local_addr(&(TO_CONN(orconn)->addr))) { + log_debug(LD_CHANNEL, + "Marking new incoming channel " U64_FORMAT " at %p as local", + U64_PRINTF_ARG(chan->global_identifier), chan); + channel_mark_local(chan); + } else { + log_debug(LD_CHANNEL, + "Marking new incoming channel " U64_FORMAT " at %p as remote", + U64_PRINTF_ARG(chan->global_identifier), chan); + channel_mark_remote(chan); + } + channel_mark_incoming(chan); - /* If we got one, we should register it */ - if (chan) channel_register(chan); + /* Register it */ + channel_register(chan); return chan; } @@ -410,6 +443,40 @@ channel_tls_free_method(channel_t *chan) } /** + * Get an estimate of the average TLS overhead for the upper layer + */ + +static double +channel_tls_get_overhead_estimate_method(channel_t *chan) +{ + double overhead = 1.0f; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + /* Just return 1.0f if we don't have sensible data */ + if (tlschan->conn->bytes_xmitted > 0 && + tlschan->conn->bytes_xmitted_by_tls >= + tlschan->conn->bytes_xmitted) { + overhead = ((double)(tlschan->conn->bytes_xmitted_by_tls)) / + ((double)(tlschan->conn->bytes_xmitted)); + + /* + * Never estimate more than 2.0; otherwise we get silly large estimates + * at the very start of a new TLS connection. + */ + if (overhead > 2.0f) overhead = 2.0f; + } + + log_debug(LD_CHANNEL, + "Estimated overhead ratio for TLS chan " U64_FORMAT " is %f", + U64_PRINTF_ARG(chan->global_identifier), overhead); + + return overhead; +} + +/** * Get the remote address of a channel_tls_t * * This implements the get_remote_addr method for channel_tls_t; copy the @@ -435,6 +502,30 @@ channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out) } /** + * Get the name of the pluggable transport used by a channel_tls_t. + * + * This implements the get_transport_name for channel_tls_t. If the + * channel uses a pluggable transport, copy its name to + * <b>transport_out</b> and return 0. If the channel did not use a + * pluggable transport, return -1. */ + +static int +channel_tls_get_transport_name_method(channel_t *chan, char **transport_out) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(transport_out); + tor_assert(tlschan->conn); + + if (!tlschan->conn->ext_or_transport) + return -1; + + *transport_out = tor_strdup(tlschan->conn->ext_or_transport); + return 0; +} + +/** * Get endpoint description of a channel_tls_t * * This implements the get_remote_descr method for channel_tls_t; it returns @@ -624,6 +715,53 @@ channel_tls_matches_target_method(channel_t *chan, } /** + * Tell the upper layer how many bytes we have queued and not yet + * sent. + */ + +static size_t +channel_tls_num_bytes_queued_method(channel_t *chan) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + return connection_get_outbuf_len(TO_CONN(tlschan->conn)); +} + +/** + * Tell the upper layer how many cells we can accept to write + * + * This implements the num_cells_writeable method for channel_tls_t; it + * returns an estimate of the number of cells we can accept with + * channel_tls_write_*_cell(). + */ + +static int +channel_tls_num_cells_writeable_method(channel_t *chan) +{ + size_t outbuf_len; + ssize_t n; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + size_t cell_network_size; + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + cell_network_size = get_cell_network_size(tlschan->conn->wide_circ_ids); + outbuf_len = connection_get_outbuf_len(TO_CONN(tlschan->conn)); + /* Get the number of cells */ + n = CEIL_DIV(OR_CONN_HIGHWATER - outbuf_len, cell_network_size); + if (n < 0) n = 0; +#if SIZEOF_SIZE_T > SIZEOF_INT + if (n > INT_MAX) n = INT_MAX; +#endif + + return (int)n; +} + +/** * Write a cell to a channel_tls_t * * This implements the write_cell method for channel_tls_t; given a @@ -798,18 +936,18 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, tor_assert(conn); tor_assert(conn->chan == chan); tor_assert(chan->conn == conn); - /* -Werror appeasement */ - tor_assert(old_state == old_state); + /* Shut the compiler up without triggering -Wtautological-compare */ + (void)old_state; base_chan = TLS_CHAN_TO_BASE(chan); - /* Make sure the base connection state makes sense - shouldn't be error, - * closed or listening. */ + /* Make sure the base connection state makes sense - shouldn't be error + * or closed. */ - tor_assert(base_chan->state == CHANNEL_STATE_OPENING || - base_chan->state == CHANNEL_STATE_OPEN || - base_chan->state == CHANNEL_STATE_MAINT || - base_chan->state == CHANNEL_STATE_CLOSING); + tor_assert(CHANNEL_IS_OPENING(base_chan) || + CHANNEL_IS_OPEN(base_chan) || + CHANNEL_IS_MAINT(base_chan) || + CHANNEL_IS_CLOSING(base_chan)); /* Did we just go to state open? */ if (state == OR_CONN_STATE_OPEN) { @@ -818,69 +956,21 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, * CHANNEL_STATE_MAINT on this. */ channel_change_state(base_chan, CHANNEL_STATE_OPEN); + /* We might have just become writeable; check and tell the scheduler */ + if (connection_or_num_cells_writeable(conn) > 0) { + scheduler_channel_wants_writes(base_chan); + } } else { /* * Not open, so from CHANNEL_STATE_OPEN we go to CHANNEL_STATE_MAINT, * otherwise no change. */ - if (base_chan->state == CHANNEL_STATE_OPEN) { + if (CHANNEL_IS_OPEN(base_chan)) { channel_change_state(base_chan, CHANNEL_STATE_MAINT); } } } -/** - * Flush cells from a channel_tls_t - * - * Try to flush up to about num_cells cells, and return how many we flushed. - */ - -ssize_t -channel_tls_flush_some_cells(channel_tls_t *chan, ssize_t num_cells) -{ - ssize_t flushed = 0; - - tor_assert(chan); - - if (flushed >= num_cells) goto done; - - /* - * If channel_tls_t ever buffers anything below the channel_t layer, flush - * that first here. - */ - - flushed += channel_flush_some_cells(TLS_CHAN_TO_BASE(chan), - num_cells - flushed); - - /* - * If channel_tls_t ever buffers anything below the channel_t layer, check - * how much we actually got and push it on down here. - */ - - done: - return flushed; -} - -/** - * Check if a channel_tls_t has anything to flush - * - * Return true if there is any more to flush on this channel (cells in queue - * or active circuits). - */ - -int -channel_tls_more_to_flush(channel_tls_t *chan) -{ - tor_assert(chan); - - /* - * If channel_tls_t ever buffers anything below channel_t, the - * check for that should go here first. - */ - - return channel_more_to_flush(TLS_CHAN_TO_BASE(chan)); -} - #ifdef KEEP_TIMING_STATS /** @@ -1182,6 +1272,44 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn) } /** + * Update channel marks after connection_or.c has changed an address + * + * This is called from connection_or_init_conn_from_address() after the + * connection's _base.addr or real_addr fields have potentially been changed + * so we can recalculate the local mark. Notably, this happens when incoming + * connections are reverse-proxied and we only learn the real address of the + * remote router by looking it up in the consensus after we finish the + * handshake and know an authenticated identity digest. + */ + +void +channel_tls_update_marks(or_connection_t *conn) +{ + channel_t *chan = NULL; + + tor_assert(conn); + tor_assert(conn->chan); + + chan = TLS_CHAN_TO_BASE(conn->chan); + + if (is_local_addr(&(TO_CONN(conn)->addr))) { + if (!channel_is_local(chan)) { + log_debug(LD_CHANNEL, + "Marking channel " U64_FORMAT " at %p as local", + U64_PRINTF_ARG(chan->global_identifier), chan); + channel_mark_local(chan); + } + } else { + if (channel_is_local(chan)) { + log_debug(LD_CHANNEL, + "Marking channel " U64_FORMAT " at %p as remote", + U64_PRINTF_ARG(chan->global_identifier), chan); + channel_mark_remote(chan); + } + } +} + +/** * Check if this cell type is allowed before the handshake is finished * * Return true if <b>command</b> is a cell command that's allowed to start a @@ -1255,13 +1383,20 @@ static void channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) { int highest_supported_version = 0; - const uint8_t *cp, *end; int started_here = 0; tor_assert(cell); tor_assert(chan); tor_assert(chan->conn); + if ((cell->payload_len % 2) == 1) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received a VERSION cell with odd payload length %d; " + "closing connection.",cell->payload_len); + connection_or_close_for_error(chan->conn, 0); + return; + } + started_here = connection_or_nonopen_was_started_here(chan->conn); if (chan->conn->link_proto != 0 || @@ -1287,11 +1422,15 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) } tor_assert(chan->conn->handshake_state); - end = cell->payload + cell->payload_len; - for (cp = cell->payload; cp+1 < end; cp += 2) { - uint16_t v = ntohs(get_uint16(cp)); - if (is_or_protocol_version_known(v) && v > highest_supported_version) - highest_supported_version = v; + + { + int i; + const uint8_t *cp = cell->payload; + for (i = 0; i < cell->payload_len / 2; ++i, cp += 2) { + uint16_t v = ntohs(get_uint16(cp)); + if (is_or_protocol_version_known(v) && v > highest_supported_version) + highest_supported_version = v; + } } if (!highest_supported_version) { log_fn(LOG_PROTOCOL_WARN, LD_OR, @@ -1325,6 +1464,8 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) return; } + rep_hist_note_negotiated_link_proto(highest_supported_version, started_here); + chan->conn->link_proto = highest_supported_version; chan->conn->handshake_state->received_versions = 1; @@ -1489,12 +1630,14 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) my_addr_ptr = (uint8_t*) cell->payload + 6; end = cell->payload + CELL_PAYLOAD_SIZE; cp = cell->payload + 6 + my_addr_len; - if (cp >= end) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Addresses too long in netinfo cell; closing connection."); - connection_or_close_for_error(chan->conn, 0); - return; - } else if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { + + /* We used to check: + * if (my_addr_len >= CELL_PAYLOAD_SIZE - 6) { + * + * This is actually never going to happen, since my_addr_len is at most 255, + * and CELL_PAYLOAD_LEN - 6 is 503. So we know that cp is < end. */ + + if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { tor_addr_from_ipv4n(&my_apparent_addr, get_uint32(my_addr_ptr)); } else if (my_addr_type == RESOLVED_TYPE_IPV6 && my_addr_len == 16) { tor_addr_from_ipv6_bytes(&my_apparent_addr, (const char *) my_addr_ptr); @@ -1514,7 +1657,7 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) return; } if (tor_addr_eq(&addr, &(chan->conn->real_addr))) { - chan->conn->is_canonical = 1; + connection_or_set_canonical(chan->conn, 1); break; } cp = next; @@ -1648,12 +1791,16 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) for (i = 0; i < n_certs; ++i) { uint8_t cert_type; uint16_t cert_len; - if (ptr + 3 > cell->payload + cell->payload_len) { + if (cell->payload_len < 3) + goto truncated; + if (ptr > cell->payload + cell->payload_len - 3) { goto truncated; } cert_type = *ptr; cert_len = ntohs(get_uint16(ptr+1)); - if (ptr + 3 + cert_len > cell->payload + cell->payload_len) { + if (cell->payload_len < 3 + cert_len) + goto truncated; + if (ptr > cell->payload + cell->payload_len - cert_len - 3) { goto truncated; } if (cert_type == OR_CERT_TYPE_TLS_LINK || diff --git a/src/or/channeltls.h b/src/or/channeltls.h index b4a7e2beac..507429420b 100644 --- a/src/or/channeltls.h +++ b/src/or/channeltls.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -40,8 +40,6 @@ channel_t * channel_tls_to_base(channel_tls_t *tlschan); channel_tls_t * channel_tls_from_base(channel_t *chan); /* Things for connection_or.c to call back into */ -ssize_t channel_tls_flush_some_cells(channel_tls_t *chan, ssize_t num_cells); -int channel_tls_more_to_flush(channel_tls_t *chan); void channel_tls_handle_cell(cell_t *cell, or_connection_t *conn); void channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, or_connection_t *conn, @@ -49,6 +47,7 @@ void channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, uint8_t state); void channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn); +void channel_tls_update_marks(or_connection_t *conn); /* Cleanup at shutdown */ void channel_tls_free_all(void); diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c new file mode 100644 index 0000000000..a0115cc6ec --- /dev/null +++ b/src/or/circpathbias.c @@ -0,0 +1,1538 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "channel.h" +#include "circpathbias.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "circuitstats.h" +#include "connection_edge.h" +#include "config.h" +#include "entrynodes.h" +#include "networkstatus.h" +#include "relay.h" + +static void pathbias_count_successful_close(origin_circuit_t *circ); +static void pathbias_count_collapse(origin_circuit_t *circ); +static void pathbias_count_use_failed(origin_circuit_t *circ); +static void pathbias_measure_use_rate(entry_guard_t *guard); +static void pathbias_measure_close_rate(entry_guard_t *guard); +static void pathbias_scale_use_rates(entry_guard_t *guard); +static void pathbias_scale_close_rates(entry_guard_t *guard); +static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard); + +/** Increment the number of times we successfully extended a circuit to + * <b>guard</b>, first checking if the failure rate is high enough that + * we should eliminate the guard. Return -1 if the guard looks no good; + * return 0 if the guard looks fine. + */ +static int +entry_guard_inc_circ_attempt_count(entry_guard_t *guard) +{ + entry_guards_changed(); + + pathbias_measure_close_rate(guard); + + if (guard->path_bias_disabled) + return -1; + + pathbias_scale_close_rates(guard); + guard->circ_attempts++; + + log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + return 0; +} + +/** The minimum number of circuit attempts before we start + * thinking about warning about path bias and dropping guards */ +static int +pathbias_get_min_circs(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_MIN_CIRC 150 + if (options->PathBiasCircThreshold >= 5) + return options->PathBiasCircThreshold; + else + return networkstatus_get_param(NULL, "pb_mincircs", + DFLT_PATH_BIAS_MIN_CIRC, + 5, INT32_MAX); +} + +/** The circuit success rate below which we issue a notice */ +static double +pathbias_get_notice_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_NOTICE_PCT 70 + if (options->PathBiasNoticeRate >= 0.0) + return options->PathBiasNoticeRate; + else + return networkstatus_get_param(NULL, "pb_noticepct", + DFLT_PATH_BIAS_NOTICE_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** The circuit success rate below which we issue a warn */ +static double +pathbias_get_warn_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_WARN_PCT 50 + if (options->PathBiasWarnRate >= 0.0) + return options->PathBiasWarnRate; + else + return networkstatus_get_param(NULL, "pb_warnpct", + DFLT_PATH_BIAS_WARN_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** + * The extreme rate is the rate at which we would drop the guard, + * if pb_dropguard is also set. Otherwise we just warn. + */ +double +pathbias_get_extreme_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_EXTREME_PCT 30 + if (options->PathBiasExtremeRate >= 0.0) + return options->PathBiasExtremeRate; + else + return networkstatus_get_param(NULL, "pb_extremepct", + DFLT_PATH_BIAS_EXTREME_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** + * If 1, we actually disable use of guards that fall below + * the extreme_pct. + */ +int +pathbias_get_dropguards(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_DROP_GUARDS 0 + if (options->PathBiasDropGuards >= 0) + return options->PathBiasDropGuards; + else + return networkstatus_get_param(NULL, "pb_dropguards", + DFLT_PATH_BIAS_DROP_GUARDS, 0, 1); +} + +/** + * This is the number of circuits at which we scale our + * counts by mult_factor/scale_factor. Note, this count is + * not exact, as we only perform the scaling in the event + * of no integer truncation. + */ +static int +pathbias_get_scale_threshold(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_SCALE_THRESHOLD 300 + if (options->PathBiasScaleThreshold >= 10) + return options->PathBiasScaleThreshold; + else + return networkstatus_get_param(NULL, "pb_scalecircs", + DFLT_PATH_BIAS_SCALE_THRESHOLD, 10, + INT32_MAX); +} + +/** + * Compute the path bias scaling ratio from the consensus + * parameters pb_multfactor/pb_scalefactor. + * + * Returns a value in (0, 1.0] which we multiply our pathbias + * counts with to scale them down. + */ +static double +pathbias_get_scale_ratio(const or_options_t *options) +{ + /* + * The scale factor is the denominator for our scaling + * of circuit counts for our path bias window. + * + * Note that our use of doubles for the path bias state + * file means that powers of 2 work best here. + */ + int denominator = networkstatus_get_param(NULL, "pb_scalefactor", + 2, 2, INT32_MAX); + (void) options; + /** + * The mult factor is the numerator for our scaling + * of circuit counts for our path bias window. It + * allows us to scale by fractions. + */ + return networkstatus_get_param(NULL, "pb_multfactor", + 1, 1, denominator)/((double)denominator); +} + +/** The minimum number of circuit usage attempts before we start + * thinking about warning about path use bias and dropping guards */ +static int +pathbias_get_min_use(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_MIN_USE 20 + if (options->PathBiasUseThreshold >= 3) + return options->PathBiasUseThreshold; + else + return networkstatus_get_param(NULL, "pb_minuse", + DFLT_PATH_BIAS_MIN_USE, + 3, INT32_MAX); +} + +/** The circuit use success rate below which we issue a notice */ +static double +pathbias_get_notice_use_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_NOTICE_USE_PCT 80 + if (options->PathBiasNoticeUseRate >= 0.0) + return options->PathBiasNoticeUseRate; + else + return networkstatus_get_param(NULL, "pb_noticeusepct", + DFLT_PATH_BIAS_NOTICE_USE_PCT, + 0, 100)/100.0; +} + +/** + * The extreme use rate is the rate at which we would drop the guard, + * if pb_dropguard is also set. Otherwise we just warn. + */ +double +pathbias_get_extreme_use_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_EXTREME_USE_PCT 60 + if (options->PathBiasExtremeUseRate >= 0.0) + return options->PathBiasExtremeUseRate; + else + return networkstatus_get_param(NULL, "pb_extremeusepct", + DFLT_PATH_BIAS_EXTREME_USE_PCT, + 0, 100)/100.0; +} + +/** + * This is the number of circuits at which we scale our + * use counts by mult_factor/scale_factor. Note, this count is + * not exact, as we only perform the scaling in the event + * of no integer truncation. + */ +static int +pathbias_get_scale_use_threshold(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_SCALE_USE_THRESHOLD 100 + if (options->PathBiasScaleUseThreshold >= 10) + return options->PathBiasScaleUseThreshold; + else + return networkstatus_get_param(NULL, "pb_scaleuse", + DFLT_PATH_BIAS_SCALE_USE_THRESHOLD, + 10, INT32_MAX); +} + +/** + * Convert a Guard's path state to string. + */ +const char * +pathbias_state_to_string(path_state_t state) +{ + switch (state) { + case PATH_STATE_NEW_CIRC: + return "new"; + case PATH_STATE_BUILD_ATTEMPTED: + return "build attempted"; + case PATH_STATE_BUILD_SUCCEEDED: + return "build succeeded"; + case PATH_STATE_USE_ATTEMPTED: + return "use attempted"; + case PATH_STATE_USE_SUCCEEDED: + return "use succeeded"; + case PATH_STATE_USE_FAILED: + return "use failed"; + case PATH_STATE_ALREADY_COUNTED: + return "already counted"; + } + + return "unknown"; +} + +/** + * This function decides if a circuit has progressed far enough to count + * as a circuit "attempt". As long as end-to-end tagging is possible, + * we assume the adversary will use it over hop-to-hop failure. Therefore, + * we only need to account bias for the last hop. This should make us + * much more resilient to ambient circuit failure, and also make that + * failure easier to measure (we only need to measure Exit failure rates). + */ +static int +pathbias_is_new_circ_attempt(origin_circuit_t *circ) +{ +#define N2N_TAGGING_IS_POSSIBLE +#ifdef N2N_TAGGING_IS_POSSIBLE + /* cpath is a circular list. We want circs with more than one hop, + * and the second hop must be waiting for keys still (it's just + * about to get them). */ + return circ->cpath && + circ->cpath->next != circ->cpath && + circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; +#else + /* If tagging attacks are no longer possible, we probably want to + * count bias from the first hop. However, one could argue that + * timing-based tagging is still more useful than per-hop failure. + * In which case, we'd never want to use this. + */ + return circ->cpath && + circ->cpath->state == CPATH_STATE_AWAITING_KEYS; +#endif +} + +/** + * Decide if the path bias code should count a circuit. + * + * @returns 1 if we should count it, 0 otherwise. + */ +static int +pathbias_should_count(origin_circuit_t *circ) +{ +#define PATHBIAS_COUNT_INTERVAL (600) + static ratelim_t count_limit = + RATELIM_INIT(PATHBIAS_COUNT_INTERVAL); + char *rate_msg = NULL; + + /* We can't do path bias accounting without entry guards. + * Testing and controller circuits also have no guards. + * + * We also don't count server-side rends, because their + * endpoint could be chosen maliciously. + * Similarly, we can't count client-side intro attempts, + * because clients can be manipulated into connecting to + * malicious intro points. */ + if (get_options()->UseEntryGuards == 0 || + circ->base_.purpose == CIRCUIT_PURPOSE_TESTING || + circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER || + circ->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || + circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED || + (circ->base_.purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && + circ->base_.purpose <= CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { + + /* Check to see if the shouldcount result has changed due to a + * unexpected purpose change that would affect our results. + * + * The reason we check the path state too here is because for the + * cannibalized versions of these purposes, we count them as successful + * before their purpose change. + */ + if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED + && circ->path_state != PATH_STATE_ALREADY_COUNTED) { + log_info(LD_BUG, + "Circuit %d is now being ignored despite being counted " + "in the past. Purpose is %s, path state is %s", + circ->global_identifier, + circuit_purpose_to_string(circ->base_.purpose), + pathbias_state_to_string(circ->path_state)); + } + circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; + return 0; + } + + /* Completely ignore one hop circuits */ + if (circ->build_state->onehop_tunnel || + circ->build_state->desired_path_len == 1) { + /* Check for inconsistency */ + if (circ->build_state->desired_path_len != 1 || + !circ->build_state->onehop_tunnel) { + if ((rate_msg = rate_limit_log(&count_limit, approx_time()))) { + log_info(LD_BUG, + "One-hop circuit has length %d. Path state is %s. " + "Circuit is a %s currently %s.%s", + circ->build_state->desired_path_len, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + tor_fragile_assert(); + } + + /* Check to see if the shouldcount result has changed due to a + * unexpected change that would affect our results */ + if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED) { + log_info(LD_BUG, + "One-hop circuit %d is now being ignored despite being counted " + "in the past. Purpose is %s, path state is %s", + circ->global_identifier, + circuit_purpose_to_string(circ->base_.purpose), + pathbias_state_to_string(circ->path_state)); + } + circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; + return 0; + } + + /* Check to see if the shouldcount result has changed due to a + * unexpected purpose change that would affect our results */ + if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_IGNORED) { + log_info(LD_BUG, + "Circuit %d is now being counted despite being ignored " + "in the past. Purpose is %s, path state is %s", + circ->global_identifier, + circuit_purpose_to_string(circ->base_.purpose), + pathbias_state_to_string(circ->path_state)); + } + circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_COUNTED; + + return 1; +} + +/** + * Check our circuit state to see if this is a successful circuit attempt. + * If so, record it in the current guard's path bias circ_attempt count. + * + * Also check for several potential error cases for bug #6475. + */ +int +pathbias_count_build_attempt(origin_circuit_t *circ) +{ +#define CIRC_ATTEMPT_NOTICE_INTERVAL (600) + static ratelim_t circ_attempt_notice_limit = + RATELIM_INIT(CIRC_ATTEMPT_NOTICE_INTERVAL); + char *rate_msg = NULL; + + if (!pathbias_should_count(circ)) { + return 0; + } + + if (pathbias_is_new_circ_attempt(circ)) { + /* Help track down the real cause of bug #6475: */ + if (circ->has_opened && circ->path_state != PATH_STATE_BUILD_ATTEMPTED) { + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Opened circuit is in strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + + /* Don't re-count cannibalized circs.. */ + if (!circ->has_opened) { + entry_guard_t *guard = NULL; + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } else if (circ->base_.n_chan) { + guard = + entry_guard_get_by_id_digest(circ->base_.n_chan->identity_digest); + } + + if (guard) { + if (circ->path_state == PATH_STATE_NEW_CIRC) { + circ->path_state = PATH_STATE_BUILD_ATTEMPTED; + + if (entry_guard_inc_circ_attempt_count(guard) < 0) { + /* Bogus guard; we already warned. */ + return -END_CIRC_REASON_TORPROTOCOL; + } + } else { + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Unopened circuit has strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } else { + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, + approx_time()))) { + log_info(LD_CIRC, + "Unopened circuit has no known guard. " + "Circuit is a %s currently %s.%s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } + } + + return 0; +} + +/** + * Check our circuit state to see if this is a successful circuit + * completion. If so, record it in the current guard's path bias + * success count. + * + * Also check for several potential error cases for bug #6475. + */ +void +pathbias_count_build_success(origin_circuit_t *circ) +{ +#define SUCCESS_NOTICE_INTERVAL (600) + static ratelim_t success_notice_limit = + RATELIM_INIT(SUCCESS_NOTICE_INTERVAL); + char *rate_msg = NULL; + entry_guard_t *guard = NULL; + + if (!pathbias_should_count(circ)) { + return; + } + + /* Don't count cannibalized/reused circs for path bias + * "build" success, since they get counted under "use" success. */ + if (!circ->has_opened) { + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) { + circ->path_state = PATH_STATE_BUILD_SUCCEEDED; + guard->circ_successes++; + entry_guards_changed(); + + log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } else { + if ((rate_msg = rate_limit_log(&success_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Succeeded circuit is in strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + + if (guard->circ_attempts < guard->circ_successes) { + log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " + "for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + if ((rate_msg = rate_limit_log(&success_notice_limit, + approx_time()))) { + log_info(LD_CIRC, + "Completed circuit has no known guard. " + "Circuit is a %s currently %s.%s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } else { + if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { + if ((rate_msg = rate_limit_log(&success_notice_limit, + approx_time()))) { + log_info(LD_BUG, + "Opened circuit is in strange path state %s. " + "Circuit is a %s currently %s.%s", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), + rate_msg); + tor_free(rate_msg); + } + } + } +} + +/** + * Record an attempt to use a circuit. Changes the circuit's + * path state and update its guard's usage counter. + * + * Used for path bias usage accounting. + */ +void +pathbias_count_use_attempt(origin_circuit_t *circ) +{ + entry_guard_t *guard; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { + log_notice(LD_BUG, + "Used circuit is in strange path state %s. " + "Circuit is a %s currently %s.", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + if (guard) { + pathbias_measure_use_rate(guard); + pathbias_scale_use_rates(guard); + guard->use_attempts++; + entry_guards_changed(); + + log_debug(LD_CIRC, + "Marked circuit %d (%f/%f) as used for guard %s ($%s).", + circ->global_identifier, + guard->use_successes, guard->use_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } + + circ->path_state = PATH_STATE_USE_ATTEMPTED; + } else { + /* Harmless but educational log message */ + log_info(LD_CIRC, + "Used circuit %d is already in path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } + + return; +} + +/** + * Check the circuit's path state is appropriate and mark it as + * successfully used. Used for path bias usage accounting. + * + * We don't actually increment the guard's counters until + * pathbias_check_close(), because the circuit can still transition + * back to PATH_STATE_USE_ATTEMPTED if a stream fails later (this + * is done so we can probe the circuit for liveness at close). + */ +void +pathbias_mark_use_success(origin_circuit_t *circ) +{ + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { + log_notice(LD_BUG, + "Used circuit %d is in strange path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + + pathbias_count_use_attempt(circ); + } + + /* We don't do any accounting at the guard until actual circuit close */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; + + return; +} + +/** + * If a stream ever detatches from a circuit in a retriable way, + * we need to mark this circuit as still needing either another + * successful stream, or in need of a probe. + * + * An adversary could let the first stream request succeed (ie the + * resolve), but then tag and timeout the remainder (via cell + * dropping), forcing them on new circuits. + * + * Rolling back the state will cause us to probe such circuits, which + * should lead to probe failures in the event of such tagging due to + * either unrecognized cells coming in while we wait for the probe, + * or the cipher state getting out of sync in the case of dropped cells. + */ +void +pathbias_mark_use_rollback(origin_circuit_t *circ) +{ + if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { + log_info(LD_CIRC, + "Rolling back pathbias use state to 'attempted' for detached " + "circuit %d", circ->global_identifier); + circ->path_state = PATH_STATE_USE_ATTEMPTED; + } +} + +/** + * Actually count a circuit success towards a guard's usage counters + * if the path state is appropriate. + */ +static void +pathbias_count_use_success(origin_circuit_t *circ) +{ + entry_guard_t *guard; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state != PATH_STATE_USE_SUCCEEDED) { + log_notice(LD_BUG, + "Successfully used circuit %d is in strange path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } else { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + if (guard) { + guard->use_successes++; + entry_guards_changed(); + + if (guard->use_attempts < guard->use_successes) { + log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) " + "for guard %s=%s", + guard->use_successes, guard->use_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + } + + log_debug(LD_CIRC, + "Marked circuit %d (%f/%f) as used successfully for guard " + "%s ($%s).", + circ->global_identifier, guard->use_successes, + guard->use_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + } + + return; +} + +/** + * Send a probe down a circuit that the client attempted to use, + * but for which the stream timed out/failed. The probe is a + * RELAY_BEGIN cell with a 0.a.b.c destination address, which + * the exit will reject and reply back, echoing that address. + * + * The reason for such probes is because it is possible to bias + * a user's paths simply by causing timeouts, and these timeouts + * are not possible to differentiate from unresponsive servers. + * + * The probe is sent at the end of the circuit lifetime for two + * reasons: to prevent cryptographic taggers from being able to + * drop cells to cause timeouts, and to prevent easy recognition + * of probes before any real client traffic happens. + * + * Returns -1 if we couldn't probe, 0 otherwise. + */ +static int +pathbias_send_usable_probe(circuit_t *circ) +{ + /* Based on connection_ap_handshake_send_begin() */ + char payload[CELL_PAYLOAD_SIZE]; + int payload_len; + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + crypt_path_t *cpath_layer = NULL; + char *probe_nonce = NULL; + + tor_assert(ocirc); + + cpath_layer = ocirc->cpath->prev; + + if (cpath_layer->state != CPATH_STATE_OPEN) { + /* This can happen for cannibalized circuits. Their + * last hop isn't yet open */ + log_info(LD_CIRC, + "Got pathbias probe request for unopened circuit %d. " + "Opened %d, len %d", ocirc->global_identifier, + ocirc->has_opened, ocirc->build_state->desired_path_len); + return -1; + } + + /* We already went down this road. */ + if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING && + ocirc->pathbias_probe_id) { + log_info(LD_CIRC, + "Got pathbias probe request for circuit %d with " + "outstanding probe", ocirc->global_identifier); + return -1; + } + + /* Can't probe if the channel isn't open */ + if (circ->n_chan == NULL || + (!CHANNEL_IS_OPEN(circ->n_chan) + && !CHANNEL_IS_MAINT(circ->n_chan))) { + log_info(LD_CIRC, + "Skipping pathbias probe for circuit %d: Channel is not open.", + ocirc->global_identifier); + return -1; + } + + circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); + + /* Update timestamp for when circuit_expire_building() should kill us */ + tor_gettimeofday(&circ->timestamp_began); + + /* Generate a random address for the nonce */ + crypto_rand((char*)ô->pathbias_probe_nonce, + sizeof(ocirc->pathbias_probe_nonce)); + ocirc->pathbias_probe_nonce &= 0x00ffffff; + probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce); + + tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce); + payload_len = (int)strlen(payload)+1; + + // XXX: need this? Can we assume ipv4 will always be supported? + // If not, how do we tell? + //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { + // set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); + // payload_len += 4; + //} + + /* Generate+Store stream id, make sure it's non-zero */ + ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc); + + if (ocirc->pathbias_probe_id==0) { + log_warn(LD_CIRC, + "Ran out of stream IDs on circuit %u during " + "pathbias probe attempt.", ocirc->global_identifier); + tor_free(probe_nonce); + return -1; + } + + log_info(LD_CIRC, + "Sending pathbias testing cell to %s:25 on stream %d for circ %d.", + probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier); + tor_free(probe_nonce); + + /* Send a test relay cell */ + if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ, + RELAY_COMMAND_BEGIN, payload, + payload_len, cpath_layer) < 0) { + log_notice(LD_CIRC, + "Failed to send pathbias probe cell on circuit %d.", + ocirc->global_identifier); + return -1; + } + + /* Mark it freshly dirty so it doesn't get expired in the meantime */ + circ->timestamp_dirty = time(NULL); + + return 0; +} + +/** + * Check the response to a pathbias probe, to ensure the + * cell is recognized and the nonce and other probe + * characteristics are as expected. + * + * If the response is valid, return 0. Otherwise return < 0. + */ +int +pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) +{ + /* Based on connection_edge_process_relay_cell() */ + relay_header_t rh; + int reason; + uint32_t ipv4_host; + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + + tor_assert(cell); + tor_assert(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING); + + relay_header_unpack(&rh, cell->payload); + + reason = rh.length > 0 ? + get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC; + + if (rh.command == RELAY_COMMAND_END && + reason == END_STREAM_REASON_EXITPOLICY && + ocirc->pathbias_probe_id == rh.stream_id) { + + /* Check length+extract host: It is in network order after the reason code. + * See connection_edge_end(). */ + if (rh.length < 9) { /* reason+ipv4+dns_ttl */ + log_notice(LD_PROTOCOL, + "Short path bias probe response length field (%d).", rh.length); + return - END_CIRC_REASON_TORPROTOCOL; + } + + ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); + + /* Check nonce */ + if (ipv4_host == ocirc->pathbias_probe_nonce) { + pathbias_mark_use_success(ocirc); + circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); + log_info(LD_CIRC, + "Got valid path bias probe back for circ %d, stream %d.", + ocirc->global_identifier, ocirc->pathbias_probe_id); + return 0; + } else { + log_notice(LD_CIRC, + "Got strange probe value 0x%x vs 0x%x back for circ %d, " + "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce, + ocirc->global_identifier, ocirc->pathbias_probe_id); + return -1; + } + } + log_info(LD_CIRC, + "Got another cell back back on pathbias probe circuit %d: " + "Command: %d, Reason: %d, Stream-id: %d", + ocirc->global_identifier, rh.command, reason, rh.stream_id); + return -1; +} + +/** + * Check if a circuit was used and/or closed successfully. + * + * If we attempted to use the circuit to carry a stream but failed + * for whatever reason, or if the circuit mysteriously died before + * we could attach any streams, record these two cases. + * + * If we *have* successfully used the circuit, or it appears to + * have been closed by us locally, count it as a success. + * + * Returns 0 if we're done making decisions with the circ, + * or -1 if we want to probe it first. + */ +int +pathbias_check_close(origin_circuit_t *ocirc, int reason) +{ + circuit_t *circ = ô->base_; + + if (!pathbias_should_count(ocirc)) { + return 0; + } + + switch (ocirc->path_state) { + /* If the circuit was closed after building, but before use, we need + * to ensure we were the ones who tried to close it (and not a remote + * actor). */ + case PATH_STATE_BUILD_SUCCEEDED: + if (reason & END_CIRC_REASON_FLAG_REMOTE) { + /* Remote circ close reasons on an unused circuit all could be bias */ + log_info(LD_CIRC, + "Circuit %d remote-closed without successful use for reason %d. " + "Circuit purpose %d currently %d,%s. Len %d.", + ocirc->global_identifier, + reason, circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + pathbias_count_collapse(ocirc); + } else if ((reason & ~END_CIRC_REASON_FLAG_REMOTE) + == END_CIRC_REASON_CHANNEL_CLOSED && + circ->n_chan && + circ->n_chan->reason_for_closing + != CHANNEL_CLOSE_REQUESTED) { + /* If we didn't close the channel ourselves, it could be bias */ + /* XXX: Only count bias if the network is live? + * What about clock jumps/suspends? */ + log_info(LD_CIRC, + "Circuit %d's channel closed without successful use for reason " + "%d, channel reason %d. Circuit purpose %d currently %d,%s. Len " + "%d.", ocirc->global_identifier, + reason, circ->n_chan->reason_for_closing, + circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + pathbias_count_collapse(ocirc); + } else { + pathbias_count_successful_close(ocirc); + } + break; + + /* If we tried to use a circuit but failed, we should probe it to ensure + * it has not been tampered with. */ + case PATH_STATE_USE_ATTEMPTED: + /* XXX: Only probe and/or count failure if the network is live? + * What about clock jumps/suspends? */ + if (pathbias_send_usable_probe(circ) == 0) + return -1; + else + pathbias_count_use_failed(ocirc); + + /* Any circuit where there were attempted streams but no successful + * streams could be bias */ + log_info(LD_CIRC, + "Circuit %d closed without successful use for reason %d. " + "Circuit purpose %d currently %d,%s. Len %d.", + ocirc->global_identifier, + reason, circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + break; + + case PATH_STATE_USE_SUCCEEDED: + pathbias_count_successful_close(ocirc); + pathbias_count_use_success(ocirc); + break; + + case PATH_STATE_USE_FAILED: + pathbias_count_use_failed(ocirc); + break; + + case PATH_STATE_NEW_CIRC: + case PATH_STATE_BUILD_ATTEMPTED: + case PATH_STATE_ALREADY_COUNTED: + default: + // Other states are uninteresting. No stats to count. + break; + } + + ocirc->path_state = PATH_STATE_ALREADY_COUNTED; + + return 0; +} + +/** + * Count a successfully closed circuit. + */ +static void +pathbias_count_successful_close(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + /* In the long run: circuit_success ~= successful_circuit_close + + * circ_failure + stream_failure */ + guard->successful_circuits_closed++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + log_info(LD_CIRC, + "Successfully closed circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count a circuit that fails after it is built, but before it can + * carry any traffic. + * + * This is needed because there are ways to destroy a + * circuit after it has successfully completed. Right now, this is + * used for purely informational/debugging purposes. + */ +static void +pathbias_count_collapse(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->collapsed_circuits++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + log_info(LD_CIRC, + "Destroyed circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count a known failed circuit (because we could not probe it). + * + * This counter is informational. + */ +static void +pathbias_count_use_failed(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->unusable_circuits++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + /* XXX note cut-and-paste code in this function compared to nearby + * functions. Would be nice to refactor. -RD */ + log_info(LD_CIRC, + "Stream-failing circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count timeouts for path bias log messages. + * + * These counts are purely informational. + */ +void +pathbias_count_timeout(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + + if (!pathbias_should_count(circ)) { + return; + } + + /* For hidden service circs, they can actually be used + * successfully and then time out later (because + * the other side declines to use them). */ + if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->timeouts++; + entry_guards_changed(); + } +} + +/** + * Helper function to count all of the currently opened circuits + * for a guard that are in a given path state range. The state + * range is inclusive on both ends. + */ +static int +pathbias_count_circs_in_states(entry_guard_t *guard, + path_state_t from, + path_state_t to) +{ + int open_circuits = 0; + + /* Count currently open circuits. Give them the benefit of the doubt. */ + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + origin_circuit_t *ocirc = NULL; + if (!CIRCUIT_IS_ORIGIN(circ) || /* didn't originate here */ + circ->marked_for_close) /* already counted */ + continue; + + ocirc = TO_ORIGIN_CIRCUIT(circ); + + if (!ocirc->cpath || !ocirc->cpath->extend_info) + continue; + + if (ocirc->path_state >= from && + ocirc->path_state <= to && + pathbias_should_count(ocirc) && + fast_memeq(guard->identity, + ocirc->cpath->extend_info->identity_digest, + DIGEST_LEN)) { + log_debug(LD_CIRC, "Found opened circuit %d in path_state %s", + ocirc->global_identifier, + pathbias_state_to_string(ocirc->path_state)); + open_circuits++; + } + } + SMARTLIST_FOREACH_END(circ); + + return open_circuits; +} + +/** + * Return the number of circuits counted as successfully closed for + * this guard. + * + * Also add in the currently open circuits to give them the benefit + * of the doubt. + */ +double +pathbias_get_close_success_count(entry_guard_t *guard) +{ + return guard->successful_circuits_closed + + pathbias_count_circs_in_states(guard, + PATH_STATE_BUILD_SUCCEEDED, + PATH_STATE_USE_SUCCEEDED); +} + +/** + * Return the number of circuits counted as successfully used + * this guard. + * + * Also add in the currently open circuits that we are attempting + * to use to give them the benefit of the doubt. + */ +double +pathbias_get_use_success_count(entry_guard_t *guard) +{ + return guard->use_successes + + pathbias_count_circs_in_states(guard, + PATH_STATE_USE_ATTEMPTED, + PATH_STATE_USE_SUCCEEDED); +} + +/** + * Check the path bias use rate against our consensus parameter limits. + * + * Emits a log message if the use success rates are too low. + * + * If pathbias_get_dropguards() is set, we also disable the use of + * very failure prone guards. + */ +static void +pathbias_measure_use_rate(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + if (guard->use_attempts > pathbias_get_min_use(options)) { + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_use_success_count(guard)/guard->use_attempts + < pathbias_get_extreme_use_rate(options)) { + /* Dropping is currently disabled by default. */ + if (pathbias_get_dropguards(options)) { + if (!guard->path_bias_disabled) { + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing to carry an extremely large " + "amount of stream on its circuits. " + "To avoid potential route manipulation attacks, Tor has " + "disabled use of this guard. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + guard->path_bias_disabled = 1; + guard->bad_since = approx_time(); + entry_guards_changed(); + return; + } + } else if (!guard->path_bias_use_extreme) { + guard->path_bias_use_extreme = 1; + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing to carry an extremely large " + "amount of streams on its circuits. " + "This could indicate a route manipulation attack, network " + "overload, bad local network connectivity, or a bug. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } else if (pathbias_get_use_success_count(guard)/guard->use_attempts + < pathbias_get_notice_use_rate(options)) { + if (!guard->path_bias_use_noticed) { + guard->path_bias_use_noticed = 1; + log_notice(LD_CIRC, + "Your Guard %s ($%s) is failing to carry more streams on its " + "circuits than usual. " + "Most likely this means the Tor network is overloaded " + "or your network connection is poor. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } + } +} + +/** + * Check the path bias circuit close status rates against our consensus + * parameter limits. + * + * Emits a log message if the use success rates are too low. + * + * If pathbias_get_dropguards() is set, we also disable the use of + * very failure prone guards. + * + * XXX: This function shares similar log messages and checks to + * pathbias_measure_use_rate(). It may be possible to combine them + * eventually, especially if we can ever remove the need for 3 + * levels of closure warns (if the overall circuit failure rate + * goes down with ntor). One way to do so would be to multiply + * the build rate with the use rate to get an idea of the total + * fraction of the total network paths the user is able to use. + * See ticket #8159. + */ +static void +pathbias_measure_close_rate(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + if (guard->circ_attempts > pathbias_get_min_circs(options)) { + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_close_success_count(guard)/guard->circ_attempts + < pathbias_get_extreme_rate(options)) { + /* Dropping is currently disabled by default. */ + if (pathbias_get_dropguards(options)) { + if (!guard->path_bias_disabled) { + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing an extremely large " + "amount of circuits. " + "To avoid potential route manipulation attacks, Tor has " + "disabled use of this guard. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + guard->path_bias_disabled = 1; + guard->bad_since = approx_time(); + entry_guards_changed(); + return; + } + } else if (!guard->path_bias_extreme) { + guard->path_bias_extreme = 1; + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing an extremely large " + "amount of circuits. " + "This could indicate a route manipulation attack, " + "extreme network overload, or a bug. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts + < pathbias_get_warn_rate(options)) { + if (!guard->path_bias_warned) { + guard->path_bias_warned = 1; + log_warn(LD_CIRC, + "Your Guard %s ($%s) is failing a very large " + "amount of circuits. " + "Most likely this means the Tor network is " + "overloaded, but it could also mean an attack against " + "you or potentially the guard itself. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts + < pathbias_get_notice_rate(options)) { + if (!guard->path_bias_noticed) { + guard->path_bias_noticed = 1; + log_notice(LD_CIRC, + "Your Guard %s ($%s) is failing more circuits than " + "usual. " + "Most likely this means the Tor network is overloaded. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(get_circuit_build_close_time_ms()/1000)); + } + } + } +} + +/** + * This function scales the path bias use rates if we have + * more data than the scaling threshold. This allows us to + * be more sensitive to recent measurements. + * + * XXX: The attempt count transfer stuff here might be done + * better by keeping separate pending counters that get + * transfered at circuit close. See ticket #8160. + */ +static void +pathbias_scale_close_rates(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + /* If we get a ton of circuits, just scale everything down */ + if (guard->circ_attempts > pathbias_get_scale_threshold(options)) { + double scale_ratio = pathbias_get_scale_ratio(options); + int opened_attempts = pathbias_count_circs_in_states(guard, + PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED); + int opened_built = pathbias_count_circs_in_states(guard, + PATH_STATE_BUILD_SUCCEEDED, + PATH_STATE_USE_FAILED); + /* Verify that the counts are sane before and after scaling */ + int counts_are_sane = (guard->circ_attempts >= guard->circ_successes); + + guard->circ_attempts -= (opened_attempts+opened_built); + guard->circ_successes -= opened_built; + + guard->circ_attempts *= scale_ratio; + guard->circ_successes *= scale_ratio; + guard->timeouts *= scale_ratio; + guard->successful_circuits_closed *= scale_ratio; + guard->collapsed_circuits *= scale_ratio; + guard->unusable_circuits *= scale_ratio; + + guard->circ_attempts += (opened_attempts+opened_built); + guard->circ_successes += opened_built; + + entry_guards_changed(); + + log_info(LD_CIRC, + "Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard " + "%s ($%s)", + guard->circ_successes, guard->successful_circuits_closed, + guard->circ_attempts, opened_built, opened_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + + /* Have the counts just become invalid by this scaling attempt? */ + if (counts_are_sane && guard->circ_attempts < guard->circ_successes) { + log_notice(LD_BUG, + "Scaling has mangled pathbias counts to %f/%f (%d/%d open) " + "for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, opened_built, + opened_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + } +} + +/** + * This function scales the path bias circuit close rates if we have + * more data than the scaling threshold. This allows us to be more + * sensitive to recent measurements. + * + * XXX: The attempt count transfer stuff here might be done + * better by keeping separate pending counters that get + * transfered at circuit close. See ticket #8160. + */ +void +pathbias_scale_use_rates(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); + + /* If we get a ton of circuits, just scale everything down */ + if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) { + double scale_ratio = pathbias_get_scale_ratio(options); + int opened_attempts = pathbias_count_circs_in_states(guard, + PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); + /* Verify that the counts are sane before and after scaling */ + int counts_are_sane = (guard->use_attempts >= guard->use_successes); + + guard->use_attempts -= opened_attempts; + + guard->use_attempts *= scale_ratio; + guard->use_successes *= scale_ratio; + + guard->use_attempts += opened_attempts; + + log_info(LD_CIRC, + "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)", + guard->use_successes, guard->use_attempts, opened_attempts, + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + + /* Have the counts just become invalid by this scaling attempt? */ + if (counts_are_sane && guard->use_attempts < guard->use_successes) { + log_notice(LD_BUG, + "Scaling has mangled pathbias usage counts to %f/%f " + "(%d open) for guard %s ($%s)", + guard->circ_successes, guard->circ_attempts, + opened_attempts, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + + entry_guards_changed(); + } +} + diff --git a/src/or/circpathbias.h b/src/or/circpathbias.h new file mode 100644 index 0000000000..9e973850d5 --- /dev/null +++ b/src/or/circpathbias.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitbuild.h + * \brief Header file for circuitbuild.c. + **/ + +#ifndef TOR_CIRCPATHBIAS_H +#define TOR_CIRCPATHBIAS_H + +double pathbias_get_extreme_rate(const or_options_t *options); +double pathbias_get_extreme_use_rate(const or_options_t *options); +int pathbias_get_dropguards(const or_options_t *options); +void pathbias_count_timeout(origin_circuit_t *circ); +void pathbias_count_build_success(origin_circuit_t *circ); +int pathbias_count_build_attempt(origin_circuit_t *circ); +int pathbias_check_close(origin_circuit_t *circ, int reason); +int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); +void pathbias_count_use_attempt(origin_circuit_t *circ); +void pathbias_mark_use_success(origin_circuit_t *circ); +void pathbias_mark_use_rollback(origin_circuit_t *circ); +const char *pathbias_state_to_string(path_state_t state); + +#endif + diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 4603de071f..946c002735 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -9,8 +9,12 @@ * \brief The actual details of building circuits. **/ +#define CIRCUITBUILD_PRIVATE + #include "or.h" #include "channel.h" +#include "circpathbias.h" +#define CIRCUITBUILD_PRIVATE #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" @@ -40,19 +44,11 @@ #include "routerparse.h" #include "routerset.h" #include "crypto.h" -#include "connection_edge.h" #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif -/********* START VARIABLES **********/ - -/** A global list of all circuits at this hop. */ -extern circuit_t *global_circuitlist; - -/********* END VARIABLES ************/ - static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port, const char *id_digest); @@ -64,17 +60,7 @@ static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); static int count_acceptable_nodes(smartlist_t *routers); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); -static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard); -static void pathbias_count_build_success(origin_circuit_t *circ); -static void pathbias_count_successful_close(origin_circuit_t *circ); -static void pathbias_count_collapse(origin_circuit_t *circ); -static void pathbias_count_use_failed(origin_circuit_t *circ); -static void pathbias_measure_use_rate(entry_guard_t *guard); -static void pathbias_measure_close_rate(entry_guard_t *guard); -static void pathbias_scale_use_rates(entry_guard_t *guard); -#ifdef CURVE25519_ENABLED static int circuits_can_use_ntor(void); -#endif /** This function tries to get a channel to the specified endpoint, * and then calls command_setup_channel() to give it the right @@ -92,18 +78,29 @@ channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port, return chan; } -/** Iterate over values of circ_id, starting from conn-\>next_circ_id, - * and with the high bit specified by conn-\>circ_id_type, until we get - * a circ_id that is not in use by any other circuit on that conn. +/** Search for a value for circ_id that we can use on <b>chan</b> for an + * outbound circuit, until we get a circ_id that is not in use by any other + * circuit on that conn. * * Return it, or 0 if can't get a unique circ_id. */ -static circid_t +STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan) { +/* This number is chosen somewhat arbitrarily; see comment below for more + * info. When the space is 80% full, it gives a one-in-a-million failure + * chance; when the space is 90% full, it gives a one-in-850 chance; and when + * the space is 95% full, it gives a one-in-26 failure chance. That seems + * okay, though you could make a case IMO for anything between N=32 and + * N=256. */ +#define MAX_CIRCID_ATTEMPTS 64 + int in_use; + unsigned n_with_circ = 0, n_pending_destroy = 0, n_weird_pending_destroy = 0; circid_t test_circ_id; circid_t attempts=0; - circid_t high_bit, max_range; + circid_t high_bit, max_range, mask; + int64_t pending_destroy_time_total = 0; + int64_t pending_destroy_time_max = 0; tor_assert(chan); @@ -113,32 +110,108 @@ get_unique_circ_id_by_chan(channel_t *chan) "a client with no identity."); return 0; } - max_range = (chan->wide_circ_ids) ? (1u<<31) : (1u<<15); + max_range = (chan->wide_circ_ids) ? (1u<<31) : (1u<<15); + mask = max_range - 1; high_bit = (chan->circ_id_type == CIRC_ID_TYPE_HIGHER) ? max_range : 0; do { - /* Sequentially iterate over test_circ_id=1...max_range until we find a - * circID such that (high_bit|test_circ_id) is not already used. */ - test_circ_id = chan->next_circ_id++; - if (test_circ_id == 0 || test_circ_id >= max_range) { - test_circ_id = 1; - chan->next_circ_id = 2; - } - if (++attempts > max_range) { - /* Make sure we don't loop forever if all circ_id's are used. This - * matters because it's an external DoS opportunity. + if (++attempts > MAX_CIRCID_ATTEMPTS) { + /* Make sure we don't loop forever because all circuit IDs are used. + * + * Once, we would try until we had tried every possible circuit ID. But + * that's quite expensive. Instead, we try MAX_CIRCID_ATTEMPTS random + * circuit IDs, and then give up. + * + * This potentially causes us to give up early if our circuit ID space + * is nearly full. If we have N circuit IDs in use, then we will reject + * a new circuit with probability (N / max_range) ^ MAX_CIRCID_ATTEMPTS. + * This means that in practice, a few percent of our circuit ID capacity + * will go unused. + * + * The alternative here, though, is to do a linear search over the + * whole circuit ID space every time we extend a circuit, which is + * not so great either. */ - if (! chan->warned_circ_ids_exhausted) { - chan->warned_circ_ids_exhausted = 1; - log_warn(LD_CIRC,"No unused circIDs found on channel %s wide " + int64_t queued_destroys; + char *m = rate_limit_log(&chan->last_warned_circ_ids_exhausted, + approx_time()); + if (m == NULL) + return 0; /* This message has been rate-limited away. */ + if (n_pending_destroy) + pending_destroy_time_total /= n_pending_destroy; + log_warn(LD_CIRC,"No unused circIDs found on channel %s wide " "circID support, with %u inbound and %u outbound circuits. " - "Failing a circuit.", + "Found %u circuit IDs in use by circuits, and %u with " + "pending destroy cells. (%u of those were marked bogusly.) " + "The ones with pending destroy cells " + "have been marked unusable for an average of %ld seconds " + "and a maximum of %ld seconds. This channel is %ld seconds " + "old. Failing a circuit.%s", chan->wide_circ_ids ? "with" : "without", - chan->num_p_circuits, chan->num_n_circuits); + chan->num_p_circuits, chan->num_n_circuits, + n_with_circ, n_pending_destroy, n_weird_pending_destroy, + (long)pending_destroy_time_total, + (long)pending_destroy_time_max, + (long)(approx_time() - chan->timestamp_created), + m); + tor_free(m); + + if (!chan->cmux) { + /* This warning should be impossible. */ + log_warn(LD_BUG, " This channel somehow has no cmux on it!"); + return 0; } + + /* analysis so far on 12184 suggests that we're running out of circuit + IDs because it looks like we have too many pending destroy + cells. Let's see how many we really have pending. + */ + queued_destroys = circuitmux_count_queued_destroy_cells(chan, + chan->cmux); + + log_warn(LD_CIRC, " Circuitmux on this channel has %u circuits, " + "of which %u are active. It says it has "I64_FORMAT + " destroy cells queued.", + circuitmux_num_circuits(chan->cmux), + circuitmux_num_active_circuits(chan->cmux), + I64_PRINTF_ARG(queued_destroys)); + + /* Change this into "if (1)" in order to get more information about + * possible failure modes here. You'll need to know how to use gdb with + * Tor: this will make Tor exit with an assertion failure if the cmux is + * corrupt. */ + if (0) + circuitmux_assert_okay(chan->cmux); + + channel_dump_statistics(chan, LOG_WARN); + return 0; } + + do { + crypto_rand((char*) &test_circ_id, sizeof(test_circ_id)); + test_circ_id &= mask; + } while (test_circ_id == 0); + test_circ_id |= high_bit; - } while (circuit_id_in_use_on_channel(test_circ_id, chan)); + + in_use = circuit_id_in_use_on_channel(test_circ_id, chan); + if (in_use == 1) + ++n_with_circ; + else if (in_use == 2) { + time_t since_when; + ++n_pending_destroy; + since_when = + circuit_id_when_marked_unusable_on_channel(test_circ_id, chan); + if (since_when) { + time_t waiting = approx_time() - since_when; + pending_destroy_time_total += waiting; + if (waiting > pending_destroy_time_max) + pending_destroy_time_max = waiting; + } else { + ++n_weird_pending_destroy; + } + } + } while (in_use); return test_circ_id; } @@ -294,14 +367,13 @@ circuit_rep_hist_note_result(origin_circuit_t *circ) } while (hop!=circ->cpath); } -#ifdef CURVE25519_ENABLED /** Return 1 iff at least one node in circ's cpath supports ntor. */ static int circuit_cpath_supports_ntor(const origin_circuit_t *circ) { - crypt_path_t *head = circ->cpath, *cpath = circ->cpath; + crypt_path_t *head, *cpath; - cpath = head; + cpath = head = circ->cpath; do { if (cpath->extend_info && !tor_mem_is_zero( @@ -314,9 +386,6 @@ circuit_cpath_supports_ntor(const origin_circuit_t *circ) return 0; } -#else -#define circuit_cpath_supports_ntor(circ) 0 -#endif /** Pick all the entries in our cpath. Stop and return 0 when we're * happy, or return -1 if an error occurs. */ @@ -324,11 +393,7 @@ static int onion_populate_cpath(origin_circuit_t *circ) { int n_tries = 0; -#ifdef CURVE25519_ENABLED const int using_ntor = circuits_can_use_ntor(); -#else - const int using_ntor = 0; -#endif #define MAX_POPULATE_ATTEMPTS 32 @@ -475,6 +540,7 @@ circuit_handle_first_hop(origin_circuit_t *circ) log_debug(LD_CIRC,"Conn open. Delivering first onion skin."); if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { log_info(LD_CIRC,"circuit_send_next_onion_skin failed."); + circ->base_.n_chan = NULL; return err_reason; } } @@ -485,9 +551,13 @@ circuit_handle_first_hop(origin_circuit_t *circ) * open and get them to send their create cells forward. * * Status is 1 if connect succeeded, or 0 if connect failed. + * + * Close_origin_circuits is 1 if we should close all the origin circuits + * through this channel, or 0 otherwise. (This happens when we want to retry + * an older guard.) */ void -circuit_n_chan_done(channel_t *chan, int status) +circuit_n_chan_done(channel_t *chan, int status, int close_origin_circuits) { smartlist_t *pending_circs; int err_reason = 0; @@ -525,6 +595,11 @@ circuit_n_chan_done(channel_t *chan, int status) circuit_mark_for_close(circ, END_CIRC_REASON_CHANNEL_CLOSED); continue; } + if (close_origin_circuits && CIRCUIT_IS_ORIGIN(circ)) { + log_info(LD_CIRC,"Channel deprecated for origin circs; closing circ."); + circuit_mark_for_close(circ, END_CIRC_REASON_CHANNEL_CLOSED); + continue; + } log_debug(LD_CIRC, "Found circ, sending create cell."); /* circuit_deliver_create_cell will set n_circ_id and add us to * chan_circuid_circuit_map, so we don't need to call @@ -583,19 +658,21 @@ circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, id = get_unique_circ_id_by_chan(circ->n_chan); if (!id) { - log_warn(LD_CIRC,"failed to get unique circID."); - return -1; + static ratelim_t circid_warning_limit = RATELIM_INIT(9600); + log_fn_ratelim(&circid_warning_limit, LOG_WARN, LD_CIRC, + "failed to get unique circID."); + goto error; } - log_debug(LD_CIRC,"Chosen circID %u.", (unsigned)id); - circuit_set_n_circid_chan(circ, id, circ->n_chan); memset(&cell, 0, sizeof(cell_t)); r = relayed ? create_cell_format_relayed(&cell, create_cell) : create_cell_format(&cell, create_cell); if (r < 0) { log_warn(LD_CIRC,"Couldn't format create cell"); - return -1; + goto error; } + log_debug(LD_CIRC,"Chosen circID %u.", (unsigned)id); + circuit_set_n_circid_chan(circ, id, circ->n_chan); cell.circ_id = circ->n_circ_id; append_cell_to_circuit_queue(circ, circ->n_chan, &cell, @@ -604,7 +681,7 @@ circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, if (CIRCUIT_IS_ORIGIN(circ)) { /* Update began timestamp for circuits starting their first hop */ if (TO_ORIGIN_CIRCUIT(circ)->cpath->state == CPATH_STATE_CLOSED) { - if (circ->n_chan->state != CHANNEL_STATE_OPEN) { + if (!CHANNEL_IS_OPEN(circ->n_chan)) { log_warn(LD_CIRC, "Got first hop for a circuit without an opened channel. " "State: %s.", channel_state_to_string(circ->n_chan->state)); @@ -619,6 +696,9 @@ circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, } return 0; + error: + circ->n_chan = NULL; + return -1; } /** We've decided to start our reachability testing. If all @@ -628,27 +708,30 @@ int inform_testing_reachability(void) { char dirbuf[128]; + char *address; const routerinfo_t *me = router_get_my_routerinfo(); if (!me) return 0; + address = tor_dup_ip(me->addr); control_event_server_status(LOG_NOTICE, "CHECKING_REACHABILITY ORADDRESS=%s:%d", - me->address, me->or_port); + address, me->or_port); if (me->dir_port) { tor_snprintf(dirbuf, sizeof(dirbuf), " and DirPort %s:%d", - me->address, me->dir_port); + address, me->dir_port); control_event_server_status(LOG_NOTICE, "CHECKING_REACHABILITY DIRADDRESS=%s:%d", - me->address, me->dir_port); + address, me->dir_port); } log_notice(LD_OR, "Now checking whether ORPort %s:%d%s %s reachable... " "(this may take up to %d minutes -- look for log " "messages indicating success)", - me->address, me->or_port, + address, me->or_port, me->dir_port ? dirbuf : "", me->dir_port ? "are" : "is", TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT/60); + tor_free(address); return 1; } @@ -689,7 +772,6 @@ circuit_timeout_want_to_count_circ(origin_circuit_t *circ) && circ->build_state->desired_path_len == DEFAULT_ROUTE_LEN; } -#ifdef CURVE25519_ENABLED /** Return true if the ntor handshake is enabled in the configuration, or if * it's been set to "auto" in the configuration and it's enabled in the * consensus. */ @@ -701,7 +783,6 @@ circuits_can_use_ntor(void) return options->UseNTorHandshake; return networkstatus_get_param(NULL, "UseNTorHandshake", 0, 0, 1); } -#endif /** Decide whether to use a TAP or ntor handshake for connecting to <b>ei</b> * directly, and set *<b>cell_type_out</b> and *<b>handshake_type_out</b> @@ -711,7 +792,6 @@ circuit_pick_create_handshake(uint8_t *cell_type_out, uint16_t *handshake_type_out, const extend_info_t *ei) { -#ifdef CURVE25519_ENABLED if (!tor_mem_is_zero((const char*)ei->curve25519_onion_key.public_key, CURVE25519_PUBKEY_LEN) && circuits_can_use_ntor()) { @@ -719,9 +799,6 @@ circuit_pick_create_handshake(uint8_t *cell_type_out, *handshake_type_out = ONION_HANDSHAKE_TYPE_NTOR; return; } -#else - (void) ei; -#endif *cell_type_out = CELL_CREATE; *handshake_type_out = ONION_HANDSHAKE_TYPE_TAP; @@ -844,20 +921,24 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) * it off at, we probably had a suspend event along this codepath, * and we should discard the value. */ - if (timediff < 0 || timediff > 2*circ_times.close_ms+1000) { + if (timediff < 0 || + timediff > 2*get_circuit_build_close_time_ms()+1000) { log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " "Assuming clock jump. Purpose %d (%s)", timediff, circ->base_.purpose, circuit_purpose_to_string(circ->base_.purpose)); } else if (!circuit_build_times_disabled()) { /* Only count circuit times if the network is live */ - if (circuit_build_times_network_check_live(&circ_times)) { - circuit_build_times_add_time(&circ_times, (build_time_t)timediff); - circuit_build_times_set_timeout(&circ_times); + if (circuit_build_times_network_check_live( + get_circuit_build_times())) { + circuit_build_times_add_time(get_circuit_build_times_mutable(), + (build_time_t)timediff); + circuit_build_times_set_timeout(get_circuit_build_times_mutable()); } if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - circuit_build_times_network_circ_success(&circ_times); + circuit_build_times_network_circ_success( + get_circuit_build_times_mutable()); } } } @@ -872,14 +953,18 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) circuit_rep_hist_note_result(circ); circuit_has_opened(circ); /* do other actions as necessary */ - if (!can_complete_circuit && !circ->build_state->onehop_tunnel) { + if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { const or_options_t *options = get_options(); - can_complete_circuit=1; + note_that_we_completed_a_circuit(); /* FFFF Log a count of known routers here */ log_notice(LD_GENERAL, "Tor has successfully opened a circuit. " "Looks like client functionality is working."); - control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0); + if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) { + log_notice(LD_GENERAL, + "Tor has successfully opened a circuit. " + "Looks like client functionality is working."); + } control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); clear_broken_connection_map(1); if (server_mode(options) && !check_whether_orport_reachable()) { @@ -962,7 +1047,8 @@ circuit_note_clock_jumped(int seconds_elapsed) seconds_elapsed >=0 ? "forward" : "backward"); control_event_general_status(LOG_WARN, "CLOCK_JUMPED TIME=%d", seconds_elapsed); - can_complete_circuit=0; /* so it'll log when it works again */ + /* so we log when it works again */ + note_that_we_maybe_cant_complete_circuits(); control_event_client_status(severity, "CIRCUIT_NOT_ESTABLISHED REASON=%s", "CLOCK_JUMPED"); circuit_mark_all_unused_circs(); @@ -1152,1516 +1238,6 @@ circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, return 0; } -/** The minimum number of circuit attempts before we start - * thinking about warning about path bias and dropping guards */ -static int -pathbias_get_min_circs(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_MIN_CIRC 150 - if (options->PathBiasCircThreshold >= 5) - return options->PathBiasCircThreshold; - else - return networkstatus_get_param(NULL, "pb_mincircs", - DFLT_PATH_BIAS_MIN_CIRC, - 5, INT32_MAX); -} - -/** The circuit success rate below which we issue a notice */ -static double -pathbias_get_notice_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_NOTICE_PCT 70 - if (options->PathBiasNoticeRate >= 0.0) - return options->PathBiasNoticeRate; - else - return networkstatus_get_param(NULL, "pb_noticepct", - DFLT_PATH_BIAS_NOTICE_PCT, 0, 100)/100.0; -} - -/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ -/** The circuit success rate below which we issue a warn */ -static double -pathbias_get_warn_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_WARN_PCT 50 - if (options->PathBiasWarnRate >= 0.0) - return options->PathBiasWarnRate; - else - return networkstatus_get_param(NULL, "pb_warnpct", - DFLT_PATH_BIAS_WARN_PCT, 0, 100)/100.0; -} - -/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ -/** - * The extreme rate is the rate at which we would drop the guard, - * if pb_dropguard is also set. Otherwise we just warn. - */ -double -pathbias_get_extreme_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_EXTREME_PCT 30 - if (options->PathBiasExtremeRate >= 0.0) - return options->PathBiasExtremeRate; - else - return networkstatus_get_param(NULL, "pb_extremepct", - DFLT_PATH_BIAS_EXTREME_PCT, 0, 100)/100.0; -} - -/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ -/** - * If 1, we actually disable use of guards that fall below - * the extreme_pct. - */ -int -pathbias_get_dropguards(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_DROP_GUARDS 0 - if (options->PathBiasDropGuards >= 0) - return options->PathBiasDropGuards; - else - return networkstatus_get_param(NULL, "pb_dropguards", - DFLT_PATH_BIAS_DROP_GUARDS, 0, 1); -} - -/** - * This is the number of circuits at which we scale our - * counts by mult_factor/scale_factor. Note, this count is - * not exact, as we only perform the scaling in the event - * of no integer truncation. - */ -static int -pathbias_get_scale_threshold(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_SCALE_THRESHOLD 300 - if (options->PathBiasScaleThreshold >= 10) - return options->PathBiasScaleThreshold; - else - return networkstatus_get_param(NULL, "pb_scalecircs", - DFLT_PATH_BIAS_SCALE_THRESHOLD, 10, - INT32_MAX); -} - -/** - * Compute the path bias scaling ratio from the consensus - * parameters pb_multfactor/pb_scalefactor. - * - * Returns a value in (0, 1.0] which we multiply our pathbias - * counts with to scale them down. - */ -static double -pathbias_get_scale_ratio(const or_options_t *options) -{ - /* - * The scale factor is the denominator for our scaling - * of circuit counts for our path bias window. - * - * Note that our use of doubles for the path bias state - * file means that powers of 2 work best here. - */ - int denominator = networkstatus_get_param(NULL, "pb_scalefactor", - 2, 2, INT32_MAX); - (void) options; - /** - * The mult factor is the numerator for our scaling - * of circuit counts for our path bias window. It - * allows us to scale by fractions. - */ - return networkstatus_get_param(NULL, "pb_multfactor", - 1, 1, denominator)/((double)denominator); -} - -/** The minimum number of circuit usage attempts before we start - * thinking about warning about path use bias and dropping guards */ -static int -pathbias_get_min_use(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_MIN_USE 20 - if (options->PathBiasUseThreshold >= 3) - return options->PathBiasUseThreshold; - else - return networkstatus_get_param(NULL, "pb_minuse", - DFLT_PATH_BIAS_MIN_USE, - 3, INT32_MAX); -} - -/** The circuit use success rate below which we issue a notice */ -static double -pathbias_get_notice_use_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_NOTICE_USE_PCT 80 - if (options->PathBiasNoticeUseRate >= 0.0) - return options->PathBiasNoticeUseRate; - else - return networkstatus_get_param(NULL, "pb_noticeusepct", - DFLT_PATH_BIAS_NOTICE_USE_PCT, - 0, 100)/100.0; -} - -/** - * The extreme use rate is the rate at which we would drop the guard, - * if pb_dropguard is also set. Otherwise we just warn. - */ -double -pathbias_get_extreme_use_rate(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_EXTREME_USE_PCT 60 - if (options->PathBiasExtremeUseRate >= 0.0) - return options->PathBiasExtremeUseRate; - else - return networkstatus_get_param(NULL, "pb_extremeusepct", - DFLT_PATH_BIAS_EXTREME_USE_PCT, - 0, 100)/100.0; -} - -/** - * This is the number of circuits at which we scale our - * use counts by mult_factor/scale_factor. Note, this count is - * not exact, as we only perform the scaling in the event - * of no integer truncation. - */ -static int -pathbias_get_scale_use_threshold(const or_options_t *options) -{ -#define DFLT_PATH_BIAS_SCALE_USE_THRESHOLD 100 - if (options->PathBiasScaleUseThreshold >= 10) - return options->PathBiasScaleUseThreshold; - else - return networkstatus_get_param(NULL, "pb_scaleuse", - DFLT_PATH_BIAS_SCALE_USE_THRESHOLD, - 10, INT32_MAX); -} - -/** - * Convert a Guard's path state to string. - */ -const char * -pathbias_state_to_string(path_state_t state) -{ - switch (state) { - case PATH_STATE_NEW_CIRC: - return "new"; - case PATH_STATE_BUILD_ATTEMPTED: - return "build attempted"; - case PATH_STATE_BUILD_SUCCEEDED: - return "build succeeded"; - case PATH_STATE_USE_ATTEMPTED: - return "use attempted"; - case PATH_STATE_USE_SUCCEEDED: - return "use succeeded"; - case PATH_STATE_USE_FAILED: - return "use failed"; - case PATH_STATE_ALREADY_COUNTED: - return "already counted"; - } - - return "unknown"; -} - -/** - * This function decides if a circuit has progressed far enough to count - * as a circuit "attempt". As long as end-to-end tagging is possible, - * we assume the adversary will use it over hop-to-hop failure. Therefore, - * we only need to account bias for the last hop. This should make us - * much more resilient to ambient circuit failure, and also make that - * failure easier to measure (we only need to measure Exit failure rates). - */ -static int -pathbias_is_new_circ_attempt(origin_circuit_t *circ) -{ -#define N2N_TAGGING_IS_POSSIBLE -#ifdef N2N_TAGGING_IS_POSSIBLE - /* cpath is a circular list. We want circs with more than one hop, - * and the second hop must be waiting for keys still (it's just - * about to get them). */ - return circ->cpath && - circ->cpath->next != circ->cpath && - circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; -#else - /* If tagging attacks are no longer possible, we probably want to - * count bias from the first hop. However, one could argue that - * timing-based tagging is still more useful than per-hop failure. - * In which case, we'd never want to use this. - */ - return circ->cpath && - circ->cpath->state == CPATH_STATE_AWAITING_KEYS; -#endif -} - -/** - * Decide if the path bias code should count a circuit. - * - * @returns 1 if we should count it, 0 otherwise. - */ -static int -pathbias_should_count(origin_circuit_t *circ) -{ -#define PATHBIAS_COUNT_INTERVAL (600) - static ratelim_t count_limit = - RATELIM_INIT(PATHBIAS_COUNT_INTERVAL); - char *rate_msg = NULL; - - /* We can't do path bias accounting without entry guards. - * Testing and controller circuits also have no guards. - * - * We also don't count server-side rends, because their - * endpoint could be chosen maliciously. - * Similarly, we can't count client-side intro attempts, - * because clients can be manipulated into connecting to - * malicious intro points. */ - if (get_options()->UseEntryGuards == 0 || - circ->base_.purpose == CIRCUIT_PURPOSE_TESTING || - circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER || - circ->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || - circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED || - (circ->base_.purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && - circ->base_.purpose <= CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { - - /* Check to see if the shouldcount result has changed due to a - * unexpected purpose change that would affect our results. - * - * The reason we check the path state too here is because for the - * cannibalized versions of these purposes, we count them as successful - * before their purpose change. - */ - if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED - && circ->path_state != PATH_STATE_ALREADY_COUNTED) { - log_info(LD_BUG, - "Circuit %d is now being ignored despite being counted " - "in the past. Purpose is %s, path state is %s", - circ->global_identifier, - circuit_purpose_to_string(circ->base_.purpose), - pathbias_state_to_string(circ->path_state)); - } - circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; - return 0; - } - - /* Completely ignore one hop circuits */ - if (circ->build_state->onehop_tunnel || - circ->build_state->desired_path_len == 1) { - /* Check for inconsistency */ - if (circ->build_state->desired_path_len != 1 || - !circ->build_state->onehop_tunnel) { - if ((rate_msg = rate_limit_log(&count_limit, approx_time()))) { - log_info(LD_BUG, - "One-hop circuit has length %d. Path state is %s. " - "Circuit is a %s currently %s.%s", - circ->build_state->desired_path_len, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - tor_fragile_assert(); - } - - /* Check to see if the shouldcount result has changed due to a - * unexpected change that would affect our results */ - if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_COUNTED) { - log_info(LD_BUG, - "One-hop circuit %d is now being ignored despite being counted " - "in the past. Purpose is %s, path state is %s", - circ->global_identifier, - circuit_purpose_to_string(circ->base_.purpose), - pathbias_state_to_string(circ->path_state)); - } - circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED; - return 0; - } - - /* Check to see if the shouldcount result has changed due to a - * unexpected purpose change that would affect our results */ - if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_IGNORED) { - log_info(LD_BUG, - "Circuit %d is now being counted despite being ignored " - "in the past. Purpose is %s, path state is %s", - circ->global_identifier, - circuit_purpose_to_string(circ->base_.purpose), - pathbias_state_to_string(circ->path_state)); - } - circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_COUNTED; - - return 1; -} - -/** - * Check our circuit state to see if this is a successful circuit attempt. - * If so, record it in the current guard's path bias circ_attempt count. - * - * Also check for several potential error cases for bug #6475. - */ -static int -pathbias_count_build_attempt(origin_circuit_t *circ) -{ -#define CIRC_ATTEMPT_NOTICE_INTERVAL (600) - static ratelim_t circ_attempt_notice_limit = - RATELIM_INIT(CIRC_ATTEMPT_NOTICE_INTERVAL); - char *rate_msg = NULL; - - if (!pathbias_should_count(circ)) { - return 0; - } - - if (pathbias_is_new_circ_attempt(circ)) { - /* Help track down the real cause of bug #6475: */ - if (circ->has_opened && circ->path_state != PATH_STATE_BUILD_ATTEMPTED) { - if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Opened circuit is in strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - - /* Don't re-count cannibalized circs.. */ - if (!circ->has_opened) { - entry_guard_t *guard = NULL; - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } else if (circ->base_.n_chan) { - guard = - entry_guard_get_by_id_digest(circ->base_.n_chan->identity_digest); - } - - if (guard) { - if (circ->path_state == PATH_STATE_NEW_CIRC) { - circ->path_state = PATH_STATE_BUILD_ATTEMPTED; - - if (entry_guard_inc_circ_attempt_count(guard) < 0) { - /* Bogus guard; we already warned. */ - return -END_CIRC_REASON_TORPROTOCOL; - } - } else { - if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Unopened circuit has strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } else { - if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, - approx_time()))) { - log_info(LD_CIRC, - "Unopened circuit has no known guard. " - "Circuit is a %s currently %s.%s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } - } - - return 0; -} - -/** - * Check our circuit state to see if this is a successful circuit - * completion. If so, record it in the current guard's path bias - * success count. - * - * Also check for several potential error cases for bug #6475. - */ -static void -pathbias_count_build_success(origin_circuit_t *circ) -{ -#define SUCCESS_NOTICE_INTERVAL (600) - static ratelim_t success_notice_limit = - RATELIM_INIT(SUCCESS_NOTICE_INTERVAL); - char *rate_msg = NULL; - entry_guard_t *guard = NULL; - - if (!pathbias_should_count(circ)) { - return; - } - - /* Don't count cannibalized/reused circs for path bias - * "build" success, since they get counted under "use" success. */ - if (!circ->has_opened) { - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) { - circ->path_state = PATH_STATE_BUILD_SUCCEEDED; - guard->circ_successes++; - entry_guards_changed(); - - log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } else { - if ((rate_msg = rate_limit_log(&success_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Succeeded circuit is in strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - - if (guard->circ_attempts < guard->circ_successes) { - log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " - "for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - if ((rate_msg = rate_limit_log(&success_notice_limit, - approx_time()))) { - log_info(LD_CIRC, - "Completed circuit has no known guard. " - "Circuit is a %s currently %s.%s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } else { - if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { - if ((rate_msg = rate_limit_log(&success_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "Opened circuit is in strange path state %s. " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state), - rate_msg); - tor_free(rate_msg); - } - } - } -} - -/** - * Record an attempt to use a circuit. Changes the circuit's - * path state and update its guard's usage counter. - * - * Used for path bias usage accounting. - */ -void -pathbias_count_use_attempt(origin_circuit_t *circ) -{ - entry_guard_t *guard; - - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { - log_notice(LD_BUG, - "Used circuit is in strange path state %s. " - "Circuit is a %s currently %s.", - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - if (guard) { - pathbias_measure_use_rate(guard); - pathbias_scale_use_rates(guard); - guard->use_attempts++; - entry_guards_changed(); - - log_debug(LD_CIRC, - "Marked circuit %d (%f/%f) as used for guard %s ($%s).", - circ->global_identifier, - guard->use_successes, guard->use_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } - - circ->path_state = PATH_STATE_USE_ATTEMPTED; - } else { - /* Harmless but educational log message */ - log_info(LD_CIRC, - "Used circuit %d is already in path state %s. " - "Circuit is a %s currently %s.", - circ->global_identifier, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } - - return; -} - -/** - * Check the circuit's path state is appropriate and mark it as - * successfully used. Used for path bias usage accounting. - * - * We don't actually increment the guard's counters until - * pathbias_check_close(), because the circuit can still transition - * back to PATH_STATE_USE_ATTEMPTED if a stream fails later (this - * is done so we can probe the circuit for liveness at close). - */ -void -pathbias_mark_use_success(origin_circuit_t *circ) -{ - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { - log_notice(LD_BUG, - "Used circuit %d is in strange path state %s. " - "Circuit is a %s currently %s.", - circ->global_identifier, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - - pathbias_count_use_attempt(circ); - } - - /* We don't do any accounting at the guard until actual circuit close */ - circ->path_state = PATH_STATE_USE_SUCCEEDED; - - return; -} - -/** - * If a stream ever detatches from a circuit in a retriable way, - * we need to mark this circuit as still needing either another - * successful stream, or in need of a probe. - * - * An adversary could let the first stream request succeed (ie the - * resolve), but then tag and timeout the remainder (via cell - * dropping), forcing them on new circuits. - * - * Rolling back the state will cause us to probe such circuits, which - * should lead to probe failures in the event of such tagging due to - * either unrecognized cells coming in while we wait for the probe, - * or the cipher state getting out of sync in the case of dropped cells. - */ -void -pathbias_mark_use_rollback(origin_circuit_t *circ) -{ - if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { - log_info(LD_CIRC, - "Rolling back pathbias use state to 'attempted' for detached " - "circuit %d", circ->global_identifier); - circ->path_state = PATH_STATE_USE_ATTEMPTED; - } -} - -/** - * Actually count a circuit success towards a guard's usage counters - * if the path state is appropriate. - */ -static void -pathbias_count_use_success(origin_circuit_t *circ) -{ - entry_guard_t *guard; - - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->path_state != PATH_STATE_USE_SUCCEEDED) { - log_notice(LD_BUG, - "Successfully used circuit %d is in strange path state %s. " - "Circuit is a %s currently %s.", - circ->global_identifier, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } else { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - if (guard) { - guard->use_successes++; - entry_guards_changed(); - - if (guard->use_attempts < guard->use_successes) { - log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) " - "for guard %s=%s", - guard->use_successes, guard->use_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - } - - log_debug(LD_CIRC, - "Marked circuit %d (%f/%f) as used successfully for guard " - "%s ($%s).", - circ->global_identifier, guard->use_successes, - guard->use_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - } - } - - return; -} - -/** - * Send a probe down a circuit that the client attempted to use, - * but for which the stream timed out/failed. The probe is a - * RELAY_BEGIN cell with a 0.a.b.c destination address, which - * the exit will reject and reply back, echoing that address. - * - * The reason for such probes is because it is possible to bias - * a user's paths simply by causing timeouts, and these timeouts - * are not possible to differentiate from unresponsive servers. - * - * The probe is sent at the end of the circuit lifetime for two - * reasons: to prevent cryptographic taggers from being able to - * drop cells to cause timeouts, and to prevent easy recognition - * of probes before any real client traffic happens. - * - * Returns -1 if we couldn't probe, 0 otherwise. - */ -static int -pathbias_send_usable_probe(circuit_t *circ) -{ - /* Based on connection_ap_handshake_send_begin() */ - char payload[CELL_PAYLOAD_SIZE]; - int payload_len; - origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - crypt_path_t *cpath_layer = NULL; - char *probe_nonce = NULL; - - tor_assert(ocirc); - - cpath_layer = ocirc->cpath->prev; - - if (cpath_layer->state != CPATH_STATE_OPEN) { - /* This can happen for cannibalized circuits. Their - * last hop isn't yet open */ - log_info(LD_CIRC, - "Got pathbias probe request for unopened circuit %d. " - "Opened %d, len %d", ocirc->global_identifier, - ocirc->has_opened, ocirc->build_state->desired_path_len); - return -1; - } - - /* We already went down this road. */ - if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING && - ocirc->pathbias_probe_id) { - log_info(LD_CIRC, - "Got pathbias probe request for circuit %d with " - "outstanding probe", ocirc->global_identifier); - return -1; - } - - /* Can't probe if the channel isn't open */ - if (circ->n_chan == NULL || - (circ->n_chan->state != CHANNEL_STATE_OPEN - && circ->n_chan->state != CHANNEL_STATE_MAINT)) { - log_info(LD_CIRC, - "Skipping pathbias probe for circuit %d: Channel is not open.", - ocirc->global_identifier); - return -1; - } - - circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); - - /* Update timestamp for when circuit_expire_building() should kill us */ - tor_gettimeofday(&circ->timestamp_began); - - /* Generate a random address for the nonce */ - crypto_rand((char*)ô->pathbias_probe_nonce, - sizeof(ocirc->pathbias_probe_nonce)); - ocirc->pathbias_probe_nonce &= 0x00ffffff; - probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce); - - tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce); - payload_len = (int)strlen(payload)+1; - - // XXX: need this? Can we assume ipv4 will always be supported? - // If not, how do we tell? - //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { - // set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); - // payload_len += 4; - //} - - /* Generate+Store stream id, make sure it's non-zero */ - ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc); - - if (ocirc->pathbias_probe_id==0) { - log_warn(LD_CIRC, - "Ran out of stream IDs on circuit %u during " - "pathbias probe attempt.", ocirc->global_identifier); - tor_free(probe_nonce); - return -1; - } - - log_info(LD_CIRC, - "Sending pathbias testing cell to %s:25 on stream %d for circ %d.", - probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier); - tor_free(probe_nonce); - - /* Send a test relay cell */ - if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ, - RELAY_COMMAND_BEGIN, payload, - payload_len, cpath_layer) < 0) { - log_notice(LD_CIRC, - "Failed to send pathbias probe cell on circuit %d.", - ocirc->global_identifier); - return -1; - } - - /* Mark it freshly dirty so it doesn't get expired in the meantime */ - circ->timestamp_dirty = time(NULL); - - return 0; -} - -/** - * Check the response to a pathbias probe, to ensure the - * cell is recognized and the nonce and other probe - * characteristics are as expected. - * - * If the response is valid, return 0. Otherwise return < 0. - */ -int -pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) -{ - /* Based on connection_edge_process_relay_cell() */ - relay_header_t rh; - int reason; - uint32_t ipv4_host; - origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - - tor_assert(cell); - tor_assert(ocirc); - tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING); - - relay_header_unpack(&rh, cell->payload); - - reason = rh.length > 0 ? - get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC; - - if (rh.command == RELAY_COMMAND_END && - reason == END_STREAM_REASON_EXITPOLICY && - ocirc->pathbias_probe_id == rh.stream_id) { - - /* Check length+extract host: It is in network order after the reason code. - * See connection_edge_end(). */ - if (rh.length < 9) { /* reason+ipv4+dns_ttl */ - log_notice(LD_PROTOCOL, - "Short path bias probe response length field (%d).", rh.length); - return - END_CIRC_REASON_TORPROTOCOL; - } - - ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); - - /* Check nonce */ - if (ipv4_host == ocirc->pathbias_probe_nonce) { - pathbias_mark_use_success(ocirc); - circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); - log_info(LD_CIRC, - "Got valid path bias probe back for circ %d, stream %d.", - ocirc->global_identifier, ocirc->pathbias_probe_id); - return 0; - } else { - log_notice(LD_CIRC, - "Got strange probe value 0x%x vs 0x%x back for circ %d, " - "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce, - ocirc->global_identifier, ocirc->pathbias_probe_id); - return -1; - } - } - log_info(LD_CIRC, - "Got another cell back back on pathbias probe circuit %d: " - "Command: %d, Reason: %d, Stream-id: %d", - ocirc->global_identifier, rh.command, reason, rh.stream_id); - return -1; -} - -/** - * Check if a circuit was used and/or closed successfully. - * - * If we attempted to use the circuit to carry a stream but failed - * for whatever reason, or if the circuit mysteriously died before - * we could attach any streams, record these two cases. - * - * If we *have* successfully used the circuit, or it appears to - * have been closed by us locally, count it as a success. - * - * Returns 0 if we're done making decisions with the circ, - * or -1 if we want to probe it first. - */ -int -pathbias_check_close(origin_circuit_t *ocirc, int reason) -{ - circuit_t *circ = ô->base_; - - if (!pathbias_should_count(ocirc)) { - return 0; - } - - switch (ocirc->path_state) { - /* If the circuit was closed after building, but before use, we need - * to ensure we were the ones who tried to close it (and not a remote - * actor). */ - case PATH_STATE_BUILD_SUCCEEDED: - if (reason & END_CIRC_REASON_FLAG_REMOTE) { - /* Remote circ close reasons on an unused circuit all could be bias */ - log_info(LD_CIRC, - "Circuit %d remote-closed without successful use for reason %d. " - "Circuit purpose %d currently %d,%s. Len %d.", - ocirc->global_identifier, - reason, circ->purpose, ocirc->has_opened, - circuit_state_to_string(circ->state), - ocirc->build_state->desired_path_len); - pathbias_count_collapse(ocirc); - } else if ((reason & ~END_CIRC_REASON_FLAG_REMOTE) - == END_CIRC_REASON_CHANNEL_CLOSED && - circ->n_chan && - circ->n_chan->reason_for_closing - != CHANNEL_CLOSE_REQUESTED) { - /* If we didn't close the channel ourselves, it could be bias */ - /* XXX: Only count bias if the network is live? - * What about clock jumps/suspends? */ - log_info(LD_CIRC, - "Circuit %d's channel closed without successful use for reason " - "%d, channel reason %d. Circuit purpose %d currently %d,%s. Len " - "%d.", ocirc->global_identifier, - reason, circ->n_chan->reason_for_closing, - circ->purpose, ocirc->has_opened, - circuit_state_to_string(circ->state), - ocirc->build_state->desired_path_len); - pathbias_count_collapse(ocirc); - } else { - pathbias_count_successful_close(ocirc); - } - break; - - /* If we tried to use a circuit but failed, we should probe it to ensure - * it has not been tampered with. */ - case PATH_STATE_USE_ATTEMPTED: - /* XXX: Only probe and/or count failure if the network is live? - * What about clock jumps/suspends? */ - if (pathbias_send_usable_probe(circ) == 0) - return -1; - else - pathbias_count_use_failed(ocirc); - - /* Any circuit where there were attempted streams but no successful - * streams could be bias */ - log_info(LD_CIRC, - "Circuit %d closed without successful use for reason %d. " - "Circuit purpose %d currently %d,%s. Len %d.", - ocirc->global_identifier, - reason, circ->purpose, ocirc->has_opened, - circuit_state_to_string(circ->state), - ocirc->build_state->desired_path_len); - break; - - case PATH_STATE_USE_SUCCEEDED: - pathbias_count_successful_close(ocirc); - pathbias_count_use_success(ocirc); - break; - - case PATH_STATE_USE_FAILED: - pathbias_count_use_failed(ocirc); - break; - - case PATH_STATE_NEW_CIRC: - case PATH_STATE_BUILD_ATTEMPTED: - case PATH_STATE_ALREADY_COUNTED: - default: - // Other states are uninteresting. No stats to count. - break; - } - - ocirc->path_state = PATH_STATE_ALREADY_COUNTED; - - return 0; -} - -/** - * Count a successfully closed circuit. - */ -static void -pathbias_count_successful_close(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - /* In the long run: circuit_success ~= successful_circuit_close + - * circ_failure + stream_failure */ - guard->successful_circuits_closed++; - entry_guards_changed(); - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - log_info(LD_CIRC, - "Successfully closed circuit has no known guard. " - "Circuit is a %s currently %s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } -} - -/** - * Count a circuit that fails after it is built, but before it can - * carry any traffic. - * - * This is needed because there are ways to destroy a - * circuit after it has successfully completed. Right now, this is - * used for purely informational/debugging purposes. - */ -static void -pathbias_count_collapse(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - guard->collapsed_circuits++; - entry_guards_changed(); - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - log_info(LD_CIRC, - "Destroyed circuit has no known guard. " - "Circuit is a %s currently %s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } -} - -/** - * Count a known failed circuit (because we could not probe it). - * - * This counter is informational. - */ -static void -pathbias_count_use_failed(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - if (!pathbias_should_count(circ)) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - guard->unusable_circuits++; - entry_guards_changed(); - } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to - * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. - * No need to log that case. */ - /* XXX note cut-and-paste code in this function compared to nearby - * functions. Would be nice to refactor. -RD */ - log_info(LD_CIRC, - "Stream-failing circuit has no known guard. " - "Circuit is a %s currently %s", - circuit_purpose_to_string(circ->base_.purpose), - circuit_state_to_string(circ->base_.state)); - } -} - -/** - * Count timeouts for path bias log messages. - * - * These counts are purely informational. - */ -void -pathbias_count_timeout(origin_circuit_t *circ) -{ - entry_guard_t *guard = NULL; - - if (!pathbias_should_count(circ)) { - return; - } - - /* For hidden service circs, they can actually be used - * successfully and then time out later (because - * the other side declines to use them). */ - if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { - return; - } - - if (circ->cpath && circ->cpath->extend_info) { - guard = entry_guard_get_by_id_digest( - circ->cpath->extend_info->identity_digest); - } - - if (guard) { - guard->timeouts++; - entry_guards_changed(); - } -} - -/** - * Helper function to count all of the currently opened circuits - * for a guard that are in a given path state range. The state - * range is inclusive on both ends. - */ -static int -pathbias_count_circs_in_states(entry_guard_t *guard, - path_state_t from, - path_state_t to) -{ - circuit_t *circ; - int open_circuits = 0; - - /* Count currently open circuits. Give them the benefit of the doubt. */ - for (circ = global_circuitlist; circ; circ = circ->next) { - origin_circuit_t *ocirc = NULL; - if (!CIRCUIT_IS_ORIGIN(circ) || /* didn't originate here */ - circ->marked_for_close) /* already counted */ - continue; - - ocirc = TO_ORIGIN_CIRCUIT(circ); - - if (!ocirc->cpath || !ocirc->cpath->extend_info) - continue; - - if (ocirc->path_state >= from && - ocirc->path_state <= to && - pathbias_should_count(ocirc) && - fast_memeq(guard->identity, - ocirc->cpath->extend_info->identity_digest, - DIGEST_LEN)) { - log_debug(LD_CIRC, "Found opened circuit %d in path_state %s", - ocirc->global_identifier, - pathbias_state_to_string(ocirc->path_state)); - open_circuits++; - } - } - - return open_circuits; -} - -/** - * Return the number of circuits counted as successfully closed for - * this guard. - * - * Also add in the currently open circuits to give them the benefit - * of the doubt. - */ -double -pathbias_get_close_success_count(entry_guard_t *guard) -{ - return guard->successful_circuits_closed + - pathbias_count_circs_in_states(guard, - PATH_STATE_BUILD_SUCCEEDED, - PATH_STATE_USE_SUCCEEDED); -} - -/** - * Return the number of circuits counted as successfully used - * this guard. - * - * Also add in the currently open circuits that we are attempting - * to use to give them the benefit of the doubt. - */ -double -pathbias_get_use_success_count(entry_guard_t *guard) -{ - return guard->use_successes + - pathbias_count_circs_in_states(guard, - PATH_STATE_USE_ATTEMPTED, - PATH_STATE_USE_SUCCEEDED); -} - -/** - * Check the path bias use rate against our consensus parameter limits. - * - * Emits a log message if the use success rates are too low. - * - * If pathbias_get_dropguards() is set, we also disable the use of - * very failure prone guards. - */ -static void -pathbias_measure_use_rate(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - if (guard->use_attempts > pathbias_get_min_use(options)) { - /* Note: We rely on the < comparison here to allow us to set a 0 - * rate and disable the feature entirely. If refactoring, don't - * change to <= */ - if (pathbias_get_use_success_count(guard)/guard->use_attempts - < pathbias_get_extreme_use_rate(options)) { - /* Dropping is currently disabled by default. */ - if (pathbias_get_dropguards(options)) { - if (!guard->path_bias_disabled) { - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing to carry an extremely large " - "amount of stream on its circuits. " - "To avoid potential route manipulation attacks, Tor has " - "disabled use of this guard. " - "Use counts are %ld/%ld. Success counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - guard->path_bias_disabled = 1; - guard->bad_since = approx_time(); - entry_guards_changed(); - return; - } - } else if (!guard->path_bias_use_extreme) { - guard->path_bias_use_extreme = 1; - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing to carry an extremely large " - "amount of streams on its circuits. " - "This could indicate a route manipulation attack, network " - "overload, bad local network connectivity, or a bug. " - "Use counts are %ld/%ld. Success counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } else if (pathbias_get_use_success_count(guard)/guard->use_attempts - < pathbias_get_notice_use_rate(options)) { - if (!guard->path_bias_use_noticed) { - guard->path_bias_use_noticed = 1; - log_notice(LD_CIRC, - "Your Guard %s ($%s) is failing to carry more streams on its " - "circuits than usual. " - "Most likely this means the Tor network is overloaded " - "or your network connection is poor. " - "Use counts are %ld/%ld. Success counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } - } -} - -/** - * Check the path bias circuit close status rates against our consensus - * parameter limits. - * - * Emits a log message if the use success rates are too low. - * - * If pathbias_get_dropguards() is set, we also disable the use of - * very failure prone guards. - * - * XXX: This function shares similar log messages and checks to - * pathbias_measure_use_rate(). It may be possible to combine them - * eventually, especially if we can ever remove the need for 3 - * levels of closure warns (if the overall circuit failure rate - * goes down with ntor). One way to do so would be to multiply - * the build rate with the use rate to get an idea of the total - * fraction of the total network paths the user is able to use. - * See ticket #8159. - */ -static void -pathbias_measure_close_rate(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - if (guard->circ_attempts > pathbias_get_min_circs(options)) { - /* Note: We rely on the < comparison here to allow us to set a 0 - * rate and disable the feature entirely. If refactoring, don't - * change to <= */ - if (pathbias_get_close_success_count(guard)/guard->circ_attempts - < pathbias_get_extreme_rate(options)) { - /* Dropping is currently disabled by default. */ - if (pathbias_get_dropguards(options)) { - if (!guard->path_bias_disabled) { - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing an extremely large " - "amount of circuits. " - "To avoid potential route manipulation attacks, Tor has " - "disabled use of this guard. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - guard->path_bias_disabled = 1; - guard->bad_since = approx_time(); - entry_guards_changed(); - return; - } - } else if (!guard->path_bias_extreme) { - guard->path_bias_extreme = 1; - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing an extremely large " - "amount of circuits. " - "This could indicate a route manipulation attack, " - "extreme network overload, or a bug. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts - < pathbias_get_warn_rate(options)) { - if (!guard->path_bias_warned) { - guard->path_bias_warned = 1; - log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing a very large " - "amount of circuits. " - "Most likely this means the Tor network is " - "overloaded, but it could also mean an attack against " - "you or potentially the guard itself. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts - < pathbias_get_notice_rate(options)) { - if (!guard->path_bias_noticed) { - guard->path_bias_noticed = 1; - log_notice(LD_CIRC, - "Your Guard %s ($%s) is failing more circuits than " - "usual. " - "Most likely this means the Tor network is overloaded. " - "Success counts are %ld/%ld. Use counts are %ld/%ld. " - "%ld circuits completed, %ld were unusable, %ld collapsed, " - "and %ld timed out. " - "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); - } - } - } -} - -/** - * This function scales the path bias use rates if we have - * more data than the scaling threshold. This allows us to - * be more sensitive to recent measurements. - * - * XXX: The attempt count transfer stuff here might be done - * better by keeping separate pending counters that get - * transfered at circuit close. See ticket #8160. - */ -static void -pathbias_scale_close_rates(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - /* If we get a ton of circuits, just scale everything down */ - if (guard->circ_attempts > pathbias_get_scale_threshold(options)) { - double scale_ratio = pathbias_get_scale_ratio(options); - int opened_attempts = pathbias_count_circs_in_states(guard, - PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED); - int opened_built = pathbias_count_circs_in_states(guard, - PATH_STATE_BUILD_SUCCEEDED, - PATH_STATE_USE_FAILED); - /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->circ_attempts >= guard->circ_successes); - - guard->circ_attempts -= (opened_attempts+opened_built); - guard->circ_successes -= opened_built; - - guard->circ_attempts *= scale_ratio; - guard->circ_successes *= scale_ratio; - guard->timeouts *= scale_ratio; - guard->successful_circuits_closed *= scale_ratio; - guard->collapsed_circuits *= scale_ratio; - guard->unusable_circuits *= scale_ratio; - - guard->circ_attempts += (opened_attempts+opened_built); - guard->circ_successes += opened_built; - - entry_guards_changed(); - - log_info(LD_CIRC, - "Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard " - "%s ($%s)", - guard->circ_successes, guard->successful_circuits_closed, - guard->circ_attempts, opened_built, opened_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - - /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->circ_attempts < guard->circ_successes) { - log_notice(LD_BUG, - "Scaling has mangled pathbias counts to %f/%f (%d/%d open) " - "for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, opened_built, - opened_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - } - } -} - -/** - * This function scales the path bias circuit close rates if we have - * more data than the scaling threshold. This allows us to be more - * sensitive to recent measurements. - * - * XXX: The attempt count transfer stuff here might be done - * better by keeping separate pending counters that get - * transfered at circuit close. See ticket #8160. - */ -void -pathbias_scale_use_rates(entry_guard_t *guard) -{ - const or_options_t *options = get_options(); - - /* If we get a ton of circuits, just scale everything down */ - if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) { - double scale_ratio = pathbias_get_scale_ratio(options); - int opened_attempts = pathbias_count_circs_in_states(guard, - PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); - /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->use_attempts >= guard->use_successes); - - guard->use_attempts -= opened_attempts; - - guard->use_attempts *= scale_ratio; - guard->use_successes *= scale_ratio; - - guard->use_attempts += opened_attempts; - - log_info(LD_CIRC, - "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)", - guard->use_successes, guard->use_attempts, opened_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); - - /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->use_attempts < guard->use_successes) { - log_notice(LD_BUG, - "Scaling has mangled pathbias usage counts to %f/%f " - "(%d open) for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, - opened_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - } - - entry_guards_changed(); - } -} - -/** Increment the number of times we successfully extended a circuit to - * <b>guard</b>, first checking if the failure rate is high enough that - * we should eliminate the guard. Return -1 if the guard looks no good; - * return 0 if the guard looks fine. - */ -static int -entry_guard_inc_circ_attempt_count(entry_guard_t *guard) -{ - entry_guards_changed(); - - pathbias_measure_close_rate(guard); - - if (guard->path_bias_disabled) - return -1; - - pathbias_scale_close_rates(guard); - guard->circ_attempts++; - - log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - return 0; -} - /** A "created" cell <b>reply</b> came back to us on circuit <b>circ</b>. * (The body of <b>reply</b> varies depending on what sort of handshake * this is.) @@ -2679,8 +1255,10 @@ circuit_finish_handshake(origin_circuit_t *circ, crypt_path_t *hop; int rv; - if ((rv = pathbias_count_build_attempt(circ)) < 0) + if ((rv = pathbias_count_build_attempt(circ)) < 0) { + log_warn(LD_CIRC, "pathbias_count_build_attempt failed: %d", rv); return rv; + } if (circ->cpath->state == CPATH_STATE_AWAITING_KEYS) { hop = circ->cpath; @@ -2694,12 +1272,15 @@ circuit_finish_handshake(origin_circuit_t *circ, tor_assert(hop->state == CPATH_STATE_AWAITING_KEYS); { + const char *msg = NULL; if (onion_skin_client_handshake(hop->handshake_state.tag, &hop->handshake_state, reply->reply, reply->handshake_len, (uint8_t*)keys, sizeof(keys), - (uint8_t*)hop->rend_circ_nonce) < 0) { - log_warn(LD_CIRC,"onion_skin_client_handshake failed."); + (uint8_t*)hop->rend_circ_nonce, + &msg) < 0) { + if (msg) + log_warn(LD_CIRC,"onion_skin_client_handshake failed: %s", msg); return -END_CIRC_REASON_TORPROTOCOL; } } @@ -2815,8 +1396,10 @@ onionskin_answer(or_circuit_t *circ, log_debug(LD_CIRC,"Finished sending '%s' cell.", circ->is_first_hop ? "created_fast" : "created"); - if (!channel_is_local(circ->p_chan) && - !channel_is_outgoing(circ->p_chan)) { + /* Ignore the local bit when testing - many test networks run on local + * addresses */ + if ((!channel_is_local(circ->p_chan) || get_options()->TestingTorNetwork) + && !channel_is_outgoing(circ->p_chan)) { /* record that we could process create cells from a non-local conn * that we didn't initiate; presumably this means that create cells * can reach us too. */ @@ -2830,11 +1413,7 @@ onionskin_answer(or_circuit_t *circ, * number of endpoints that would give something away about our destination. * * If the routerlist <b>nodes</b> doesn't have enough routers - * to handle the desired path length, return as large a path length as - * is feasible, except if it's less than 2, in which case return -1. - * XXX ^^ I think this behavior is a hold-over from back when we had only a - * few relays in the network, and certainly back before guards existed. - * We should very likely get rid of it. -RD + * to handle the desired path length, return -1. */ static int new_route_len(uint8_t purpose, extend_info_t *exit, smartlist_t *nodes) @@ -2855,19 +1434,13 @@ new_route_len(uint8_t purpose, extend_info_t *exit, smartlist_t *nodes) log_debug(LD_CIRC,"Chosen route length %d (%d/%d routers suitable).", routelen, num_acceptable_routers, smartlist_len(nodes)); - if (num_acceptable_routers < 2) { + if (num_acceptable_routers < routelen) { log_info(LD_CIRC, - "Not enough acceptable routers (%d). Discarding this circuit.", - num_acceptable_routers); + "Not enough acceptable routers (%d/%d). Discarding this circuit.", + num_acceptable_routers, routelen); return -1; } - if (num_acceptable_routers < routelen) { - log_info(LD_CIRC,"Not enough routers: cutting routelen from %d to %d.", - routelen, num_acceptable_routers); - routelen = num_acceptable_routers; - } - return routelen; } @@ -2997,7 +1570,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) * -1 means "Don't use this router at all." */ the_nodes = nodelist_get_list(); - n_supported = tor_malloc(sizeof(int)*smartlist_len(the_nodes)); + n_supported = tor_calloc(smartlist_len(the_nodes), sizeof(int)); SMARTLIST_FOREACH_BEGIN(the_nodes, const node_t *, node) { const int i = node_sl_idx; if (router_digest_is_me(node->identity)) { @@ -3168,6 +1741,83 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) return NULL; } +#if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) +/* The config option Tor2webRendezvousPoints has been set and we need + * to pick an RP out of that set. Make sure that the RP we choose is + * alive, and return it. Return NULL if no usable RP could be found in + * Tor2webRendezvousPoints. */ +STATIC const node_t * +pick_tor2web_rendezvous_node(router_crn_flags_t flags, + const or_options_t *options) +{ + const node_t *rp_node = NULL; + const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0; + const int need_desc = (flags & CRN_NEED_DESC) != 0; + + smartlist_t *whitelisted_live_rps = smartlist_new(); + smartlist_t *all_live_nodes = smartlist_new(); + + tor_assert(options->Tor2webRendezvousPoints); + + /* Add all running nodes to all_live_nodes */ + router_add_running_nodes_to_smartlist(all_live_nodes, + allow_invalid, + 0, 0, 0, + need_desc); + + /* Filter all_live_nodes to only add live *and* whitelisted RPs to + * the list whitelisted_live_rps. */ + SMARTLIST_FOREACH_BEGIN(all_live_nodes, node_t *, live_node) { + if (routerset_contains_node(options->Tor2webRendezvousPoints, live_node)) { + smartlist_add(whitelisted_live_rps, live_node); + } + } SMARTLIST_FOREACH_END(live_node); + + /* Honor ExcludeNodes */ + if (options->ExcludeNodes) { + routerset_subtract_nodes(whitelisted_live_rps, options->ExcludeNodes); + } + + /* Now pick randomly amongst the whitelisted RPs. No need to waste time + doing bandwidth load balancing, for most use cases + 'whitelisted_live_rps' contains a single OR anyway. */ + rp_node = smartlist_choose(whitelisted_live_rps); + + if (!rp_node) { + log_warn(LD_REND, "Could not find a Rendezvous Point that suits " + "the purposes of Tor2webRendezvousPoints. Choosing random one."); + } + + smartlist_free(whitelisted_live_rps); + smartlist_free(all_live_nodes); + + return rp_node; +} +#endif + +/* Pick a Rendezvous Point for our HS circuits according to <b>flags</b>. */ +static const node_t * +pick_rendezvous_node(router_crn_flags_t flags) +{ + const or_options_t *options = get_options(); + + if (options->AllowInvalid_ & ALLOW_INVALID_RENDEZVOUS) + flags |= CRN_ALLOW_INVALID; + +#ifdef ENABLE_TOR2WEB_MODE + /* The user wants us to pick specific RPs. */ + if (options->Tor2webRendezvousPoints) { + const node_t *tor2web_rp = pick_tor2web_rendezvous_node(flags, options); + if (tor2web_rp) { + return tor2web_rp; + } + /* Else, if no tor2web RP was found, fall back to choosing a random node */ + } +#endif + + return router_choose_random_node(NULL, options->ExcludeNodes, flags); +} + /** Return a pointer to a suitable router to be the exit node for the * circuit of purpose <b>purpose</b> that we're about to build (or NULL * if no router is suitable). @@ -3198,9 +1848,13 @@ choose_good_exit_server(uint8_t purpose, else 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; - return router_choose_random_node(NULL, options->ExcludeNodes, flags); + { + /* Pick a new RP */ + const node_t *rendezvous_node = pick_rendezvous_node(flags); + log_info(LD_REND, "Picked new RP: %s", + safe_str_client(node_describe(rendezvous_node))); + return rendezvous_node; + } } log_warn(LD_BUG,"Unhandled purpose %d", purpose); tor_fragile_assert(); @@ -3310,7 +1964,7 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit) choose_good_exit_server(circ->base_.purpose, state->need_uptime, state->need_capacity, state->is_internal); if (!node) { - log_warn(LD_CIRC,"failed to choose an exit server"); + log_warn(LD_CIRC,"Failed to choose an exit server"); return -1; } exit = extend_info_from_node(node, 0); @@ -3437,7 +2091,8 @@ choose_good_middle_server(uint8_t purpose, tor_assert(CIRCUIT_PURPOSE_MIN_ <= purpose && purpose <= CIRCUIT_PURPOSE_MAX_); - log_debug(LD_CIRC, "Contemplating intermediate hop: random choice."); + log_debug(LD_CIRC, "Contemplating intermediate hop %d: random choice.", + cur_len); excluded = smartlist_new(); if ((r = build_state_get_exit_node(state))) { nodelist_add_node_and_family(excluded, r); @@ -3499,9 +2154,18 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) smartlist_add(excluded, (void*)node); }); } - /* and exclude current entry guards and their families, if applicable */ + /* and exclude current entry guards and their families, + * unless we're in a test network, and excluding guards + * would exclude all nodes (i.e. we're in an incredibly small tor network, + * or we're using TestingAuthVoteGuard *). + * This is an incomplete fix, but is no worse than the previous behaviour, + * and only applies to minimal, testing tor networks + * (so it's no less secure) */ /*XXXX025 use the using_as_guard flag to accomplish this.*/ - if (options->UseEntryGuards) { + if (options->UseEntryGuards + && (!options->TestingTorNetwork || + smartlist_len(nodelist_get_list()) > smartlist_len(get_entry_guards()) + )) { SMARTLIST_FOREACH(get_entry_guards(), const entry_guard_t *, entry, { if ((node = node_get_by_id(entry->identity))) { @@ -3631,13 +2295,9 @@ extend_info_new(const char *nickname, const char *digest, strlcpy(info->nickname, nickname, sizeof(info->nickname)); if (onion_key) info->onion_key = crypto_pk_dup_key(onion_key); -#ifdef CURVE25519_ENABLED if (curve25519_key) memcpy(&info->curve25519_onion_key, curve25519_key, sizeof(curve25519_public_key_t)); -#else - (void)curve25519_key; -#endif tor_addr_copy(&info->addr, addr); info->port = port; return info; diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index a3091707e8..01563791b7 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -22,7 +22,8 @@ origin_circuit_t *circuit_establish_circuit(uint8_t purpose, extend_info_t *exit, int flags); int circuit_handle_first_hop(origin_circuit_t *circ); -void circuit_n_chan_done(channel_t *chan, int status); +void circuit_n_chan_done(channel_t *chan, int status, + int close_origin_circuits); int inform_testing_reachability(void); int circuit_timeout_want_to_count_circ(origin_circuit_t *circ); int circuit_send_next_onion_skin(origin_circuit_t *circ); @@ -57,16 +58,15 @@ const char *build_state_get_exit_nickname(cpath_build_state_t *state); const node_t *choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state); -double pathbias_get_extreme_rate(const or_options_t *options); -double pathbias_get_extreme_use_rate(const or_options_t *options); -int pathbias_get_dropguards(const or_options_t *options); -void pathbias_count_timeout(origin_circuit_t *circ); -int pathbias_check_close(origin_circuit_t *circ, int reason); -int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); -void pathbias_count_use_attempt(origin_circuit_t *circ); -void pathbias_mark_use_success(origin_circuit_t *circ); -void pathbias_mark_use_rollback(origin_circuit_t *circ); -const char *pathbias_state_to_string(path_state_t state); + +#ifdef CIRCUITBUILD_PRIVATE +STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan); +#if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) +STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags, + const or_options_t *options); +#endif + +#endif #endif diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index c7b15e40ba..bf7f8daca7 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1,16 +1,17 @@ /* Copyright 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file circuitlist.c * \brief Manage the global circuit list. **/ - +#define CIRCUITLIST_PRIVATE #include "or.h" #include "channel.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -20,6 +21,7 @@ #include "connection_edge.h" #include "connection_or.h" #include "control.h" +#include "main.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -31,20 +33,22 @@ #include "rephist.h" #include "routerlist.h" #include "routerset.h" + #include "ht.h" /********* START VARIABLES **********/ /** A global list of all circuits at this hop. */ -circuit_t *global_circuitlist=NULL; +static smartlist_t *global_circuitlist = NULL; /** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */ static smartlist_t *circuits_pending_chans = NULL; -static void circuit_free(circuit_t *circ); -static void circuit_free_cpath(crypt_path_t *cpath); static void circuit_free_cpath_node(crypt_path_t *victim); static void cpath_ref_decref(crypt_path_reference_t *cpath_ref); +//static void circuit_set_rend_token(or_circuit_t *circ, int is_rend_circ, +// const uint8_t *token); +static void circuit_clear_rend_token(or_circuit_t *circ); /********* END VARIABLES ************/ @@ -55,6 +59,8 @@ typedef struct chan_circid_circuit_map_t { channel_t *chan; circid_t circ_id; circuit_t *circuit; + /* For debugging 12184: when was this placeholder item added? */ + time_t made_placeholder_at; } chan_circid_circuit_map_t; /** Helper for hash tables: compare the channel and circuit ID for a and @@ -72,7 +78,15 @@ chan_circid_entries_eq_(chan_circid_circuit_map_t *a, static INLINE unsigned int chan_circid_entry_hash_(chan_circid_circuit_map_t *a) { - return ((unsigned)a->circ_id) ^ (unsigned)(uintptr_t)(a->chan); + /* Try to squeze the siphash input into 8 bytes to save any extra siphash + * rounds. This hash function is in the critical path. */ + uintptr_t chan = (uintptr_t) (void*) a->chan; + uint32_t array[2]; + array[0] = a->circ_id; + /* The low bits of the channel pointer are uninteresting, since the channel + * is a pretty big structure. */ + array[1] = (uint32_t) (chan >> 6); + return (unsigned) siphash24g(array, sizeof(array)); } /** Map from [chan,circid] to circuit. */ @@ -80,9 +94,9 @@ static HT_HEAD(chan_circid_map, chan_circid_circuit_map_t) chan_circid_map = HT_INITIALIZER(); HT_PROTOTYPE(chan_circid_map, chan_circid_circuit_map_t, node, chan_circid_entry_hash_, chan_circid_entries_eq_) -HT_GENERATE(chan_circid_map, chan_circid_circuit_map_t, node, - chan_circid_entry_hash_, chan_circid_entries_eq_, 0.6, - malloc, realloc, free) +HT_GENERATE2(chan_circid_map, chan_circid_circuit_map_t, node, + chan_circid_entry_hash_, chan_circid_entries_eq_, 0.6, + tor_reallocarray_, tor_free_) /** The most recently returned entry from circuit_get_by_circid_chan; * used to improve performance when many cells arrive in a row from the @@ -172,6 +186,7 @@ circuit_set_circid_chan_helper(circuit_t *circ, int direction, found = HT_FIND(chan_circid_map, &chan_circid_map, &search); if (found) { found->circuit = circ; + found->made_placeholder_at = 0; } else { found = tor_malloc_zero(sizeof(chan_circid_circuit_map_t)); found->circ_id = id; @@ -207,18 +222,129 @@ circuit_set_circid_chan_helper(circuit_t *circ, int direction, } } +/** Mark that circuit id <b>id</b> shouldn't be used on channel <b>chan</b>, + * even if there is no circuit on the channel. We use this to keep the + * circuit id from getting re-used while we have queued but not yet sent + * a destroy cell. */ +void +channel_mark_circid_unusable(channel_t *chan, circid_t id) +{ + chan_circid_circuit_map_t search; + chan_circid_circuit_map_t *ent; + + /* See if there's an entry there. That wouldn't be good. */ + memset(&search, 0, sizeof(search)); + search.chan = chan; + search.circ_id = id; + ent = HT_FIND(chan_circid_map, &chan_circid_map, &search); + + if (ent && ent->circuit) { + /* we have a problem. */ + log_warn(LD_BUG, "Tried to mark %u unusable on %p, but there was already " + "a circuit there.", (unsigned)id, chan); + } else if (ent) { + /* It's already marked. */ + if (!ent->made_placeholder_at) + ent->made_placeholder_at = approx_time(); + } else { + ent = tor_malloc_zero(sizeof(chan_circid_circuit_map_t)); + ent->chan = chan; + ent->circ_id = id; + /* leave circuit at NULL. */ + ent->made_placeholder_at = approx_time(); + HT_INSERT(chan_circid_map, &chan_circid_map, ent); + } +} + +/** Mark that a circuit id <b>id</b> can be used again on <b>chan</b>. + * We use this to re-enable the circuit ID after we've sent a destroy cell. + */ +void +channel_mark_circid_usable(channel_t *chan, circid_t id) +{ + chan_circid_circuit_map_t search; + chan_circid_circuit_map_t *ent; + + /* See if there's an entry there. That wouldn't be good. */ + memset(&search, 0, sizeof(search)); + search.chan = chan; + search.circ_id = id; + ent = HT_REMOVE(chan_circid_map, &chan_circid_map, &search); + if (ent && ent->circuit) { + log_warn(LD_BUG, "Tried to mark %u usable on %p, but there was already " + "a circuit there.", (unsigned)id, chan); + return; + } + if (_last_circid_chan_ent == ent) + _last_circid_chan_ent = NULL; + tor_free(ent); +} + +/** Called to indicate that a DESTROY is pending on <b>chan</b> with + * circuit ID <b>id</b>, but hasn't been sent yet. */ +void +channel_note_destroy_pending(channel_t *chan, circid_t id) +{ + circuit_t *circ = circuit_get_by_circid_channel_even_if_marked(id,chan); + if (circ) { + if (circ->n_chan == chan && circ->n_circ_id == id) { + circ->n_delete_pending = 1; + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + if (orcirc->p_chan == chan && orcirc->p_circ_id == id) { + circ->p_delete_pending = 1; + } + } + return; + } + channel_mark_circid_unusable(chan, id); +} + +/** Called to indicate that a DESTROY is no longer pending on <b>chan</b> with + * circuit ID <b>id</b> -- typically, because it has been sent. */ +MOCK_IMPL(void, channel_note_destroy_not_pending, + (channel_t *chan, circid_t id)) +{ + circuit_t *circ = circuit_get_by_circid_channel_even_if_marked(id,chan); + if (circ) { + if (circ->n_chan == chan && circ->n_circ_id == id) { + circ->n_delete_pending = 0; + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + if (orcirc->p_chan == chan && orcirc->p_circ_id == id) { + circ->p_delete_pending = 0; + } + } + /* XXXX this shouldn't happen; log a bug here. */ + return; + } + channel_mark_circid_usable(chan, id); +} + /** Set the p_conn field of a circuit <b>circ</b>, along * with the corresponding circuit ID, and add the circuit as appropriate * to the (chan,id)-\>circuit map. */ void -circuit_set_p_circid_chan(or_circuit_t *circ, circid_t id, +circuit_set_p_circid_chan(or_circuit_t *or_circ, circid_t id, channel_t *chan) { - circuit_set_circid_chan_helper(TO_CIRCUIT(circ), CELL_DIRECTION_IN, - id, chan); + circuit_t *circ = TO_CIRCUIT(or_circ); + channel_t *old_chan = or_circ->p_chan; + circid_t old_id = or_circ->p_circ_id; + + circuit_set_circid_chan_helper(circ, CELL_DIRECTION_IN, id, chan); + + if (chan) { + tor_assert(bool_eq(or_circ->p_chan_cells.n, + or_circ->next_active_on_p_chan)); + + chan->timestamp_last_had_circuits = approx_time(); + } - if (chan) - tor_assert(bool_eq(circ->p_chan_cells.n, circ->next_active_on_p_chan)); + if (circ->p_delete_pending && old_chan) { + channel_mark_circid_unusable(old_chan, old_id); + circ->p_delete_pending = 0; + } } /** Set the n_conn field of a circuit <b>circ</b>, along @@ -228,10 +354,21 @@ void circuit_set_n_circid_chan(circuit_t *circ, circid_t id, channel_t *chan) { + channel_t *old_chan = circ->n_chan; + circid_t old_id = circ->n_circ_id; + circuit_set_circid_chan_helper(circ, CELL_DIRECTION_OUT, id, chan); - if (chan) + if (chan) { tor_assert(bool_eq(circ->n_chan_cells.n, circ->next_active_on_n_chan)); + + chan->timestamp_last_had_circuits = approx_time(); + } + + if (circ->n_delete_pending && old_chan) { + channel_mark_circid_unusable(old_chan, old_id); + circ->n_delete_pending = 0; + } } /** Change the state of <b>circ</b> to <b>state</b>, adding it to or removing @@ -257,21 +394,6 @@ circuit_set_state(circuit_t *circ, uint8_t state) circ->state = state; } -/** Add <b>circ</b> to the global list of circuits. This is called only from - * within circuit_new. - */ -static void -circuit_add(circuit_t *circ) -{ - if (!global_circuitlist) { /* first one */ - global_circuitlist = circ; - circ->next = NULL; - } else { - circ->next = global_circuitlist; - global_circuitlist = circ; - } -} - /** Append to <b>out</b> all circuits in state CHAN_WAIT waiting for * the given connection. */ void @@ -329,32 +451,24 @@ circuit_count_pending_on_channel(channel_t *chan) void circuit_close_all_marked(void) { - circuit_t *tmp,*m; - - while (global_circuitlist && global_circuitlist->marked_for_close) { - tmp = global_circuitlist->next; - circuit_free(global_circuitlist); - global_circuitlist = tmp; - } - - tmp = global_circuitlist; - while (tmp && tmp->next) { - if (tmp->next->marked_for_close) { - m = tmp->next->next; - circuit_free(tmp->next); - tmp->next = m; - /* Need to check new tmp->next; don't advance tmp. */ - } else { - /* Advance tmp. */ - tmp = tmp->next; + smartlist_t *lst = circuit_get_global_list(); + SMARTLIST_FOREACH_BEGIN(lst, circuit_t *, circ) { + /* Fix up index if SMARTLIST_DEL_CURRENT just moved this one. */ + circ->global_circuitlist_idx = circ_sl_idx; + if (circ->marked_for_close) { + circ->global_circuitlist_idx = -1; + circuit_free(circ); + SMARTLIST_DEL_CURRENT(lst, circ); } - } + } SMARTLIST_FOREACH_END(circ); } /** Return the head of the global linked list of circuits. */ -circuit_t * -circuit_get_global_list_(void) +MOCK_IMPL(smartlist_t *, +circuit_get_global_list,(void)) { + if (NULL == global_circuitlist) + global_circuitlist = smartlist_new(); return global_circuitlist; } @@ -570,8 +684,10 @@ init_circuit_base(circuit_t *circ) circ->package_window = circuit_initial_package_window(); circ->deliver_window = CIRCWINDOW_START; + cell_queue_init(&circ->n_chan_cells); - circuit_add(circ); + smartlist_add(circuit_get_global_list(), circ); + circ->global_circuitlist_idx = smartlist_len(circuit_get_global_list()) - 1; } /** Allocate space for a new circuit, initializing with <b>p_circ_id</b> @@ -595,7 +711,7 @@ origin_circuit_new(void) init_circuit_base(TO_CIRCUIT(circ)); - circ_times.last_circ_at = approx_time(); + circuit_build_times_update_last_circ(get_circuit_build_times_mutable()); return circ; } @@ -615,6 +731,7 @@ or_circuit_new(circid_t p_circ_id, channel_t *p_chan) circuit_set_p_circid_chan(circ, p_circ_id, p_chan); circ->remaining_relay_early_cells = MAX_RELAY_EARLY_CELLS_PER_CIRCUIT; + cell_queue_init(&circ->p_chan_cells); init_circuit_base(TO_CIRCUIT(circ)); @@ -623,11 +740,12 @@ or_circuit_new(circid_t p_circ_id, channel_t *p_chan) /** Deallocate space associated with circ. */ -static void +STATIC void circuit_free(circuit_t *circ) { void *mem; size_t memlen; + int should_free = 1; if (!circ) return; @@ -643,7 +761,7 @@ circuit_free(circuit_t *circ) } tor_free(ocirc->build_state); - circuit_free_cpath(ocirc->cpath); + circuit_clear_cpath(ocirc); crypto_pk_free(ocirc->intro_key); rend_data_free(ocirc->rend_data); @@ -667,11 +785,15 @@ circuit_free(circuit_t *circ) memlen = sizeof(or_circuit_t); tor_assert(circ->magic == OR_CIRCUIT_MAGIC); + should_free = (ocirc->workqueue_entry == NULL); + crypto_cipher_free(ocirc->p_crypto); crypto_digest_free(ocirc->p_digest); crypto_cipher_free(ocirc->n_crypto); crypto_digest_free(ocirc->n_digest); + circuit_clear_rend_token(ocirc); + if (ocirc->rend_splice) { or_circuit_t *other = ocirc->rend_splice; tor_assert(other->base_.magic == OR_CIRCUIT_MAGIC); @@ -689,6 +811,17 @@ circuit_free(circuit_t *circ) extend_info_free(circ->n_hop); tor_free(circ->n_chan_create_cell); + if (circ->global_circuitlist_idx != -1) { + int idx = circ->global_circuitlist_idx; + circuit_t *c2 = smartlist_get(global_circuitlist, idx); + tor_assert(c2 == circ); + smartlist_del(global_circuitlist, idx); + if (idx < smartlist_len(global_circuitlist)) { + c2 = smartlist_get(global_circuitlist, idx); + c2->global_circuitlist_idx = idx; + } + } + /* Remove from map. */ circuit_set_n_circid_chan(circ, 0, NULL); @@ -696,15 +829,28 @@ circuit_free(circuit_t *circ) * "active" checks will be violated. */ cell_queue_clear(&circ->n_chan_cells); - memwipe(mem, 0xAA, memlen); /* poison memory */ - tor_free(mem); + if (should_free) { + memwipe(mem, 0xAA, memlen); /* poison memory */ + tor_free(mem); + } else { + /* If we made it here, this is an or_circuit_t that still has a pending + * cpuworker request which we weren't able to cancel. Instead, set up + * the magic value so that when the reply comes back, we'll know to discard + * the reply and free this structure. + */ + memwipe(mem, 0xAA, memlen); + circ->magic = DEAD_CIRCUIT_MAGIC; + } } -/** Deallocate space associated with the linked list <b>cpath</b>. */ -static void -circuit_free_cpath(crypt_path_t *cpath) +/** Deallocate the linked list circ-><b>cpath</b>, and remove the cpath from + * <b>circ</b>. */ +void +circuit_clear_cpath(origin_circuit_t *circ) { - crypt_path_t *victim, *head=cpath; + crypt_path_t *victim, *head, *cpath; + + head = cpath = circ->cpath; if (!cpath) return; @@ -718,13 +864,7 @@ circuit_free_cpath(crypt_path_t *cpath) } circuit_free_cpath_node(cpath); -} -/** Remove all the items in the cpath on <b>circ</b>.*/ -void -circuit_clear_cpath(origin_circuit_t *circ) -{ - circuit_free_cpath(circ->cpath); circ->cpath = NULL; } @@ -732,11 +872,11 @@ circuit_clear_cpath(origin_circuit_t *circ) void circuit_free_all(void) { - circuit_t *next; - while (global_circuitlist) { - next = global_circuitlist->next; - if (! CIRCUIT_IS_ORIGIN(global_circuitlist)) { - or_circuit_t *or_circ = TO_OR_CIRCUIT(global_circuitlist); + smartlist_t *lst = circuit_get_global_list(); + + SMARTLIST_FOREACH_BEGIN(lst, circuit_t *, tmp) { + if (! CIRCUIT_IS_ORIGIN(tmp)) { + or_circuit_t *or_circ = TO_OR_CIRCUIT(tmp); while (or_circ->resolving_streams) { edge_connection_t *next_conn; next_conn = or_circ->resolving_streams->next_stream; @@ -744,13 +884,29 @@ circuit_free_all(void) or_circ->resolving_streams = next_conn; } } - circuit_free(global_circuitlist); - global_circuitlist = next; - } + tmp->global_circuitlist_idx = -1; + circuit_free(tmp); + SMARTLIST_DEL_CURRENT(lst, tmp); + } SMARTLIST_FOREACH_END(tmp); + + smartlist_free(lst); + global_circuitlist = NULL; smartlist_free(circuits_pending_chans); circuits_pending_chans = NULL; + { + chan_circid_circuit_map_t **elt, **next, *c; + for (elt = HT_START(chan_circid_map, &chan_circid_map); + elt; + elt = next) { + c = *elt; + next = HT_NEXT_RMV(chan_circid_map, &chan_circid_map, elt); + + tor_assert(c->circuit == NULL); + tor_free(c); + } + } HT_CLEAR(chan_circid_map, &chan_circid_map); } @@ -812,10 +968,9 @@ circuit_dump_conn_details(int severity, void circuit_dump_by_conn(connection_t *conn, int severity) { - circuit_t *circ; edge_connection_t *tmpconn; - for (circ = global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { circid_t n_circ_id = circ->n_circ_id, p_circ_id = 0; if (circ->marked_for_close) { @@ -846,72 +1001,7 @@ circuit_dump_by_conn(connection_t *conn, int severity) } } } -} - -/** A helper function for circuit_dump_by_chan() below. Log a bunch - * of information about circuit <b>circ</b>. - */ -static void -circuit_dump_chan_details(int severity, - circuit_t *circ, - channel_t *chan, - const char *type, - circid_t this_circid, - circid_t other_circid) -{ - tor_log(severity, LD_CIRC, "Conn %p has %s circuit: circID %u " - "(other side %u), state %d (%s), born %ld:", - chan, type, (unsigned)this_circid, (unsigned)other_circid, circ->state, - circuit_state_to_string(circ->state), - (long)circ->timestamp_began.tv_sec); - if (CIRCUIT_IS_ORIGIN(circ)) { /* circ starts at this node */ - circuit_log_path(severity, LD_CIRC, TO_ORIGIN_CIRCUIT(circ)); - } -} - -/** Log, at severity <b>severity</b>, information about each circuit - * that is connected to <b>chan</b>. - */ -void -circuit_dump_by_chan(channel_t *chan, int severity) -{ - circuit_t *circ; - - tor_assert(chan); - - for (circ = global_circuitlist; circ; circ = circ->next) { - circid_t n_circ_id = circ->n_circ_id, p_circ_id = 0; - - if (circ->marked_for_close) { - continue; - } - - if (!CIRCUIT_IS_ORIGIN(circ)) { - p_circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; - } - - if (! CIRCUIT_IS_ORIGIN(circ) && TO_OR_CIRCUIT(circ)->p_chan && - TO_OR_CIRCUIT(circ)->p_chan == chan) { - circuit_dump_chan_details(severity, circ, chan, "App-ward", - p_circ_id, n_circ_id); - } - - if (circ->n_chan && circ->n_chan == chan) { - circuit_dump_chan_details(severity, circ, chan, "Exit-ward", - n_circ_id, p_circ_id); - } - - if (!circ->n_chan && circ->n_hop && - channel_matches_extend_info(chan, circ->n_hop) && - tor_memeq(chan->identity_digest, - circ->n_hop->identity_digest, DIGEST_LEN)) { - circuit_dump_chan_details(severity, circ, chan, - (circ->state == CIRCUIT_STATE_OPEN && - !CIRCUIT_IS_ORIGIN(circ)) ? - "Endpoint" : "Pending", - n_circ_id, p_circ_id); - } - } + SMARTLIST_FOREACH_END(circ); } /** Return the circuit whose global ID is <b>id</b>, or NULL if no @@ -919,8 +1009,7 @@ circuit_dump_by_chan(channel_t *chan, int severity) origin_circuit_t * circuit_get_by_global_id(uint32_t id) { - circuit_t *circ; - for (circ=global_circuitlist;circ;circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (CIRCUIT_IS_ORIGIN(circ) && TO_ORIGIN_CIRCUIT(circ)->global_identifier == id) { if (circ->marked_for_close) @@ -929,6 +1018,7 @@ circuit_get_by_global_id(uint32_t id) return TO_ORIGIN_CIRCUIT(circ); } } + SMARTLIST_FOREACH_END(circ); return NULL; } @@ -936,9 +1026,13 @@ circuit_get_by_global_id(uint32_t id) * - circ-\>n_circ_id or circ-\>p_circ_id is equal to <b>circ_id</b>, and * - circ is attached to <b>chan</b>, either as p_chan or n_chan. * Return NULL if no such circuit exists. + * + * If <b>found_entry_out</b> is provided, set it to true if we have a + * placeholder entry for circid/chan, and leave it unset otherwise. */ static INLINE circuit_t * -circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan) +circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan, + int *found_entry_out) { chan_circid_circuit_map_t search; chan_circid_circuit_map_t *found; @@ -959,21 +1053,27 @@ circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan) " circ_id %u, channel ID " U64_FORMAT " (%p)", found->circuit, (unsigned)circ_id, U64_PRINTF_ARG(chan->global_identifier), chan); + if (found_entry_out) + *found_entry_out = 1; return found->circuit; } log_debug(LD_CIRC, - "circuit_get_by_circid_channel_impl() found nothing for" + "circuit_get_by_circid_channel_impl() found %s for" " circ_id %u, channel ID " U64_FORMAT " (%p)", + found ? "placeholder" : "nothing", (unsigned)circ_id, U64_PRINTF_ARG(chan->global_identifier), chan); + if (found_entry_out) + *found_entry_out = found ? 1 : 0; + return NULL; /* The rest of this checks for bugs. Disabled by default. */ /* We comment it out because coverity complains otherwise. { circuit_t *circ; - for (circ=global_circuitlist;circ;circ = circ->next) { + TOR_LIST_FOREACH(circ, &global_circuitlist, head) { if (! CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); if (or_circ->p_chan == chan && or_circ->p_circ_id == circ_id) { @@ -1001,7 +1101,7 @@ circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan) circuit_t * circuit_get_by_circid_channel(circid_t circ_id, channel_t *chan) { - circuit_t *circ = circuit_get_by_circid_channel_impl(circ_id, chan); + circuit_t *circ = circuit_get_by_circid_channel_impl(circ_id, chan, NULL); if (!circ || circ->marked_for_close) return NULL; else @@ -1017,15 +1117,45 @@ circuit_t * circuit_get_by_circid_channel_even_if_marked(circid_t circ_id, channel_t *chan) { - return circuit_get_by_circid_channel_impl(circ_id, chan); + return circuit_get_by_circid_channel_impl(circ_id, chan, NULL); } /** Return true iff the circuit ID <b>circ_id</b> is currently used by a - * circuit, marked or not, on <b>chan</b>. */ + * circuit, marked or not, on <b>chan</b>, or if the circ ID is reserved until + * a queued destroy cell can be sent. + * + * (Return 1 if the circuit is present, marked or not; Return 2 + * if the circuit ID is pending a destroy.) + **/ int circuit_id_in_use_on_channel(circid_t circ_id, channel_t *chan) { - return circuit_get_by_circid_channel_impl(circ_id, chan) != NULL; + int found = 0; + if (circuit_get_by_circid_channel_impl(circ_id, chan, &found) != NULL) + return 1; + if (found) + return 2; + return 0; +} + +/** Helper for debugging 12184. Returns the time since which 'circ_id' has + * been marked unusable on 'chan'. */ +time_t +circuit_id_when_marked_unusable_on_channel(circid_t circ_id, channel_t *chan) +{ + chan_circid_circuit_map_t search; + chan_circid_circuit_map_t *found; + + memset(&search, 0, sizeof(search)); + search.circ_id = circ_id; + search.chan = chan; + + found = HT_FIND(chan_circid_map, &chan_circid_map, &search); + + if (! found || found->circuit) + return 0; + + return found->made_placeholder_at; } /** Return the circuit that a given edge connection is using. */ @@ -1049,13 +1179,59 @@ circuit_get_by_edge_conn(edge_connection_t *conn) void circuit_unlink_all_from_channel(channel_t *chan, int reason) { - circuit_t *circ; + smartlist_t *detached = smartlist_new(); - channel_unlink_all_circuits(chan); +/* #define DEBUG_CIRCUIT_UNLINK_ALL */ - for (circ = global_circuitlist; circ; circ = circ->next) { + channel_unlink_all_circuits(chan, detached); + +#ifdef DEBUG_CIRCUIT_UNLINK_ALL + { + smartlist_t *detached_2 = smartlist_new(); + int mismatch = 0, badlen = 0; + + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (circ->n_chan == chan || + (!CIRCUIT_IS_ORIGIN(circ) && + TO_OR_CIRCUIT(circ)->p_chan == chan)) { + smartlist_add(detached_2, circ); + } + } + SMARTLIST_FOREACH_END(circ); + + if (smartlist_len(detached) != smartlist_len(detached_2)) { + log_warn(LD_BUG, "List of detached circuits had the wrong length! " + "(got %d, should have gotten %d)", + (int)smartlist_len(detached), + (int)smartlist_len(detached_2)); + badlen = 1; + } + smartlist_sort_pointers(detached); + smartlist_sort_pointers(detached_2); + + SMARTLIST_FOREACH(detached, circuit_t *, c, + if (c != smartlist_get(detached_2, c_sl_idx)) + mismatch = 1; + ); + + if (mismatch) + log_warn(LD_BUG, "Mismatch in list of detached circuits."); + + if (badlen || mismatch) { + smartlist_free(detached); + detached = detached_2; + } else { + log_notice(LD_CIRC, "List of %d circuits was as expected.", + (int)smartlist_len(detached)); + smartlist_free(detached_2); + } + } +#endif + + SMARTLIST_FOREACH_BEGIN(detached, circuit_t *, circ) { int mark = 0; if (circ->n_chan == chan) { + circuit_set_n_circid_chan(circ, 0, NULL); mark = 1; @@ -1071,9 +1247,16 @@ circuit_unlink_all_from_channel(channel_t *chan, int reason) mark = 1; } } - if (mark && !circ->marked_for_close) + if (!mark) { + log_warn(LD_BUG, "Circuit on detached list which I had no reason " + "to mark"); + continue; + } + if (!circ->marked_for_close) circuit_mark_for_close(circ, reason); - } + } SMARTLIST_FOREACH_END(circ); + + smartlist_free(detached); } /** Return a circ such that @@ -1088,9 +1271,7 @@ circuit_unlink_all_from_channel(channel_t *chan, int reason) origin_circuit_t * circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data) { - circuit_t *circ; - - for (circ = global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circ->marked_for_close && circ->purpose == CIRCUIT_PURPOSE_C_REND_READY) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); @@ -1103,6 +1284,7 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data) return ocirc; } } + SMARTLIST_FOREACH_END(circ); return NULL; } @@ -1115,14 +1297,17 @@ origin_circuit_t * circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, const char *digest, uint8_t purpose) { - circuit_t *circ; + int idx; + smartlist_t *lst = circuit_get_global_list(); tor_assert(CIRCUIT_PURPOSE_IS_ORIGIN(purpose)); if (start == NULL) - circ = global_circuitlist; + idx = 0; else - circ = TO_CIRCUIT(start)->next; + idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1; + + for ( ; idx < smartlist_len(lst); ++idx) { + circuit_t *circ = smartlist_get(lst, idx); - for ( ; circ; circ = circ->next) { if (circ->marked_for_close) continue; if (circ->purpose != purpose) @@ -1137,43 +1322,175 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, return NULL; } -/** Return the first OR circuit in the global list whose purpose is - * <b>purpose</b>, and whose rend_token is the <b>len</b>-byte - * <b>token</b>. */ +/** Map from rendezvous cookie to or_circuit_t */ +static digestmap_t *rend_cookie_map = NULL; + +/** Map from introduction point digest to or_circuit_t */ +static digestmap_t *intro_digest_map = NULL; + +/** Return the OR circuit whose purpose is <b>purpose</b>, and whose + * rend_token is the REND_TOKEN_LEN-byte <b>token</b>. If <b>is_rend_circ</b>, + * look for rendezvous point circuits; otherwise look for introduction point + * circuits. */ static or_circuit_t * -circuit_get_by_rend_token_and_purpose(uint8_t purpose, const char *token, - size_t len) +circuit_get_by_rend_token_and_purpose(uint8_t purpose, int is_rend_circ, + const char *token) { - circuit_t *circ; - for (circ = global_circuitlist; circ; circ = circ->next) { - if (! circ->marked_for_close && - circ->purpose == purpose && - tor_memeq(TO_OR_CIRCUIT(circ)->rend_token, token, len)) - return TO_OR_CIRCUIT(circ); + or_circuit_t *circ; + digestmap_t *map = is_rend_circ ? rend_cookie_map : intro_digest_map; + + if (!map) + return NULL; + + circ = digestmap_get(map, token); + if (!circ || + circ->base_.purpose != purpose || + circ->base_.marked_for_close) + return NULL; + + if (!circ->rendinfo) { + char *t = tor_strdup(hex_str(token, REND_TOKEN_LEN)); + log_warn(LD_BUG, "Wanted a circuit with %s:%d, but lookup returned a " + "circuit with no rendinfo set.", + safe_str(t), is_rend_circ); + tor_free(t); + return NULL; } - return NULL; + + if (! bool_eq(circ->rendinfo->is_rend_circ, is_rend_circ) || + tor_memneq(circ->rendinfo->rend_token, token, REND_TOKEN_LEN)) { + char *t = tor_strdup(hex_str(token, REND_TOKEN_LEN)); + log_warn(LD_BUG, "Wanted a circuit with %s:%d, but lookup returned %s:%d", + safe_str(t), is_rend_circ, + safe_str(hex_str(circ->rendinfo->rend_token, REND_TOKEN_LEN)), + (int)circ->rendinfo->is_rend_circ); + tor_free(t); + return NULL; + } + + return circ; +} + +/** Clear the rendezvous cookie or introduction point key digest that's + * configured on <b>circ</b>, if any, and remove it from any such maps. */ +static void +circuit_clear_rend_token(or_circuit_t *circ) +{ + or_circuit_t *found_circ; + digestmap_t *map; + + if (!circ || !circ->rendinfo) + return; + + map = circ->rendinfo->is_rend_circ ? rend_cookie_map : intro_digest_map; + + if (!map) { + log_warn(LD_BUG, "Tried to clear rend token on circuit, but found no map"); + return; + } + + found_circ = digestmap_get(map, circ->rendinfo->rend_token); + if (found_circ == circ) { + /* Great, this is the right one. */ + digestmap_remove(map, circ->rendinfo->rend_token); + } else if (found_circ) { + log_warn(LD_BUG, "Tried to clear rend token on circuit, but " + "it was already replaced in the map."); + } else { + log_warn(LD_BUG, "Tried to clear rend token on circuit, but " + "it not in the map at all."); + } + + tor_free(circ->rendinfo); /* Sets it to NULL too */ +} + +/** Set the rendezvous cookie (if is_rend_circ), or the introduction point + * digest (if ! is_rend_circ) of <b>circ</b> to the REND_TOKEN_LEN-byte value + * in <b>token</b>, and add it to the appropriate map. If it previously had a + * token, clear it. If another circuit previously had the same + * cookie/intro-digest, mark that circuit and remove it from the map. */ +static void +circuit_set_rend_token(or_circuit_t *circ, int is_rend_circ, + const uint8_t *token) +{ + digestmap_t **map_p, *map; + or_circuit_t *found_circ; + + /* Find the right map, creating it as needed */ + map_p = is_rend_circ ? &rend_cookie_map : &intro_digest_map; + + if (!*map_p) + *map_p = digestmap_new(); + + map = *map_p; + + /* If this circuit already has a token, we need to remove that. */ + if (circ->rendinfo) + circuit_clear_rend_token(circ); + + if (token == NULL) { + /* We were only trying to remove this token, not set a new one. */ + return; + } + + found_circ = digestmap_get(map, (const char *)token); + if (found_circ) { + tor_assert(found_circ != circ); + circuit_clear_rend_token(found_circ); + if (! found_circ->base_.marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(found_circ), END_CIRC_REASON_FINISHED); + if (is_rend_circ) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Duplicate rendezvous cookie (%s...) used on two circuits", + hex_str((const char*)token, 4)); /* only log first 4 chars */ + } + } + } + + /* Now set up the rendinfo */ + circ->rendinfo = tor_malloc(sizeof(*circ->rendinfo)); + memcpy(circ->rendinfo->rend_token, token, REND_TOKEN_LEN); + circ->rendinfo->is_rend_circ = is_rend_circ ? 1 : 0; + + digestmap_set(map, (const char *)token, circ); } /** Return the circuit waiting for a rendezvous with the provided cookie. * Return NULL if no such circuit is found. */ or_circuit_t * -circuit_get_rendezvous(const char *cookie) +circuit_get_rendezvous(const uint8_t *cookie) { return circuit_get_by_rend_token_and_purpose( CIRCUIT_PURPOSE_REND_POINT_WAITING, - cookie, REND_COOKIE_LEN); + 1, (const char*)cookie); } /** Return the circuit waiting for intro cells of the given digest. * Return NULL if no such circuit is found. */ or_circuit_t * -circuit_get_intro_point(const char *digest) +circuit_get_intro_point(const uint8_t *digest) { return circuit_get_by_rend_token_and_purpose( - CIRCUIT_PURPOSE_INTRO_POINT, digest, - DIGEST_LEN); + CIRCUIT_PURPOSE_INTRO_POINT, 0, + (const char *)digest); +} + +/** Set the rendezvous cookie of <b>circ</b> to <b>cookie</b>. If another + * circuit previously had that cookie, mark it. */ +void +circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie) +{ + circuit_set_rend_token(circ, 1, cookie); +} + +/** Set the intro point key digest of <b>circ</b> to <b>cookie</b>. If another + * circuit previously had that intro point digest, mark it. */ +void +circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest) +{ + circuit_set_rend_token(circ, 0, digest); } /** Return a circuit that is open, is CIRCUIT_PURPOSE_C_GENERAL, @@ -1191,7 +1508,6 @@ origin_circuit_t * circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags) { - circuit_t *circ_; origin_circuit_t *best=NULL; int need_uptime = (flags & CIRCLAUNCH_NEED_UPTIME) != 0; int need_capacity = (flags & CIRCLAUNCH_NEED_CAPACITY) != 0; @@ -1207,7 +1523,7 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, "capacity %d, internal %d", purpose, need_uptime, need_capacity, internal); - for (circ_=global_circuitlist; circ_; circ_ = circ_->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ_) { if (CIRCUIT_IS_ORIGIN(circ_) && circ_->state == CIRCUIT_STATE_OPEN && !circ_->marked_for_close && @@ -1229,7 +1545,7 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, do { const node_t *ri2; if (tor_memeq(hop->extend_info->identity_digest, - info->identity_digest, DIGEST_LEN)) + info->identity_digest, DIGEST_LEN)) goto next; if (ri1 && (ri2 = node_get_by_id(hop->extend_info->identity_digest)) @@ -1257,6 +1573,7 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, } } } + SMARTLIST_FOREACH_END(circ_); return best; } @@ -1296,14 +1613,13 @@ circuit_get_cpath_hop(origin_circuit_t *circ, int hopnum) void circuit_mark_all_unused_circs(void) { - circuit_t *circ; - - for (circ=global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (CIRCUIT_IS_ORIGIN(circ) && !circ->marked_for_close && !circ->timestamp_dirty) circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } + SMARTLIST_FOREACH_END(circ); } /** Go through the circuitlist; for each circuit that starts at us @@ -1316,15 +1632,14 @@ circuit_mark_all_unused_circs(void) void circuit_mark_all_dirty_circs_as_unusable(void) { - circuit_t *circ; - - for (circ=global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (CIRCUIT_IS_ORIGIN(circ) && !circ->marked_for_close && circ->timestamp_dirty) { mark_circuit_unusable_for_new_conns(TO_ORIGIN_CIRCUIT(circ)); } } + SMARTLIST_FOREACH_END(circ); } /** Mark <b>circ</b> to be closed next time we call @@ -1344,9 +1659,9 @@ circuit_mark_all_dirty_circs_as_unusable(void) * - If circ->rend_splice is set (we are the midpoint of a joined * rendezvous stream), then mark the other circuit to close as well. */ -void -circuit_mark_for_close_(circuit_t *circ, int reason, int line, - const char *file) +MOCK_IMPL(void, +circuit_mark_for_close_, (circuit_t *circ, int reason, int line, + const char *file)) { int orig_reason = reason; /* Passed to the controller */ assert_circuit_ok(circ); @@ -1417,39 +1732,44 @@ circuit_mark_for_close_(circuit_t *circ, int reason, int line, tor_assert(circ->state == CIRCUIT_STATE_OPEN); tor_assert(ocirc->build_state->chosen_exit); tor_assert(ocirc->rend_data); - /* treat this like getting a nack from it */ - log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", - safe_str_client(ocirc->rend_data->onion_address), - safe_str_client(build_state_get_exit_nickname(ocirc->build_state)), - timed_out ? "Recording timeout." : "Removing from descriptor."); - rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, - ocirc->rend_data, - timed_out ? - INTRO_POINT_FAILURE_TIMEOUT : - INTRO_POINT_FAILURE_GENERIC); + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + /* treat this like getting a nack from it */ + log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", + safe_str_client(ocirc->rend_data->onion_address), + safe_str_client(build_state_get_exit_nickname(ocirc->build_state)), + timed_out ? "Recording timeout." : "Removing from descriptor."); + rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, + ocirc->rend_data, + timed_out ? + INTRO_POINT_FAILURE_TIMEOUT : + INTRO_POINT_FAILURE_GENERIC); + } } else if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCING && reason != END_CIRC_REASON_TIMEOUT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); if (ocirc->build_state->chosen_exit && ocirc->rend_data) { - log_info(LD_REND, "Failed intro circ %s to %s " - "(building circuit to intro point). " - "Marking intro point as possibly unreachable.", - safe_str_client(ocirc->rend_data->onion_address), - safe_str_client(build_state_get_exit_nickname(ocirc->build_state))); - rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, - ocirc->rend_data, - INTRO_POINT_FAILURE_UNREACHABLE); + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + log_info(LD_REND, "Failed intro circ %s to %s " + "(building circuit to intro point). " + "Marking intro point as possibly unreachable.", + safe_str_client(ocirc->rend_data->onion_address), + safe_str_client(build_state_get_exit_nickname( + ocirc->build_state))); + rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, + ocirc->rend_data, + INTRO_POINT_FAILURE_UNREACHABLE); + } } } + if (circ->n_chan) { circuit_clear_cell_queue(circ, circ->n_chan); /* Only send destroy if the channel isn't closing anyway */ - if (!(circ->n_chan->state == CHANNEL_STATE_CLOSING || - circ->n_chan->state == CHANNEL_STATE_CLOSED || - circ->n_chan->state == CHANNEL_STATE_ERROR)) { + if (!CHANNEL_CONDEMNED(circ->n_chan)) { channel_send_destroy(circ->n_circ_id, circ->n_chan, reason); } circuitmux_detach_circuit(circ->n_chan->cmux, circ); + circuit_set_n_circid_chan(circ, 0, NULL); } if (! CIRCUIT_IS_ORIGIN(circ)) { @@ -1477,12 +1797,11 @@ circuit_mark_for_close_(circuit_t *circ, int reason, int line, if (or_circ->p_chan) { circuit_clear_cell_queue(circ, or_circ->p_chan); /* Only send destroy if the channel isn't closing anyway */ - if (!(or_circ->p_chan->state == CHANNEL_STATE_CLOSING || - or_circ->p_chan->state == CHANNEL_STATE_CLOSED || - or_circ->p_chan->state == CHANNEL_STATE_ERROR)) { + if (!CHANNEL_CONDEMNED(or_circ->p_chan)) { channel_send_destroy(or_circ->p_circ_id, or_circ->p_chan, reason); } circuitmux_detach_circuit(or_circ->p_chan->cmux, circ); + circuit_set_p_circid_chan(or_circ, 0, NULL); } } else { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); @@ -1521,8 +1840,59 @@ marked_circuit_free_cells(circuit_t *circ) cell_queue_clear(& TO_OR_CIRCUIT(circ)->p_chan_cells); } -/** Return the number of cells used by the circuit <b>c</b>'s cell queues. */ static size_t +single_conn_free_bytes(connection_t *conn) +{ + size_t result = 0; + if (conn->inbuf) { + result += buf_allocation(conn->inbuf); + buf_clear(conn->inbuf); + } + if (conn->outbuf) { + result += buf_allocation(conn->outbuf); + buf_clear(conn->outbuf); + } + if (conn->type == CONN_TYPE_DIR) { + dir_connection_t *dir_conn = TO_DIR_CONN(conn); + if (dir_conn->zlib_state) { + result += tor_zlib_state_size(dir_conn->zlib_state); + tor_zlib_free(dir_conn->zlib_state); + dir_conn->zlib_state = NULL; + } + } + return result; +} + +/** Aggressively free buffer contents on all the buffers of all streams in the + * list starting at <b>stream</b>. Return the number of bytes recovered. */ +static size_t +marked_circuit_streams_free_bytes(edge_connection_t *stream) +{ + size_t result = 0; + for ( ; stream; stream = stream->next_stream) { + connection_t *conn = TO_CONN(stream); + result += single_conn_free_bytes(conn); + if (conn->linked_conn) { + result += single_conn_free_bytes(conn->linked_conn); + } + } + return result; +} + +/** Aggressively free buffer contents on all the buffers of all streams on + * circuit <b>c</b>. Return the number of bytes recovered. */ +static size_t +marked_circuit_free_stream_bytes(circuit_t *c) +{ + if (CIRCUIT_IS_ORIGIN(c)) { + return marked_circuit_streams_free_bytes(TO_ORIGIN_CIRCUIT(c)->p_streams); + } else { + return marked_circuit_streams_free_bytes(TO_OR_CIRCUIT(c)->n_streams); + } +} + +/** Return the number of cells used by the circuit <b>c</b>'s cell queues. */ +STATIC size_t n_cells_in_circ_queues(const circuit_t *c) { size_t n = c->n_chan_cells.n; @@ -1541,17 +1911,19 @@ n_cells_in_circ_queues(const circuit_t *c) * This function will return incorrect results if the oldest cell queued on * the circuit is older than 2**32 msec (about 49 days) old. */ -static uint32_t +STATIC uint32_t circuit_max_queued_cell_age(const circuit_t *c, uint32_t now) { uint32_t age = 0; - if (c->n_chan_cells.head) - age = now - c->n_chan_cells.head->inserted_time; + packed_cell_t *cell; + + if (NULL != (cell = TOR_SIMPLEQ_FIRST(&c->n_chan_cells.head))) + age = now - cell->inserted_time; if (! CIRCUIT_IS_ORIGIN(c)) { - const or_circuit_t *orcirc = TO_OR_CIRCUIT((circuit_t*)c); - if (orcirc->p_chan_cells.head) { - uint32_t age2 = now - orcirc->p_chan_cells.head->inserted_time; + const or_circuit_t *orcirc = CONST_TO_OR_CIRCUIT(c); + if (NULL != (cell = TOR_SIMPLEQ_FIRST(&orcirc->p_chan_cells.head))) { + uint32_t age2 = now - cell->inserted_time; if (age2 > age) return age2; } @@ -1559,20 +1931,107 @@ circuit_max_queued_cell_age(const circuit_t *c, uint32_t now) return age; } -/** Temporary variable for circuits_compare_by_oldest_queued_cell_ This is a - * kludge to work around the fact that qsort doesn't provide a way for - * comparison functions to take an extra argument. */ -static uint32_t circcomp_now_tmp; +/** Return the age in milliseconds of the oldest buffer chunk on <b>conn</b>, + * where age is taken in milliseconds before the time <b>now</b> (in truncated + * milliseconds since the epoch). If the connection has no data, treat + * it as having age zero. + **/ +static uint32_t +conn_get_buffer_age(const connection_t *conn, uint32_t now) +{ + uint32_t age = 0, age2; + if (conn->outbuf) { + age2 = buf_get_oldest_chunk_timestamp(conn->outbuf, now); + if (age2 > age) + age = age2; + } + if (conn->inbuf) { + age2 = buf_get_oldest_chunk_timestamp(conn->inbuf, now); + if (age2 > age) + age = age2; + } + return age; +} + +/** Return the age in milliseconds of the oldest buffer chunk on any stream in + * the linked list <b>stream</b>, where age is taken in milliseconds before + * the time <b>now</b> (in truncated milliseconds since the epoch). */ +static uint32_t +circuit_get_streams_max_data_age(const edge_connection_t *stream, uint32_t now) +{ + uint32_t age = 0, age2; + for (; stream; stream = stream->next_stream) { + const connection_t *conn = TO_CONN(stream); + age2 = conn_get_buffer_age(conn, now); + if (age2 > age) + age = age2; + if (conn->linked_conn) { + age2 = conn_get_buffer_age(conn->linked_conn, now); + if (age2 > age) + age = age2; + } + } + return age; +} -/** Helper to sort a list of circuit_t by age of oldest cell, in descending - * order. Requires that circcomp_now_tmp is set correctly. */ +/** Return the age in milliseconds of the oldest buffer chunk on any stream + * attached to the circuit <b>c</b>, where age is taken in milliseconds before + * the time <b>now</b> (in truncated milliseconds since the epoch). */ +STATIC uint32_t +circuit_max_queued_data_age(const circuit_t *c, uint32_t now) +{ + if (CIRCUIT_IS_ORIGIN(c)) { + return circuit_get_streams_max_data_age( + CONST_TO_ORIGIN_CIRCUIT(c)->p_streams, now); + } else { + return circuit_get_streams_max_data_age( + CONST_TO_OR_CIRCUIT(c)->n_streams, now); + } +} + +/** Return the age of the oldest cell or stream buffer chunk on the circuit + * <b>c</b>, where age is taken in milliseconds before the time <b>now</b> (in + * truncated milliseconds since the epoch). */ +STATIC uint32_t +circuit_max_queued_item_age(const circuit_t *c, uint32_t now) +{ + uint32_t cell_age = circuit_max_queued_cell_age(c, now); + uint32_t data_age = circuit_max_queued_data_age(c, now); + if (cell_age > data_age) + return cell_age; + else + return data_age; +} + +/** Helper to sort a list of circuit_t by age of oldest item, in descending + * order. */ static int -circuits_compare_by_oldest_queued_cell_(const void **a_, const void **b_) +circuits_compare_by_oldest_queued_item_(const void **a_, const void **b_) { const circuit_t *a = *a_; const circuit_t *b = *b_; - uint32_t age_a = circuit_max_queued_cell_age(a, circcomp_now_tmp); - uint32_t age_b = circuit_max_queued_cell_age(b, circcomp_now_tmp); + uint32_t age_a = a->age_tmp; + uint32_t age_b = b->age_tmp; + + if (age_a < age_b) + return 1; + else if (age_a == age_b) + return 0; + else + return -1; +} + +static uint32_t now_ms_for_buf_cmp; + +/** Helper to sort a list of circuit_t by age of oldest item, in descending + * order. */ +static int +conns_compare_by_buffer_age_(const void **a_, const void **b_) +{ + const connection_t *a = *a_; + const connection_t *b = *b_; + time_t age_a = conn_get_buffer_age(a, now_ms_for_buf_cmp); + time_t age_b = conn_get_buffer_age(b, now_ms_for_buf_cmp); if (age_a < age_b) return 1; @@ -1582,69 +2041,117 @@ circuits_compare_by_oldest_queued_cell_(const void **a_, const void **b_) return -1; } -#define FRACTION_OF_CELLS_TO_RETAIN_ON_OOM 0.90 +#define FRACTION_OF_DATA_TO_RETAIN_ON_OOM 0.90 /** We're out of memory for cells, having allocated <b>current_allocation</b> * bytes' worth. Kill the 'worst' circuits until we're under - * FRACTION_OF_CIRCS_TO_RETAIN_ON_OOM of our maximum usage. */ + * FRACTION_OF_DATA_TO_RETAIN_ON_OOM of our maximum usage. */ void circuits_handle_oom(size_t current_allocation) { - /* Let's hope there's enough slack space for this allocation here... */ - smartlist_t *circlist = smartlist_new(); - circuit_t *circ; - size_t n_cells_removed=0, n_cells_to_remove; + smartlist_t *circlist; + smartlist_t *connection_array = get_connection_array(); + int conn_idx; + size_t mem_to_recover; + size_t mem_recovered=0; int n_circuits_killed=0; + int n_dirconns_killed=0; struct timeval now; + uint32_t now_ms; log_notice(LD_GENERAL, "We're low on memory. Killing circuits with " "over-long queues. (This behavior is controlled by " - "MaxMemInCellQueues.)"); + "MaxMemInQueues.)"); { - size_t mem_target = (size_t)(get_options()->MaxMemInCellQueues * - FRACTION_OF_CELLS_TO_RETAIN_ON_OOM); - size_t mem_to_recover; + size_t mem_target = (size_t)(get_options()->MaxMemInQueues * + FRACTION_OF_DATA_TO_RETAIN_ON_OOM); if (current_allocation <= mem_target) return; mem_to_recover = current_allocation - mem_target; - n_cells_to_remove = CEIL_DIV(mem_to_recover, packed_cell_mem_cost()); } - /* This algorithm itself assumes that you've got enough memory slack - * to actually run it. */ - for (circ = global_circuitlist; circ; circ = circ->next) - smartlist_add(circlist, circ); + tor_gettimeofday_cached_monotonic(&now); + now_ms = (uint32_t)tv_to_msec(&now); - /* Set circcomp_now_tmp so that the sort can work. */ - tor_gettimeofday_cached(&now); - circcomp_now_tmp = (uint32_t)tv_to_msec(&now); + circlist = circuit_get_global_list(); + SMARTLIST_FOREACH_BEGIN(circlist, circuit_t *, circ) { + circ->age_tmp = circuit_max_queued_item_age(circ, now_ms); + } SMARTLIST_FOREACH_END(circ); /* This is O(n log n); there are faster algorithms we could use instead. * Let's hope this doesn't happen enough to be in the critical path. */ - smartlist_sort(circlist, circuits_compare_by_oldest_queued_cell_); + smartlist_sort(circlist, circuits_compare_by_oldest_queued_item_); + + /* Fix up the indices before we run into trouble */ + SMARTLIST_FOREACH_BEGIN(circlist, circuit_t *, circ) { + circ->global_circuitlist_idx = circ_sl_idx; + } SMARTLIST_FOREACH_END(circ); + + /* Now sort the connection array ... */ + now_ms_for_buf_cmp = now_ms; + smartlist_sort(connection_array, conns_compare_by_buffer_age_); + now_ms_for_buf_cmp = 0; - /* Okay, now the worst circuits are at the front of the list. Let's mark - * them, and reclaim their storage aggressively. */ + /* Fix up the connection array to its new order. */ + SMARTLIST_FOREACH_BEGIN(connection_array, connection_t *, conn) { + conn->conn_array_index = conn_sl_idx; + } SMARTLIST_FOREACH_END(conn); + + /* Okay, now the worst circuits and connections are at the front of their + * respective lists. Let's mark them, and reclaim their storage + * aggressively. */ + conn_idx = 0; SMARTLIST_FOREACH_BEGIN(circlist, circuit_t *, circ) { - size_t n = n_cells_in_circ_queues(circ); + size_t n; + size_t freed; + + /* Free storage in any non-linked directory connections that have buffered + * data older than this circuit. */ + while (conn_idx < smartlist_len(connection_array)) { + connection_t *conn = smartlist_get(connection_array, conn_idx); + uint32_t conn_age = conn_get_buffer_age(conn, now_ms); + if (conn_age < circ->age_tmp) { + break; + } + if (conn->type == CONN_TYPE_DIR && conn->linked_conn == NULL) { + if (!conn->marked_for_close) + connection_mark_for_close(conn); + mem_recovered += single_conn_free_bytes(conn); + + ++n_dirconns_killed; + + if (mem_recovered >= mem_to_recover) + goto done_recovering_mem; + } + ++conn_idx; + } + + /* Now, kill the circuit. */ + n = n_cells_in_circ_queues(circ); if (! circ->marked_for_close) { circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); } marked_circuit_free_cells(circ); + freed = marked_circuit_free_stream_bytes(circ); ++n_circuits_killed; - n_cells_removed += n; - if (n_cells_removed >= n_cells_to_remove) - break; - } SMARTLIST_FOREACH_END(circ); - clean_cell_pool(); /* In case this helps. */ + mem_recovered += n * packed_cell_mem_cost(); + mem_recovered += freed; - log_notice(LD_GENERAL, "Removed "U64_FORMAT" bytes by killing %d circuits.", - U64_PRINTF_ARG(n_cells_removed * packed_cell_mem_cost()), - n_circuits_killed); + if (mem_recovered >= mem_to_recover) + goto done_recovering_mem; + } SMARTLIST_FOREACH_END(circ); - smartlist_free(circlist); + done_recovering_mem: + + log_notice(LD_GENERAL, "Removed "U64_FORMAT" bytes by killing %d circuits; " + "%d circuits remain alive. Also killed %d non-linked directory " + "connections.", + U64_PRINTF_ARG(mem_recovered), + n_circuits_killed, + smartlist_len(circlist) - n_circuits_killed, + n_dirconns_killed); } /** Verify that cpath layer <b>cp</b> has all of its invariants @@ -1716,15 +2223,10 @@ assert_circuit_ok(const circuit_t *c) tor_assert(c->purpose >= CIRCUIT_PURPOSE_MIN_ && c->purpose <= CIRCUIT_PURPOSE_MAX_); - { - /* Having a separate variable for this pleases GCC 4.2 in ways I hope I - * never understand. -NM. */ - circuit_t *nonconst_circ = (circuit_t*) c; - if (CIRCUIT_IS_ORIGIN(c)) - origin_circ = TO_ORIGIN_CIRCUIT(nonconst_circ); - else - or_circ = TO_OR_CIRCUIT(nonconst_circ); - } + if (CIRCUIT_IS_ORIGIN(c)) + origin_circ = CONST_TO_ORIGIN_CIRCUIT(c); + else + or_circ = CONST_TO_OR_CIRCUIT(c); if (c->n_chan) { tor_assert(!c->n_hop); @@ -1733,15 +2235,16 @@ assert_circuit_ok(const circuit_t *c) /* We use the _impl variant here to make sure we don't fail on marked * circuits, which would not be returned by the regular function. */ circuit_t *c2 = circuit_get_by_circid_channel_impl(c->n_circ_id, - c->n_chan); + c->n_chan, NULL); tor_assert(c == c2); } } if (or_circ && or_circ->p_chan) { if (or_circ->p_circ_id) { /* ibid */ - circuit_t *c2 = circuit_get_by_circid_channel_impl(or_circ->p_circ_id, - or_circ->p_chan); + circuit_t *c2 = + circuit_get_by_circid_channel_impl(or_circ->p_circ_id, + or_circ->p_chan, NULL); tor_assert(c == c2); } } diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index acc4b81fcd..4e600da57d 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,17 +12,22 @@ #ifndef TOR_CIRCUITLIST_H #define TOR_CIRCUITLIST_H -circuit_t * circuit_get_global_list_(void); +#include "testsupport.h" + +MOCK_DECL(smartlist_t *, circuit_get_global_list, (void)); const char *circuit_state_to_string(int state); const char *circuit_purpose_to_controller_string(uint8_t purpose); const char *circuit_purpose_to_controller_hs_state_string(uint8_t purpose); const char *circuit_purpose_to_string(uint8_t purpose); void circuit_dump_by_conn(connection_t *conn, int severity); -void circuit_dump_by_chan(channel_t *chan, int severity); void circuit_set_p_circid_chan(or_circuit_t *circ, circid_t id, channel_t *chan); void circuit_set_n_circid_chan(circuit_t *circ, circid_t id, channel_t *chan); +void channel_mark_circid_unusable(channel_t *chan, circid_t id); +void channel_mark_circid_usable(channel_t *chan, circid_t id); +time_t circuit_id_when_marked_unusable_on_channel(circid_t circ_id, + channel_t *chan); void circuit_set_state(circuit_t *circ, uint8_t state); void circuit_close_all_marked(void); int32_t circuit_initial_package_window(void); @@ -41,14 +46,16 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( const rend_data_t *rend_data); origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, const char *digest, uint8_t purpose); -or_circuit_t *circuit_get_rendezvous(const char *cookie); -or_circuit_t *circuit_get_intro_point(const char *digest); +or_circuit_t *circuit_get_rendezvous(const uint8_t *cookie); +or_circuit_t *circuit_get_intro_point(const uint8_t *digest); +void circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie); +void circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest); origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags); void circuit_mark_all_unused_circs(void); void circuit_mark_all_dirty_circs_as_unusable(void); -void circuit_mark_for_close_(circuit_t *circ, int reason, - int line, const char *file); +MOCK_DECL(void, circuit_mark_for_close_, (circuit_t *circ, int reason, + int line, const char *file)); int circuit_get_cpath_len(origin_circuit_t *circ); void circuit_clear_cpath(origin_circuit_t *circ); crypt_path_t *circuit_get_cpath_hop(origin_circuit_t *circ, int hopnum); @@ -64,5 +71,17 @@ void assert_circuit_ok(const circuit_t *c); void circuit_free_all(void); void circuits_handle_oom(size_t current_allocation); +void channel_note_destroy_pending(channel_t *chan, circid_t id); +MOCK_DECL(void, channel_note_destroy_not_pending, + (channel_t *chan, circid_t id)); + +#ifdef CIRCUITLIST_PRIVATE +STATIC void circuit_free(circuit_t *circ); +STATIC size_t n_cells_in_circ_queues(const circuit_t *c); +STATIC uint32_t circuit_max_queued_data_age(const circuit_t *c, uint32_t now); +STATIC uint32_t circuit_max_queued_cell_age(const circuit_t *c, uint32_t now); +STATIC uint32_t circuit_max_queued_item_age(const circuit_t *c, uint32_t now); +#endif + #endif diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 545cfd0650..a77bffac90 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,6 +10,7 @@ #include "channel.h" #include "circuitlist.h" #include "circuitmux.h" +#include "relay.h" /* * Private typedefs for circuitmux.c @@ -115,6 +116,22 @@ struct circuitmux_s { */ struct circuit_t *active_circuits_head, *active_circuits_tail; + /** List of queued destroy cells */ + cell_queue_t destroy_cell_queue; + /** Boolean: True iff the last cell to circuitmux_get_first_active_circuit + * returned the destroy queue. Used to force alternation between + * destroy/non-destroy cells. + * + * XXXX There is no reason to think that alternating is a particularly good + * approach -- it's just designed to prevent destroys from starving other + * cells completely. + */ + unsigned int last_cell_was_destroy : 1; + /** Destroy counter: increment this when a destroy gets queued, decrement + * when we unqueue it, so we can test to make sure they don't starve. + */ + int64_t destroy_ctr; + /* * Circuitmux policy; if this is non-NULL, it can override the built- * in round-robin active circuits behavior. This is how EWMA works in @@ -193,6 +210,11 @@ static void circuitmux_assert_okay_pass_one(circuitmux_t *cmux); static void circuitmux_assert_okay_pass_two(circuitmux_t *cmux); static void circuitmux_assert_okay_pass_three(circuitmux_t *cmux); +/* Static global variables */ + +/** Count the destroy balance to debug destroy queue logic */ +static int64_t global_destroy_ctr = 0; + /* Function definitions */ /** @@ -341,9 +363,9 @@ HT_HEAD(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t); /* Emit a bunch of hash table stuff */ HT_PROTOTYPE(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node, chanid_circid_entry_hash, chanid_circid_entries_eq); -HT_GENERATE(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node, - chanid_circid_entry_hash, chanid_circid_entries_eq, 0.6, - malloc, realloc, free); +HT_GENERATE2(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node, + chanid_circid_entry_hash, chanid_circid_entries_eq, 0.6, + tor_reallocarray_, tor_free_) /* * Circuitmux alloc/free functions @@ -361,16 +383,20 @@ circuitmux_alloc(void) rv = tor_malloc_zero(sizeof(*rv)); rv->chanid_circid_map = tor_malloc_zero(sizeof(*( rv->chanid_circid_map))); HT_INIT(chanid_circid_muxinfo_map, rv->chanid_circid_map); + cell_queue_init(&rv->destroy_cell_queue); return rv; } /** * Detach all circuits from a circuitmux (use before circuitmux_free()) + * + * If <b>detached_out</b> is non-NULL, add every detached circuit_t to + * detached_out. */ void -circuitmux_detach_all_circuits(circuitmux_t *cmux) +circuitmux_detach_all_circuits(circuitmux_t *cmux, smartlist_t *detached_out) { chanid_circid_muxinfo_t **i = NULL, *to_remove; channel_t *chan = NULL; @@ -386,7 +412,11 @@ circuitmux_detach_all_circuits(circuitmux_t *cmux) i = HT_START(chanid_circid_muxinfo_map, cmux->chanid_circid_map); while (i) { to_remove = *i; - if (to_remove) { + + if (! to_remove) { + log_warn(LD_BUG, "Somehow, an HT iterator gave us a NULL pointer."); + break; + } else { /* Find a channel and circuit */ chan = channel_find_by_global_id(to_remove->chan_id); if (chan) { @@ -407,6 +437,9 @@ circuitmux_detach_all_circuits(circuitmux_t *cmux) /* Clear n_mux */ circ->n_mux = NULL; + + if (detached_out) + smartlist_add(detached_out, circ); } else if (circ->magic == OR_CIRCUIT_MAGIC) { /* * Update active_circuits et al.; this does policy notifies, so @@ -422,6 +455,9 @@ circuitmux_detach_all_circuits(circuitmux_t *cmux) * so clear p_mux. */ TO_OR_CIRCUIT(circ)->p_mux = NULL; + + if (detached_out) + smartlist_add(detached_out, circ); } else { /* Complain and move on */ log_warn(LD_CIRC, @@ -476,6 +512,31 @@ circuitmux_detach_all_circuits(circuitmux_t *cmux) cmux->n_cells = 0; } +/** Reclaim all circuit IDs currently marked as unusable on <b>chan</b> because + * of pending destroy cells in <b>cmux</b>. + * + * This function must be called AFTER circuits are unlinked from the (channel, + * circuid-id) map with circuit_unlink_all_from_channel(), but before calling + * circuitmux_free(). + */ +void +circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, channel_t *chan) +{ + packed_cell_t *cell; + int n_bad = 0; + TOR_SIMPLEQ_FOREACH(cell, &cmux->destroy_cell_queue.head, next) { + circid_t circid = 0; + if (packed_cell_is_destroy(chan, cell, &circid)) { + channel_mark_circid_usable(chan, circid); + } else { + ++n_bad; + } + } + if (n_bad) + log_warn(LD_BUG, "%d cell(s) on destroy queue did not look like a " + "DESTROY cell.", n_bad); +} + /** * Free a circuitmux_t; the circuits must be detached first with * circuitmux_detach_all_circuits(). @@ -508,6 +569,30 @@ circuitmux_free(circuitmux_t *cmux) tor_free(cmux->chanid_circid_map); } + /* + * We're throwing away some destroys; log the counter and + * adjust the global counter by the queue size. + */ + if (cmux->destroy_cell_queue.n > 0) { + cmux->destroy_ctr -= cmux->destroy_cell_queue.n; + global_destroy_ctr -= cmux->destroy_cell_queue.n; + log_debug(LD_CIRC, + "Freeing cmux at %p with %u queued destroys; the last cmux " + "destroy balance was "I64_FORMAT", global is "I64_FORMAT, + cmux, cmux->destroy_cell_queue.n, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); + } else { + log_debug(LD_CIRC, + "Freeing cmux at %p with no queued destroys, the cmux destroy " + "balance was "I64_FORMAT", global is "I64_FORMAT, + cmux, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); + } + + cell_queue_clear(&cmux->destroy_cell_queue); + tor_free(cmux); } @@ -536,8 +621,8 @@ circuitmux_clear_policy(circuitmux_t *cmux) * Return the policy currently installed on a circuitmux_t */ -const circuitmux_policy_t * -circuitmux_get_policy(circuitmux_t *cmux) +MOCK_IMPL(const circuitmux_policy_t *, +circuitmux_get_policy, (circuitmux_t *cmux)) { tor_assert(cmux); @@ -811,12 +896,12 @@ circuitmux_num_cells_for_circuit(circuitmux_t *cmux, circuit_t *circ) * Query total number of available cells on a circuitmux */ -unsigned int -circuitmux_num_cells(circuitmux_t *cmux) +MOCK_IMPL(unsigned int, +circuitmux_num_cells, (circuitmux_t *cmux)) { tor_assert(cmux); - return cmux->n_cells; + return cmux->n_cells + cmux->destroy_cell_queue.n; } /** @@ -851,9 +936,9 @@ circuitmux_num_circuits(circuitmux_t *cmux) * Attach a circuit to a circuitmux, for the specified direction. */ -void -circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, - cell_direction_t direction) +MOCK_IMPL(void, +circuitmux_attach_circuit,(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction)) { channel_t *chan = NULL; uint64_t channel_id; @@ -1000,15 +1085,18 @@ circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, * no-op if not attached. */ -void -circuitmux_detach_circuit(circuitmux_t *cmux, circuit_t *circ) +MOCK_IMPL(void, +circuitmux_detach_circuit,(circuitmux_t *cmux, circuit_t *circ)) { chanid_circid_muxinfo_t search, *hashent = NULL; /* * Use this to keep track of whether we found it for n_chan or * p_chan for consistency checking. + * + * The 0 initializer is not a valid cell_direction_t value. + * We assert that it has been replaced with a valid value before it is used. */ - cell_direction_t last_searched_direction; + cell_direction_t last_searched_direction = 0; tor_assert(cmux); tor_assert(cmux->chanid_circid_map); @@ -1038,6 +1126,9 @@ circuitmux_detach_circuit(circuitmux_t *cmux, circuit_t *circ) } } + tor_assert(last_searched_direction == CELL_DIRECTION_OUT + || last_searched_direction == CELL_DIRECTION_IN); + /* * If hashent isn't NULL, we have a circuit to detach; don't remove it from * the map until later of circuitmux_make_circuit_inactive() breaks. @@ -1368,16 +1459,36 @@ circuitmux_set_num_cells(circuitmux_t *cmux, circuit_t *circ, /** * Pick a circuit to send from, using the active circuits list or a * circuitmux policy if one is available. This is called from channel.c. + * + * If we would rather send a destroy cell, return NULL and set + * *<b>destroy_queue_out</b> to the destroy queue. + * + * If we have nothing to send, set *<b>destroy_queue_out</b> to NULL and + * return NULL. */ circuit_t * -circuitmux_get_first_active_circuit(circuitmux_t *cmux) +circuitmux_get_first_active_circuit(circuitmux_t *cmux, + cell_queue_t **destroy_queue_out) { circuit_t *circ = NULL; tor_assert(cmux); + tor_assert(destroy_queue_out); + + *destroy_queue_out = NULL; + + if (cmux->destroy_cell_queue.n && + (!cmux->last_cell_was_destroy || cmux->n_active_circuits == 0)) { + /* We have destroy cells to send, and either we just sent a relay cell, + * or we have no relay cells to send. */ - if (cmux->n_active_circuits > 0) { + /* XXXX We should let the cmux policy have some say in this eventually. */ + /* XXXX Alternating is not a terribly brilliant approach here. */ + *destroy_queue_out = &cmux->destroy_cell_queue; + + cmux->last_cell_was_destroy = 1; + } else if (cmux->n_active_circuits > 0) { /* We also must have a cell available for this to be the case */ tor_assert(cmux->n_cells > 0); /* Do we have a policy-provided circuit selector? */ @@ -1389,7 +1500,11 @@ circuitmux_get_first_active_circuit(circuitmux_t *cmux) tor_assert(cmux->active_circuits_head); circ = cmux->active_circuits_head; } - } else tor_assert(cmux->n_cells == 0); + cmux->last_cell_was_destroy = 0; + } else { + tor_assert(cmux->n_cells == 0); + tor_assert(cmux->destroy_cell_queue.n == 0); + } return circ; } @@ -1463,6 +1578,26 @@ circuitmux_notify_xmit_cells(circuitmux_t *cmux, circuit_t *circ, circuitmux_assert_okay_paranoid(cmux); } +/** + * Notify the circuitmux that a destroy was sent, so we can update + * the counter. + */ + +void +circuitmux_notify_xmit_destroy(circuitmux_t *cmux) +{ + tor_assert(cmux); + + --(cmux->destroy_ctr); + --(global_destroy_ctr); + log_debug(LD_CIRC, + "Cmux at %p sent a destroy, cmux counter is now "I64_FORMAT", " + "global counter is now "I64_FORMAT, + cmux, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); +} + /* * Circuitmux consistency checking assertions */ @@ -1743,3 +1878,124 @@ circuitmux_assert_okay_pass_three(circuitmux_t *cmux) } } +/*DOCDOC */ +void +circuitmux_append_destroy_cell(channel_t *chan, + circuitmux_t *cmux, + circid_t circ_id, + uint8_t reason) +{ + cell_t cell; + memset(&cell, 0, sizeof(cell_t)); + cell.circ_id = circ_id; + cell.command = CELL_DESTROY; + cell.payload[0] = (uint8_t) reason; + + cell_queue_append_packed_copy(NULL, &cmux->destroy_cell_queue, 0, &cell, + chan->wide_circ_ids, 0); + + /* Destroy entering the queue, update counters */ + ++(cmux->destroy_ctr); + ++global_destroy_ctr; + log_debug(LD_CIRC, + "Cmux at %p queued a destroy for circ %u, cmux counter is now " + I64_FORMAT", global counter is now "I64_FORMAT, + cmux, circ_id, + I64_PRINTF_ARG(cmux->destroy_ctr), + I64_PRINTF_ARG(global_destroy_ctr)); + + /* XXXX Duplicate code from append_cell_to_circuit_queue */ + if (!channel_has_queued_writes(chan)) { + /* There is no data at all waiting to be sent on the outbuf. Add a + * cell, so that we can notice when it gets flushed, flushed_some can + * get called, and we can start putting more data onto the buffer then. + */ + log_debug(LD_GENERAL, "Primed a buffer."); + channel_flush_from_first_active_circuit(chan, 1); + } +} + +/*DOCDOC; for debugging 12184. This runs slowly. */ +int64_t +circuitmux_count_queued_destroy_cells(const channel_t *chan, + const circuitmux_t *cmux) +{ + int64_t n_destroy_cells = cmux->destroy_ctr; + int64_t destroy_queue_size = cmux->destroy_cell_queue.n; + + int64_t manual_total = 0; + int64_t manual_total_in_map = 0; + packed_cell_t *cell; + + TOR_SIMPLEQ_FOREACH(cell, &cmux->destroy_cell_queue.head, next) { + circid_t id; + ++manual_total; + + id = packed_cell_get_circid(cell, chan->wide_circ_ids); + if (circuit_id_in_use_on_channel(id, (channel_t*)chan)) + ++manual_total_in_map; + } + + if (n_destroy_cells != destroy_queue_size || + n_destroy_cells != manual_total || + n_destroy_cells != manual_total_in_map) { + log_warn(LD_BUG, " Discrepancy in counts for queued destroy cells on " + "circuitmux. n="I64_FORMAT". queue_size="I64_FORMAT". " + "manual_total="I64_FORMAT". manual_total_in_map="I64_FORMAT".", + I64_PRINTF_ARG(n_destroy_cells), + I64_PRINTF_ARG(destroy_queue_size), + I64_PRINTF_ARG(manual_total), + I64_PRINTF_ARG(manual_total_in_map)); + } + + return n_destroy_cells; +} + +/** + * Compare cmuxes to see which is more preferred; return < 0 if + * cmux_1 has higher priority (i.e., cmux_1 < cmux_2 in the scheduler's + * sort order), > 0 if cmux_2 has higher priority, or 0 if they are + * equally preferred. + * + * If the cmuxes have different cmux policies or the policy does not + * support the cmp_cmux method, return 0. + */ + +MOCK_IMPL(int, +circuitmux_compare_muxes, (circuitmux_t *cmux_1, circuitmux_t *cmux_2)) +{ + const circuitmux_policy_t *policy; + + tor_assert(cmux_1); + tor_assert(cmux_2); + + if (cmux_1 == cmux_2) { + /* Equivalent because they're the same cmux */ + return 0; + } + + if (cmux_1->policy && cmux_2->policy) { + if (cmux_1->policy == cmux_2->policy) { + policy = cmux_1->policy; + + if (policy->cmp_cmux) { + /* Okay, we can compare! */ + return policy->cmp_cmux(cmux_1, cmux_1->policy_data, + cmux_2, cmux_2->policy_data); + } else { + /* + * Equivalent because the policy doesn't know how to compare between + * muxes. + */ + return 0; + } + } else { + /* Equivalent because they have different policies */ + return 0; + } + } else { + /* Equivalent because one or both are missing a policy */ + return 0; + } +} + diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index 25644ffab7..837e3961bf 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,6 +10,7 @@ #define TOR_CIRCUITMUX_H #include "or.h" +#include "testsupport.h" typedef struct circuitmux_policy_s circuitmux_policy_t; typedef struct circuitmux_policy_data_s circuitmux_policy_data_t; @@ -56,6 +57,9 @@ struct circuitmux_policy_s { /* Choose a circuit */ circuit_t * (*pick_active_circuit)(circuitmux_t *cmux, circuitmux_policy_data_t *pol_data); + /* Optional: channel comparator for use by the scheduler */ + int (*cmp_cmux)(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1, + circuitmux_t *cmux_2, circuitmux_policy_data_t *pol_data_2); }; /* @@ -98,12 +102,14 @@ void circuitmux_assert_okay(circuitmux_t *cmux); /* Create/destroy */ circuitmux_t * circuitmux_alloc(void); -void circuitmux_detach_all_circuits(circuitmux_t *cmux); +void circuitmux_detach_all_circuits(circuitmux_t *cmux, + smartlist_t *detached_out); void circuitmux_free(circuitmux_t *cmux); /* Policy control */ void circuitmux_clear_policy(circuitmux_t *cmux); -const circuitmux_policy_t * circuitmux_get_policy(circuitmux_t *cmux); +MOCK_DECL(const circuitmux_policy_t *, + circuitmux_get_policy, (circuitmux_t *cmux)); void circuitmux_set_policy(circuitmux_t *cmux, const circuitmux_policy_t *pol); @@ -115,22 +121,40 @@ int circuitmux_is_circuit_attached(circuitmux_t *cmux, circuit_t *circ); int circuitmux_is_circuit_active(circuitmux_t *cmux, circuit_t *circ); unsigned int circuitmux_num_cells_for_circuit(circuitmux_t *cmux, circuit_t *circ); -unsigned int circuitmux_num_cells(circuitmux_t *cmux); +MOCK_DECL(unsigned int, circuitmux_num_cells, (circuitmux_t *cmux)); unsigned int circuitmux_num_circuits(circuitmux_t *cmux); unsigned int circuitmux_num_active_circuits(circuitmux_t *cmux); +/* Debuging interface - slow. */ +int64_t circuitmux_count_queued_destroy_cells(const channel_t *chan, + const circuitmux_t *cmux); + /* Channel interface */ -circuit_t * circuitmux_get_first_active_circuit(circuitmux_t *cmux); +circuit_t * circuitmux_get_first_active_circuit(circuitmux_t *cmux, + cell_queue_t **destroy_queue_out); void circuitmux_notify_xmit_cells(circuitmux_t *cmux, circuit_t *circ, unsigned int n_cells); +void circuitmux_notify_xmit_destroy(circuitmux_t *cmux); /* Circuit interface */ -void circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, - cell_direction_t direction); -void circuitmux_detach_circuit(circuitmux_t *cmux, circuit_t *circ); +MOCK_DECL(void, circuitmux_attach_circuit, (circuitmux_t *cmux, + circuit_t *circ, + cell_direction_t direction)); +MOCK_DECL(void, circuitmux_detach_circuit, + (circuitmux_t *cmux, circuit_t *circ)); void circuitmux_clear_num_cells(circuitmux_t *cmux, circuit_t *circ); void circuitmux_set_num_cells(circuitmux_t *cmux, circuit_t *circ, unsigned int n_cells); +void circuitmux_append_destroy_cell(channel_t *chan, + circuitmux_t *cmux, circid_t circ_id, + uint8_t reason); +void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, + channel_t *chan); + +/* Optional interchannel comparisons for scheduling */ +MOCK_DECL(int, circuitmux_compare_muxes, + (circuitmux_t *cmux_1, circuitmux_t *cmux_2)); + #endif /* TOR_CIRCUITMUX_H */ diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index 3f37d7b9a0..1c0318de06 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.c @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -187,6 +187,9 @@ ewma_notify_xmit_cells(circuitmux_t *cmux, static circuit_t * ewma_pick_active_circuit(circuitmux_t *cmux, circuitmux_policy_data_t *pol_data); +static int +ewma_cmp_cmux(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1, + circuitmux_t *cmux_2, circuitmux_policy_data_t *pol_data_2); /*** EWMA global variables ***/ @@ -209,7 +212,8 @@ circuitmux_policy_t ewma_policy = { /*.notify_circ_inactive =*/ ewma_notify_circ_inactive, /*.notify_set_n_cells =*/ NULL, /* EWMA doesn't need this */ /*.notify_xmit_cells =*/ ewma_notify_xmit_cells, - /*.pick_active_circuit =*/ ewma_pick_active_circuit + /*.pick_active_circuit =*/ ewma_pick_active_circuit, + /*.cmp_cmux =*/ ewma_cmp_cmux }; /*** EWMA method implementations using the below EWMA helper functions ***/ @@ -273,8 +277,8 @@ ewma_alloc_circ_data(circuitmux_t *cmux, tor_assert(circ); tor_assert(direction == CELL_DIRECTION_OUT || direction == CELL_DIRECTION_IN); - /* Shut the compiler up */ - tor_assert(cell_count == cell_count); + /* Shut the compiler up without triggering -Wtautological-compare */ + (void)cell_count; cdata = tor_malloc_zero(sizeof(*cdata)); cdata->base_.magic = EWMA_POL_CIRC_DATA_MAGIC; @@ -453,6 +457,58 @@ ewma_pick_active_circuit(circuitmux_t *cmux, return circ; } +/** + * Compare two EWMA cmuxes, and return -1, 0 or 1 to indicate which should + * be more preferred - see circuitmux_compare_muxes() of circuitmux.c. + */ + +static int +ewma_cmp_cmux(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1, + circuitmux_t *cmux_2, circuitmux_policy_data_t *pol_data_2) +{ + ewma_policy_data_t *p1 = NULL, *p2 = NULL; + cell_ewma_t *ce1 = NULL, *ce2 = NULL; + + tor_assert(cmux_1); + tor_assert(pol_data_1); + tor_assert(cmux_2); + tor_assert(pol_data_2); + + p1 = TO_EWMA_POL_DATA(pol_data_1); + p2 = TO_EWMA_POL_DATA(pol_data_1); + + if (p1 != p2) { + /* Get the head cell_ewma_t from each queue */ + if (smartlist_len(p1->active_circuit_pqueue) > 0) { + ce1 = smartlist_get(p1->active_circuit_pqueue, 0); + } + + if (smartlist_len(p2->active_circuit_pqueue) > 0) { + ce2 = smartlist_get(p2->active_circuit_pqueue, 0); + } + + /* Got both of them? */ + if (ce1 != NULL && ce2 != NULL) { + /* Pick whichever one has the better best circuit */ + return compare_cell_ewma_counts(ce1, ce2); + } else { + if (ce1 != NULL ) { + /* We only have a circuit on cmux_1, so prefer it */ + return -1; + } else if (ce2 != NULL) { + /* We only have a circuit on cmux_2, so prefer it */ + return 1; + } else { + /* No circuits at all; no preference */ + return 0; + } + } + } else { + /* We got identical params */ + return 0; + } +} + /** Helper for sorting cell_ewma_t values in their priority queue. */ static int compare_cell_ewma_counts(const void *p1, const void *p2) diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h index a512745c77..3feef834dd 100644 --- a/src/or/circuitmux_ewma.h +++ b/src/or/circuitmux_ewma.h @@ -1,4 +1,4 @@ -/* * Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* * Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 1d7812bf2b..7b3ad56537 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define CIRCUITSTATS_PRIVATE @@ -12,12 +12,17 @@ #include "config.h" #include "confparse.h" #include "control.h" +#include "main.h" #include "networkstatus.h" #include "statefile.h" #undef log #include <math.h> +static void cbt_control_event_buildtimeout_set( + const circuit_build_times_t *cbt, + buildtimeout_set_event_t type); + #define CBT_BIN_TO_MS(bin) ((bin)*CBT_BIN_WIDTH + (CBT_BIN_WIDTH/2)) /** Global list of circuit build times */ @@ -26,12 +31,46 @@ // vary in their own latency. The downside of this is that guards // can change frequently, so we'd be building a lot more circuits // most likely. -/* XXXX024 Make this static; add accessor functions. */ -circuit_build_times_t circ_times; +static circuit_build_times_t circ_times; +#ifdef TOR_UNIT_TESTS /** If set, we're running the unit tests: we should avoid clobbering * our state file or accessing get_options() or get_or_state() */ static int unit_tests = 0; +#else +#define unit_tests 0 +#endif + +/** Return a pointer to the data structure describing our current circuit + * build time history and computations. */ +const circuit_build_times_t * +get_circuit_build_times(void) +{ + return &circ_times; +} + +/** As get_circuit_build_times, but return a mutable pointer. */ +circuit_build_times_t * +get_circuit_build_times_mutable(void) +{ + return &circ_times; +} + +/** Return the time to wait before actually closing an under-construction, in + * milliseconds. */ +double +get_circuit_build_close_time_ms(void) +{ + return circ_times.close_ms; +} + +/** Return the time to wait before giving up on an under-construction circuit, + * in milliseconds. */ +double +get_circuit_build_timeout_ms(void) +{ + return circ_times.timeout_ms; +} /** * This function decides if CBT learning should be disabled. It returns @@ -56,18 +95,22 @@ circuit_build_times_disabled(void) if (consensus_disabled || config_disabled || dirauth_disabled || state_disabled) { +#if 0 log_debug(LD_CIRC, "CircuitBuildTime learning is disabled. " "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); +#endif return 1; } else { +#if 0 log_debug(LD_CIRC, "CircuitBuildTime learning is not disabled. " "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); +#endif return 0; } } @@ -154,7 +197,7 @@ circuit_build_times_min_circs_to_observe(void) /** Return true iff <b>cbt</b> has recorded enough build times that we * want to start acting on the timeout it implies. */ int -circuit_build_times_enough_to_compute(circuit_build_times_t *cbt) +circuit_build_times_enough_to_compute(const circuit_build_times_t *cbt) { return cbt->total_build_times >= circuit_build_times_min_circs_to_observe(); } @@ -361,7 +404,7 @@ circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, * distress anyway, so memory correctness here is paramount over * doing acrobatics to preserve the array. */ - recent_circs = tor_malloc_zero(sizeof(int8_t)*num); + recent_circs = tor_calloc(num, sizeof(int8_t)); if (cbt->liveness.timeouts_after_firsthop && cbt->liveness.num_recent_circs > 0) { memcpy(recent_circs, cbt->liveness.timeouts_after_firsthop, @@ -438,7 +481,7 @@ circuit_build_times_get_initial_timeout(void) * Leave estimated parameters, timeout and network liveness intact * for future use. */ -void +STATIC void circuit_build_times_reset(circuit_build_times_t *cbt) { memset(cbt->circuit_build_times, 0, sizeof(cbt->circuit_build_times)); @@ -465,13 +508,13 @@ circuit_build_times_init(circuit_build_times_t *cbt) cbt->liveness.num_recent_circs = circuit_build_times_recent_circuit_count(NULL); cbt->liveness.timeouts_after_firsthop = - tor_malloc_zero(sizeof(int8_t)*cbt->liveness.num_recent_circs); + tor_calloc(cbt->liveness.num_recent_circs, sizeof(int8_t)); } else { cbt->liveness.num_recent_circs = 0; cbt->liveness.timeouts_after_firsthop = NULL; } cbt->close_ms = cbt->timeout_ms = circuit_build_times_get_initial_timeout(); - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); + cbt_control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); } /** @@ -557,7 +600,7 @@ circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time) * Return maximum circuit build time */ static build_time_t -circuit_build_times_max(circuit_build_times_t *cbt) +circuit_build_times_max(const circuit_build_times_t *cbt) { int i = 0; build_time_t max_build_time = 0; @@ -598,7 +641,7 @@ circuit_build_times_min(circuit_build_times_t *cbt) * The return value must be freed by the caller. */ static uint32_t * -circuit_build_times_create_histogram(circuit_build_times_t *cbt, +circuit_build_times_create_histogram(const circuit_build_times_t *cbt, build_time_t *nbins) { uint32_t *histogram; @@ -606,7 +649,7 @@ circuit_build_times_create_histogram(circuit_build_times_t *cbt, int i, c; *nbins = 1 + (max_build_time / CBT_BIN_WIDTH); - histogram = tor_malloc_zero(*nbins * sizeof(build_time_t)); + histogram = tor_calloc(*nbins, sizeof(build_time_t)); // calculate histogram for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { @@ -648,7 +691,7 @@ circuit_build_times_get_xm(circuit_build_times_t *cbt) if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE) num_modes = 1; - nth_max_bin = (build_time_t*)tor_malloc_zero(num_modes*sizeof(build_time_t)); + nth_max_bin = tor_calloc(num_modes, sizeof(build_time_t)); /* Determine the N most common build times */ for (i = 0; i < nbins; i++) { @@ -688,7 +731,7 @@ circuit_build_times_get_xm(circuit_build_times_t *cbt) * the or_state_t state structure. */ void -circuit_build_times_update_state(circuit_build_times_t *cbt, +circuit_build_times_update_state(const circuit_build_times_t *cbt, or_state_t *state) { uint32_t *histogram; @@ -830,7 +873,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, } /* build_time_t 0 means uninitialized */ - loaded_times = tor_malloc_zero(sizeof(build_time_t)*state->TotalBuildTimes); + loaded_times = tor_calloc(state->TotalBuildTimes, sizeof(build_time_t)); for (line = state->BuildtimeHistogram; line; line = line->next) { smartlist_t *args = smartlist_new(); @@ -949,7 +992,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, * an acceptable approximation because we are only concerned with the * accuracy of the CDF of the tail. */ -int +STATIC int circuit_build_times_update_alpha(circuit_build_times_t *cbt) { build_time_t *x=cbt->circuit_build_times; @@ -1031,9 +1074,9 @@ circuit_build_times_update_alpha(circuit_build_times_t *cbt) * random_sample_from_Pareto_distribution * That's right. I'll cite wikipedia all day long. * - * Return value is in milliseconds. + * Return value is in milliseconds, clamped to INT32_MAX. */ -double +STATIC double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, double quantile) { @@ -1042,7 +1085,21 @@ circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, tor_assert(1.0-quantile > 0); tor_assert(cbt->Xm > 0); - ret = cbt->Xm/pow(1.0-quantile,1.0/cbt->alpha); + /* If either alpha or p are 0, we would divide by zero, yielding an + * infinite (double) result; which would be clamped to INT32_MAX. + * Instead, initialise ret to INT32_MAX, and skip over these + * potentially illegal/trapping divides by zero. + */ + ret = INT32_MAX; + + if (cbt->alpha > 0) { + double p; + p = pow(1.0-quantile,1.0/cbt->alpha); + if (p > 0) { + ret = cbt->Xm/p; + } + } + if (ret > INT32_MAX) { ret = INT32_MAX; } @@ -1050,6 +1107,7 @@ circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, return ret; } +#ifdef TOR_UNIT_TESTS /** Pareto CDF */ double circuit_build_times_cdf(circuit_build_times_t *cbt, double x) @@ -1060,7 +1118,9 @@ circuit_build_times_cdf(circuit_build_times_t *cbt, double x) tor_assert(0 <= ret && ret <= 1.0); return ret; } +#endif +#ifdef TOR_UNIT_TESTS /** * Generate a synthetic time using our distribution parameters. * @@ -1093,7 +1153,9 @@ circuit_build_times_generate_sample(circuit_build_times_t *cbt, tor_assert(ret > 0); return ret; } +#endif +#ifdef TOR_UNIT_TESTS /** * Estimate an initial alpha parameter by solving the quantile * function with a quantile point and a specific timeout value. @@ -1114,12 +1176,13 @@ circuit_build_times_initial_alpha(circuit_build_times_t *cbt, (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms)); tor_assert(cbt->alpha > 0); } +#endif /** * Returns true if we need circuits to be built */ int -circuit_build_times_needs_circuits(circuit_build_times_t *cbt) +circuit_build_times_needs_circuits(const circuit_build_times_t *cbt) { /* Return true if < MIN_CIRCUITS_TO_OBSERVE */ return !circuit_build_times_enough_to_compute(cbt); @@ -1130,13 +1193,19 @@ circuit_build_times_needs_circuits(circuit_build_times_t *cbt) * right now. */ int -circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt) +circuit_build_times_needs_circuits_now(const circuit_build_times_t *cbt) { return circuit_build_times_needs_circuits(cbt) && approx_time()-cbt->last_circ_at > circuit_build_times_test_frequency(); } /** + * How long should we be unreachable before we think we need to check if + * our published IP address has changed. + */ +#define CIRCUIT_TIMEOUT_BEFORE_RECHECK_IP (60*3) + +/** * Called to indicate that the network showed some signs of liveness, * i.e. we received a cell. * @@ -1151,12 +1220,15 @@ circuit_build_times_network_is_live(circuit_build_times_t *cbt) { time_t now = approx_time(); if (cbt->liveness.nonlive_timeouts > 0) { + time_t time_since_live = now - cbt->liveness.network_last_live; log_notice(LD_CIRC, "Tor now sees network activity. Restoring circuit build " "timeout recording. Network was down for %d seconds " "during %d circuit attempts.", - (int)(now - cbt->liveness.network_last_live), + (int)time_since_live, cbt->liveness.nonlive_timeouts); + if (time_since_live > CIRCUIT_TIMEOUT_BEFORE_RECHECK_IP) + reschedule_descriptor_update_check(); } cbt->liveness.network_last_live = now; cbt->liveness.nonlive_timeouts = 0; @@ -1263,7 +1335,7 @@ circuit_build_times_network_close(circuit_build_times_t *cbt, * in the case of recent liveness changes. */ int -circuit_build_times_network_check_live(circuit_build_times_t *cbt) +circuit_build_times_network_check_live(const circuit_build_times_t *cbt) { if (cbt->liveness.nonlive_timeouts > 0) { return 0; @@ -1282,7 +1354,7 @@ circuit_build_times_network_check_live(circuit_build_times_t *cbt) * to restart the process of building test circuits and estimating a * new timeout. */ -int +STATIC int circuit_build_times_network_check_changed(circuit_build_times_t *cbt) { int total_build_times = cbt->total_build_times; @@ -1313,10 +1385,11 @@ circuit_build_times_network_check_changed(circuit_build_times_t *cbt) } cbt->liveness.after_firsthop_idx = 0; +#define MAX_TIMEOUT ((int32_t) (INT32_MAX/2)) /* Check to see if this has happened before. If so, double the timeout * to give people on abysmally bad network connections a shot at access */ if (cbt->timeout_ms >= circuit_build_times_get_initial_timeout()) { - if (cbt->timeout_ms > INT32_MAX/2 || cbt->close_ms > INT32_MAX/2) { + if (cbt->timeout_ms > MAX_TIMEOUT || cbt->close_ms > MAX_TIMEOUT) { log_warn(LD_CIRC, "Insanely large circuit build timeout value. " "(timeout = %fmsec, close = %fmsec)", cbt->timeout_ms, cbt->close_ms); @@ -1328,8 +1401,9 @@ circuit_build_times_network_check_changed(circuit_build_times_t *cbt) cbt->close_ms = cbt->timeout_ms = circuit_build_times_get_initial_timeout(); } +#undef MAX_TIMEOUT - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); + cbt_control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); log_notice(LD_CIRC, "Your network connection speed appears to have changed. Resetting " @@ -1511,7 +1585,7 @@ circuit_build_times_set_timeout(circuit_build_times_t *cbt) } } - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_COMPUTED); + cbt_control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_COMPUTED); timeout_rate = circuit_build_times_timeout_rate(cbt); @@ -1546,6 +1620,8 @@ circuit_build_times_set_timeout(circuit_build_times_t *cbt) cbt->total_build_times); } } + +#ifdef TOR_UNIT_TESTS /** Make a note that we're running unit tests (rather than running Tor * itself), so we avoid clobbering our state file. */ void @@ -1553,4 +1629,46 @@ circuitbuild_running_unit_tests(void) { unit_tests = 1; } +#endif + +void +circuit_build_times_update_last_circ(circuit_build_times_t *cbt) +{ + cbt->last_circ_at = approx_time(); +} + +static void +cbt_control_event_buildtimeout_set(const circuit_build_times_t *cbt, + buildtimeout_set_event_t type) +{ + char *args = NULL; + double qnt; + + switch (type) { + case BUILDTIMEOUT_SET_EVENT_RESET: + case BUILDTIMEOUT_SET_EVENT_SUSPENDED: + case BUILDTIMEOUT_SET_EVENT_DISCARD: + qnt = 1.0; + break; + case BUILDTIMEOUT_SET_EVENT_COMPUTED: + case BUILDTIMEOUT_SET_EVENT_RESUME: + default: + qnt = circuit_build_times_quantile_cutoff(); + break; + } + + tor_asprintf(&args, "TOTAL_TIMES=%lu " + "TIMEOUT_MS=%lu XM=%lu ALPHA=%f CUTOFF_QUANTILE=%f " + "TIMEOUT_RATE=%f CLOSE_MS=%lu CLOSE_RATE=%f", + (unsigned long)cbt->total_build_times, + (unsigned long)cbt->timeout_ms, + (unsigned long)cbt->Xm, cbt->alpha, qnt, + circuit_build_times_timeout_rate(cbt), + (unsigned long)cbt->close_ms, + circuit_build_times_close_rate(cbt)); + + control_event_buildtimeout_set(type, args); + + tor_free(args); +} diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h index 87dce99f4f..fe05a24e97 100644 --- a/src/or/circuitstats.h +++ b/src/or/circuitstats.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,11 +12,14 @@ #ifndef TOR_CIRCUITSTATS_H #define TOR_CIRCUITSTATS_H -extern circuit_build_times_t circ_times; +const circuit_build_times_t *get_circuit_build_times(void); +circuit_build_times_t *get_circuit_build_times_mutable(void); +double get_circuit_build_close_time_ms(void); +double get_circuit_build_timeout_ms(void); int circuit_build_times_disabled(void); -int circuit_build_times_enough_to_compute(circuit_build_times_t *cbt); -void circuit_build_times_update_state(circuit_build_times_t *cbt, +int circuit_build_times_enough_to_compute(const circuit_build_times_t *cbt); +void circuit_build_times_update_state(const circuit_build_times_t *cbt, or_state_t *state); int circuit_build_times_parse_state(circuit_build_times_t *cbt, or_state_t *state); @@ -27,9 +30,9 @@ int circuit_build_times_count_close(circuit_build_times_t *cbt, void circuit_build_times_set_timeout(circuit_build_times_t *cbt); int circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time); -int circuit_build_times_needs_circuits(circuit_build_times_t *cbt); +int circuit_build_times_needs_circuits(const circuit_build_times_t *cbt); -int circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt); +int circuit_build_times_needs_circuits_now(const circuit_build_times_t *cbt); void circuit_build_times_init(circuit_build_times_t *cbt); void circuit_build_times_free_timeouts(circuit_build_times_t *cbt); void circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, @@ -37,29 +40,59 @@ void circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, double circuit_build_times_timeout_rate(const circuit_build_times_t *cbt); double circuit_build_times_close_rate(const circuit_build_times_t *cbt); +void circuit_build_times_update_last_circ(circuit_build_times_t *cbt); + #ifdef CIRCUITSTATS_PRIVATE -double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, +STATIC double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, double quantile); +STATIC int circuit_build_times_update_alpha(circuit_build_times_t *cbt); +STATIC void circuit_build_times_reset(circuit_build_times_t *cbt); + +/* Network liveness functions */ +STATIC int circuit_build_times_network_check_changed( + circuit_build_times_t *cbt); +#endif + +#ifdef TOR_UNIT_TESTS build_time_t circuit_build_times_generate_sample(circuit_build_times_t *cbt, double q_lo, double q_hi); +double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); void circuit_build_times_initial_alpha(circuit_build_times_t *cbt, double quantile, double time_ms); -int circuit_build_times_update_alpha(circuit_build_times_t *cbt); -double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); void circuitbuild_running_unit_tests(void); -void circuit_build_times_reset(circuit_build_times_t *cbt); - -/* Network liveness functions */ -int circuit_build_times_network_check_changed(circuit_build_times_t *cbt); #endif /* Network liveness functions */ void circuit_build_times_network_is_live(circuit_build_times_t *cbt); -int circuit_build_times_network_check_live(circuit_build_times_t *cbt); +int circuit_build_times_network_check_live(const circuit_build_times_t *cbt); void circuit_build_times_network_circ_success(circuit_build_times_t *cbt); -/* DOCDOC circuit_build_times_get_bw_scale */ -int circuit_build_times_get_bw_scale(networkstatus_t *ns); +#ifdef CIRCUITSTATS_PRIVATE +/** Structure for circuit build times history */ +struct circuit_build_times_s { + /** The circular array of recorded build times in milliseconds */ + build_time_t circuit_build_times[CBT_NCIRCUITS_TO_OBSERVE]; + /** Current index in the circuit_build_times circular array */ + int build_times_idx; + /** Total number of build times accumulated. Max CBT_NCIRCUITS_TO_OBSERVE */ + int total_build_times; + /** Information about the state of our local network connection */ + network_liveness_t liveness; + /** Last time we built a circuit. Used to decide to build new test circs */ + time_t last_circ_at; + /** "Minimum" value of our pareto distribution (actually mode) */ + build_time_t Xm; + /** alpha exponent for pareto dist. */ + double alpha; + /** Have we computed a timeout? */ + int have_computed_timeout; + /** The exact value for that timeout in milliseconds. Stored as a double + * to maintain precision from calculations to and from quantile value. */ + double timeout_ms; + /** How long we wait before actually closing the circuit. */ + double close_ms; +}; +#endif #endif diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 06a51a04a2..3b6f982666 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,6 +12,7 @@ #include "or.h" #include "addressmap.h" #include "channel.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" @@ -31,12 +32,6 @@ #include "router.h" #include "routerlist.h" -/********* START VARIABLES **********/ - -extern circuit_t *global_circuitlist; /* from circuitlist.c */ - -/********* END VARIABLES ************/ - static void circuit_expire_old_circuits_clientside(void); static void circuit_increment_failure_count(void); @@ -205,7 +200,7 @@ circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob, return 1; } else { if (a->timestamp_dirty || - timercmp(&a->timestamp_began, &b->timestamp_began, >)) + timercmp(&a->timestamp_began, &b->timestamp_began, OP_GT)) return 1; if (ob->build_state->is_internal) /* XXX023 what the heck is this internal thing doing here. I @@ -273,7 +268,6 @@ circuit_get_best(const entry_connection_t *conn, int must_be_open, uint8_t purpose, int need_uptime, int need_internal) { - circuit_t *circ; origin_circuit_t *best=NULL; struct timeval now; int intro_going_on_but_too_old = 0; @@ -286,7 +280,7 @@ circuit_get_best(const entry_connection_t *conn, tor_gettimeofday(&now); - for (circ=global_circuitlist;circ;circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { origin_circuit_t *origin_circ; if (!CIRCUIT_IS_ORIGIN(circ)) continue; @@ -301,7 +295,7 @@ circuit_get_best(const entry_connection_t *conn, } if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, - need_uptime,need_internal,now.tv_sec)) + need_uptime,need_internal, (time_t)now.tv_sec)) continue; /* now this is an acceptable circ to hand back. but that doesn't @@ -310,6 +304,7 @@ circuit_get_best(const entry_connection_t *conn, if (!best || circuit_is_better(origin_circ,best,conn)) best = origin_circ; } + SMARTLIST_FOREACH_END(circ); if (!best && intro_going_on_but_too_old) log_info(LD_REND|LD_CIRC, "There is an intro circuit being created " @@ -323,11 +318,9 @@ circuit_get_best(const entry_connection_t *conn, static int count_pending_general_client_circuits(void) { - const circuit_t *circ; - int count = 0; - for (circ = global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (circ->marked_for_close || circ->state == CIRCUIT_STATE_OPEN || circ->purpose != CIRCUIT_PURPOSE_C_GENERAL || @@ -336,6 +329,7 @@ count_pending_general_client_circuits(void) ++count; } + SMARTLIST_FOREACH_END(circ); return count; } @@ -375,7 +369,6 @@ circuit_conforms_to_options(const origin_circuit_t *circ, void circuit_expire_building(void) { - circuit_t *victim, *next_circ = global_circuitlist; /* circ_times.timeout_ms and circ_times.close_ms are from * circuit_build_times_get_initial_timeout() if we haven't computed * custom timeouts yet */ @@ -393,10 +386,9 @@ circuit_expire_building(void) * we want to be more lenient with timeouts, in case the * user has relocated and/or changed network connections. * See bug #3443. */ - while (next_circ) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, next_circ) { if (!CIRCUIT_IS_ORIGIN(next_circ) || /* didn't originate here */ next_circ->marked_for_close) { /* don't mess with marked circs */ - next_circ = next_circ->next; continue; } @@ -408,9 +400,7 @@ circuit_expire_building(void) any_opened_circs = 1; break; } - next_circ = next_circ->next; - } - next_circ = global_circuitlist; + } SMARTLIST_FOREACH_END(next_circ); #define SET_CUTOFF(target, msec) do { \ long ms = tor_lround(msec); \ @@ -451,12 +441,12 @@ circuit_expire_building(void) * RTTs = 4a + 3b + 2c * RTTs = 9h */ - SET_CUTOFF(general_cutoff, circ_times.timeout_ms); - SET_CUTOFF(begindir_cutoff, circ_times.timeout_ms); + SET_CUTOFF(general_cutoff, get_circuit_build_timeout_ms()); + SET_CUTOFF(begindir_cutoff, get_circuit_build_timeout_ms()); /* > 3hop circs seem to have a 1.0 second delay on their cannibalized * 4th hop. */ - SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (10/6.0) + 1000); + SET_CUTOFF(fourhop_cutoff, get_circuit_build_timeout_ms() * (10/6.0) + 1000); /* CIRCUIT_PURPOSE_C_ESTABLISH_REND behaves more like a RELAY cell. * Use the stream cutoff (more or less). */ @@ -465,26 +455,24 @@ circuit_expire_building(void) /* Be lenient with cannibalized circs. They already survived the official * CBT, and they're usually not performance-critical. */ SET_CUTOFF(cannibalized_cutoff, - MAX(circ_times.close_ms*(4/6.0), + MAX(get_circuit_build_close_time_ms()*(4/6.0), options->CircuitStreamTimeout * 1000) + 1000); /* Intro circs have an extra round trip (and are also 4 hops long) */ - SET_CUTOFF(c_intro_cutoff, circ_times.timeout_ms * (14/6.0) + 1000); + SET_CUTOFF(c_intro_cutoff, get_circuit_build_timeout_ms() * (14/6.0) + 1000); /* Server intro circs have an extra round trip */ - SET_CUTOFF(s_intro_cutoff, circ_times.timeout_ms * (9/6.0) + 1000); + SET_CUTOFF(s_intro_cutoff, get_circuit_build_timeout_ms() * (9/6.0) + 1000); - SET_CUTOFF(close_cutoff, circ_times.close_ms); - SET_CUTOFF(extremely_old_cutoff, circ_times.close_ms*2 + 1000); + SET_CUTOFF(close_cutoff, get_circuit_build_close_time_ms()); + SET_CUTOFF(extremely_old_cutoff, get_circuit_build_close_time_ms()*2 + 1000); SET_CUTOFF(hs_extremely_old_cutoff, - MAX(circ_times.close_ms*2 + 1000, + MAX(get_circuit_build_close_time_ms()*2 + 1000, options->SocksTimeout * 1000)); - while (next_circ) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *,victim) { struct timeval cutoff; - victim = next_circ; - next_circ = next_circ->next; if (!CIRCUIT_IS_ORIGIN(victim) || /* didn't originate here */ victim->marked_for_close) /* don't mess with marked circs */ continue; @@ -526,7 +514,7 @@ circuit_expire_building(void) if (TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out) cutoff = hs_extremely_old_cutoff; - if (timercmp(&victim->timestamp_began, &cutoff, >)) + if (timercmp(&victim->timestamp_began, &cutoff, OP_GT)) continue; /* it's still young, leave it alone */ /* We need to double-check the opened state here because @@ -536,7 +524,7 @@ circuit_expire_building(void) * aren't either. */ if (!any_opened_circs && victim->state != CIRCUIT_STATE_OPEN) { /* It's still young enough that we wouldn't close it, right? */ - if (timercmp(&victim->timestamp_began, &close_cutoff, >)) { + if (timercmp(&victim->timestamp_began, &close_cutoff, OP_GT)) { if (!TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout) { int first_hop_succeeded = TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_OPEN; @@ -546,7 +534,9 @@ circuit_expire_building(void) "%d guards are live.", TO_ORIGIN_CIRCUIT(victim)->global_identifier, circuit_purpose_to_string(victim->purpose), - TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len, + TO_ORIGIN_CIRCUIT(victim)->build_state ? + TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len : + -1, circuit_state_to_string(victim->state), channel_state_to_string(victim->n_chan->state), num_live_entry_guards(0)); @@ -555,12 +545,14 @@ circuit_expire_building(void) * was a timeout, and the timeout value needs to reset if we * see enough of them. Note this means we also need to avoid * double-counting below, too. */ - circuit_build_times_count_timeout(&circ_times, first_hop_succeeded); + circuit_build_times_count_timeout(get_circuit_build_times_mutable(), + first_hop_succeeded); TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout = 1; } continue; } else { static ratelim_t relax_timeout_limit = RATELIM_INIT(3600); + const double build_close_ms = get_circuit_build_close_time_ms(); log_fn_ratelim(&relax_timeout_limit, LOG_NOTICE, LD_CIRC, "No circuits are opened. Relaxed timeout for circuit %d " "(a %s %d-hop circuit in state %s with channel state %s) to " @@ -568,10 +560,13 @@ circuit_expire_building(void) "anyway. %d guards are live.", TO_ORIGIN_CIRCUIT(victim)->global_identifier, circuit_purpose_to_string(victim->purpose), - TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len, + TO_ORIGIN_CIRCUIT(victim)->build_state ? + TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len : + -1, circuit_state_to_string(victim->state), channel_state_to_string(victim->n_chan->state), - (long)circ_times.close_ms, num_live_entry_guards(0)); + (long)build_close_ms, + num_live_entry_guards(0)); } } @@ -651,7 +646,7 @@ circuit_expire_building(void) } if (circuit_timeout_want_to_count_circ(TO_ORIGIN_CIRCUIT(victim)) && - circuit_build_times_enough_to_compute(&circ_times)) { + circuit_build_times_enough_to_compute(get_circuit_build_times())) { /* Circuits are allowed to last longer for measurement. * Switch their purpose and wait. */ if (victim->purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { @@ -665,8 +660,9 @@ circuit_expire_building(void) * have a timeout. We also want to avoid double-counting * already "relaxed" circuits, which are counted above. */ if (!TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout) { - circuit_build_times_count_timeout(&circ_times, - first_hop_succeeded); + circuit_build_times_count_timeout( + get_circuit_build_times_mutable(), + first_hop_succeeded); } continue; } @@ -676,17 +672,18 @@ circuit_expire_building(void) * it off at, we probably had a suspend event along this codepath, * and we should discard the value. */ - if (timercmp(&victim->timestamp_began, &extremely_old_cutoff, <)) { + if (timercmp(&victim->timestamp_began, &extremely_old_cutoff, OP_LT)) { log_notice(LD_CIRC, "Extremely large value for circuit build timeout: %lds. " "Assuming clock jump. Purpose %d (%s)", (long)(now.tv_sec - victim->timestamp_began.tv_sec), victim->purpose, circuit_purpose_to_string(victim->purpose)); - } else if (circuit_build_times_count_close(&circ_times, - first_hop_succeeded, - victim->timestamp_created.tv_sec)) { - circuit_build_times_set_timeout(&circ_times); + } else if (circuit_build_times_count_close( + get_circuit_build_times_mutable(), + first_hop_succeeded, + (time_t)victim->timestamp_created.tv_sec)) { + circuit_build_times_set_timeout(get_circuit_build_times_mutable()); } } } @@ -711,7 +708,8 @@ circuit_expire_building(void) * and we have tried to send an INTRODUCE1 cell specifying it. * Thus, if the pending_final_cpath field *is* NULL, then we * want to not spare it. */ - if (TO_ORIGIN_CIRCUIT(victim)->build_state->pending_final_cpath == + if (TO_ORIGIN_CIRCUIT(victim)->build_state && + TO_ORIGIN_CIRCUIT(victim)->build_state->pending_final_cpath == NULL) break; /* fallthrough! */ @@ -750,23 +748,27 @@ circuit_expire_building(void) if (victim->n_chan) log_info(LD_CIRC, - "Abandoning circ %u %s:%d (state %d,%d:%s, purpose %d, " + "Abandoning circ %u %s:%u (state %d,%d:%s, purpose %d, " "len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, channel_get_canonical_remote_descr(victim->n_chan), (unsigned)victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, victim->state, circuit_state_to_string(victim->state), victim->purpose, - TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); + TO_ORIGIN_CIRCUIT(victim)->build_state ? + TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len : + -1); else log_info(LD_CIRC, - "Abandoning circ %u %d (state %d,%d:%s, purpose %d, len %d)", + "Abandoning circ %u %u (state %d,%d:%s, purpose %d, len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, (unsigned)victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, victim->state, circuit_state_to_string(victim->state), victim->purpose, - TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); + TO_ORIGIN_CIRCUIT(victim)->build_state ? + TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len : + -1); circuit_log_path(LOG_INFO,LD_CIRC,TO_ORIGIN_CIRCUIT(victim)); if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) @@ -775,7 +777,138 @@ circuit_expire_building(void) circuit_mark_for_close(victim, END_CIRC_REASON_TIMEOUT); pathbias_count_timeout(TO_ORIGIN_CIRCUIT(victim)); + } SMARTLIST_FOREACH_END(victim); +} + +/** For debugging #8387: track when we last called + * circuit_expire_old_circuits_clientside. */ +static time_t last_expired_clientside_circuits = 0; + +/** + * As a diagnostic for bug 8387, log information about how many one-hop + * circuits we have around that have been there for at least <b>age</b> + * seconds. Log a few of them. + */ +void +circuit_log_ancient_one_hop_circuits(int age) +{ +#define MAX_ANCIENT_ONEHOP_CIRCUITS_TO_LOG 10 + time_t now = time(NULL); + time_t cutoff = now - age; + int n_found = 0; + smartlist_t *log_these = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + const origin_circuit_t *ocirc; + if (! CIRCUIT_IS_ORIGIN(circ)) + continue; + if (circ->timestamp_created.tv_sec >= cutoff) + continue; + ocirc = CONST_TO_ORIGIN_CIRCUIT(circ); + + if (ocirc->build_state && ocirc->build_state->onehop_tunnel) { + ++n_found; + + if (smartlist_len(log_these) < MAX_ANCIENT_ONEHOP_CIRCUITS_TO_LOG) + smartlist_add(log_these, (origin_circuit_t*) ocirc); + } } + SMARTLIST_FOREACH_END(circ); + + if (n_found == 0) + goto done; + + log_notice(LD_HEARTBEAT, + "Diagnostic for issue 8387: Found %d one-hop circuits more " + "than %d seconds old! Logging %d...", + n_found, age, smartlist_len(log_these)); + + SMARTLIST_FOREACH_BEGIN(log_these, const origin_circuit_t *, ocirc) { + char created[ISO_TIME_LEN+1]; + int stream_num; + const edge_connection_t *conn; + char *dirty = NULL; + const circuit_t *circ = TO_CIRCUIT(ocirc); + + format_local_iso_time(created, + (time_t)circ->timestamp_created.tv_sec); + + if (circ->timestamp_dirty) { + char dirty_since[ISO_TIME_LEN+1]; + format_local_iso_time(dirty_since, circ->timestamp_dirty); + + tor_asprintf(&dirty, "Dirty since %s (%ld seconds vs %ld-second cutoff)", + dirty_since, (long)(now - circ->timestamp_dirty), + (long) get_options()->MaxCircuitDirtiness); + } else { + dirty = tor_strdup("Not marked dirty"); + } + + log_notice(LD_HEARTBEAT, " #%d created at %s. %s, %s. %s for close. " + "Package window: %d. " + "%s for new conns. %s.", + ocirc_sl_idx, + created, + circuit_state_to_string(circ->state), + circuit_purpose_to_string(circ->purpose), + circ->marked_for_close ? "Marked" : "Not marked", + circ->package_window, + ocirc->unusable_for_new_conns ? "Not usable" : "usable", + dirty); + tor_free(dirty); + + stream_num = 0; + for (conn = ocirc->p_streams; conn; conn = conn->next_stream) { + const connection_t *c = TO_CONN(conn); + char stream_created[ISO_TIME_LEN+1]; + if (++stream_num >= 5) + break; + + format_local_iso_time(stream_created, c->timestamp_created); + + log_notice(LD_HEARTBEAT, " Stream#%d created at %s. " + "%s conn in state %s. " + "It is %slinked and %sreading from a linked connection %p. " + "Package window %d. " + "%s for close (%s:%d). Hold-open is %sset. " + "Has %ssent RELAY_END. %s on circuit.", + stream_num, + stream_created, + conn_type_to_string(c->type), + conn_state_to_string(c->type, c->state), + c->linked ? "" : "not ", + c->reading_from_linked_conn ? "": "not", + c->linked_conn, + conn->package_window, + c->marked_for_close ? "Marked" : "Not marked", + c->marked_for_close_file ? c->marked_for_close_file : "--", + c->marked_for_close, + c->hold_open_until_flushed ? "" : "not ", + conn->edge_has_sent_end ? "" : "not ", + conn->edge_blocked_on_circ ? "Blocked" : "Not blocked"); + if (! c->linked_conn) + continue; + + c = c->linked_conn; + + log_notice(LD_HEARTBEAT, " Linked to %s connection in state %s " + "(Purpose %d). %s for close (%s:%d). Hold-open is %sset. ", + conn_type_to_string(c->type), + conn_state_to_string(c->type, c->state), + c->purpose, + c->marked_for_close ? "Marked" : "Not marked", + c->marked_for_close_file ? c->marked_for_close_file : "--", + c->marked_for_close, + c->hold_open_until_flushed ? "" : "not "); + } + } SMARTLIST_FOREACH_END(ocirc); + + log_notice(LD_HEARTBEAT, "It has been %ld seconds since I last called " + "circuit_expire_old_circuits_clientside().", + (long)(now - last_expired_clientside_circuits)); + + done: + smartlist_free(log_these); } /** Remove any elements in <b>needed_ports</b> that are handled by an @@ -810,7 +943,6 @@ int circuit_stream_is_being_handled(entry_connection_t *conn, uint16_t port, int min) { - circuit_t *circ; const node_t *exitnode; int num=0; time_t now = time(NULL); @@ -818,7 +950,7 @@ circuit_stream_is_being_handled(entry_connection_t *conn, get_options()->LongLivedPorts, conn ? conn->socks_request->port : port); - for (circ=global_circuitlist;circ;circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (CIRCUIT_IS_ORIGIN(circ) && !circ->marked_for_close && circ->purpose == CIRCUIT_PURPOSE_C_GENERAL && @@ -848,6 +980,7 @@ circuit_stream_is_being_handled(entry_connection_t *conn, } } } + SMARTLIST_FOREACH_END(circ); return 0; } @@ -861,7 +994,6 @@ circuit_stream_is_being_handled(entry_connection_t *conn, static void circuit_predict_and_launch_new(void) { - circuit_t *circ; int num=0, num_internal=0, num_uptime_internal=0; int hidserv_needs_uptime=0, hidserv_needs_capacity=1; int port_needs_uptime=0, port_needs_capacity=1; @@ -869,7 +1001,7 @@ circuit_predict_and_launch_new(void) int flags = 0; /* First, count how many of each type of circuit we have already. */ - for (circ=global_circuitlist;circ;circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { cpath_build_state_t *build_state; origin_circuit_t *origin_circ; if (!CIRCUIT_IS_ORIGIN(circ)) @@ -892,6 +1024,7 @@ circuit_predict_and_launch_new(void) if (build_state->need_uptime && build_state->is_internal) num_uptime_internal++; } + SMARTLIST_FOREACH_END(circ); /* If that's enough, then stop now. */ if (num >= MAX_UNUSED_OPEN_CIRCUITS) @@ -899,9 +1032,11 @@ circuit_predict_and_launch_new(void) /* Second, see if we need any more exit circuits. */ /* check if we know of a port that's been requested recently - * and no circuit is currently available that can handle it. */ + * and no circuit is currently available that can handle it. + * Exits (obviously) require an exit circuit. */ if (!circuit_all_predicted_ports_handled(now, &port_needs_uptime, - &port_needs_capacity)) { + &port_needs_capacity) + && router_have_consensus_path() == CONSENSUS_PATH_EXIT) { if (port_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; if (port_needs_capacity) @@ -913,8 +1048,10 @@ circuit_predict_and_launch_new(void) return; } - /* Third, see if we need any more hidden service (server) circuits. */ - if (num_rend_services() && num_uptime_internal < 3) { + /* Third, see if we need any more hidden service (server) circuits. + * HS servers only need an internal circuit. */ + if (num_rend_services() && num_uptime_internal < 3 + && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) { flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL); log_info(LD_CIRC, @@ -925,11 +1062,13 @@ circuit_predict_and_launch_new(void) return; } - /* Fourth, see if we need any more hidden service (client) circuits. */ + /* Fourth, see if we need any more hidden service (client) circuits. + * HS clients only need an internal circuit. */ if (rep_hist_get_predicted_internal(now, &hidserv_needs_uptime, &hidserv_needs_capacity) && ((num_uptime_internal<2 && hidserv_needs_uptime) || - num_internal<2)) { + num_internal<2) + && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) { if (hidserv_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; if (hidserv_needs_capacity) @@ -946,15 +1085,23 @@ circuit_predict_and_launch_new(void) /* Finally, check to see if we still need more circuits to learn * a good build timeout. But if we're close to our max number we * want, don't do another -- we want to leave a few slots open so - * we can still build circuits preemptively as needed. */ - if (num < MAX_UNUSED_OPEN_CIRCUITS-2 && - ! circuit_build_times_disabled() && - circuit_build_times_needs_circuits_now(&circ_times)) { - flags = CIRCLAUNCH_NEED_CAPACITY; - log_info(LD_CIRC, - "Have %d clean circs need another buildtime test circ.", num); - circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); - return; + * we can still build circuits preemptively as needed. + * XXXX make the assumption that build timeout streams should be + * created whenever we can build internal circuits. */ + if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) { + if (num < MAX_UNUSED_OPEN_CIRCUITS-2 && + ! circuit_build_times_disabled() && + circuit_build_times_needs_circuits_now(get_circuit_build_times())) { + flags = CIRCLAUNCH_NEED_CAPACITY; + /* if there are no exits in the consensus, make timeout + * circuits internal */ + if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL) + flags |= CIRCLAUNCH_IS_INTERNAL; + log_info(LD_CIRC, + "Have %d clean circs need another buildtime test circ.", num); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); + return; + } } } @@ -969,23 +1116,48 @@ circuit_predict_and_launch_new(void) void circuit_build_needed_circs(time_t now) { - static time_t time_to_new_circuit = 0; const or_options_t *options = get_options(); - /* launch a new circ for any pending streams that need one */ - connection_ap_attach_pending(); + /* launch a new circ for any pending streams that need one + * XXXX make the assumption that (some) AP streams (i.e. HS clients) + * don't require an exit circuit, review in #13814. + * This allows HSs to function in a consensus without exits. */ + if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) + connection_ap_attach_pending(); + + /* make sure any hidden services have enough intro points + * HS intro point streams only require an internal circuit */ + if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) + rend_services_introduce(); + + circuit_expire_old_circs_as_needed(now); + + if (!options->DisablePredictedCircuits) + circuit_predict_and_launch_new(); +} - /* make sure any hidden services have enough intro points */ - rend_services_introduce(); +/** + * Called once a second either directly or from + * circuit_build_needed_circs(). As appropriate (once per NewCircuitPeriod) + * resets failure counts and expires old circuits. + */ +void +circuit_expire_old_circs_as_needed(time_t now) +{ + static time_t time_to_expire_and_reset = 0; - if (time_to_new_circuit < now) { + if (time_to_expire_and_reset < now) { circuit_reset_failure_count(1); - time_to_new_circuit = now + options->NewCircuitPeriod; + time_to_expire_and_reset = now + get_options()->NewCircuitPeriod; if (proxy_mode(get_options())) addressmap_clean(now); circuit_expire_old_circuits_clientside(); #if 0 /* disable for now, until predict-and-launch-new can cull leftovers */ + + /* If we ever re-enable, this has to move into + * circuit_build_needed_circs */ + circ = circuit_get_youngest_clean_open(CIRCUIT_PURPOSE_C_GENERAL); if (get_options()->RunTesting && circ && @@ -995,8 +1167,6 @@ circuit_build_needed_circs(time_t now) } #endif } - if (!options->DisablePredictedCircuits) - circuit_predict_and_launch_new(); } /** If the stream <b>conn</b> is a member of any of the linked @@ -1078,14 +1248,14 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) static void circuit_expire_old_circuits_clientside(void) { - circuit_t *circ; struct timeval cutoff, now; tor_gettimeofday(&now); cutoff = now; + last_expired_clientside_circuits = now.tv_sec; if (! circuit_build_times_disabled() && - circuit_build_times_needs_circuits(&circ_times)) { + circuit_build_times_needs_circuits(get_circuit_build_times())) { /* Circuits should be shorter lived if we need more of them * for learning a good build timeout */ cutoff.tv_sec -= IDLE_TIMEOUT_WHILE_LEARNING; @@ -1093,7 +1263,7 @@ circuit_expire_old_circuits_clientside(void) cutoff.tv_sec -= get_options()->CircuitIdleTimeout; } - for (circ = global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (circ->marked_for_close || !CIRCUIT_IS_ORIGIN(circ)) continue; /* If the circuit has been dirty for too long, and there are no streams @@ -1113,7 +1283,7 @@ circuit_expire_old_circuits_clientside(void) if (circ->purpose != CIRCUIT_PURPOSE_PATH_BIAS_TESTING) circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } else if (!circ->timestamp_dirty && circ->state == CIRCUIT_STATE_OPEN) { - if (timercmp(&circ->timestamp_began, &cutoff, <)) { + if (timercmp(&circ->timestamp_began, &cutoff, OP_LT)) { if (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL || circ->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT || circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -1145,7 +1315,7 @@ circuit_expire_old_circuits_clientside(void) } } } - } + } SMARTLIST_FOREACH_END(circ); } /** How long do we wait before killing circuits with the properties @@ -1172,11 +1342,10 @@ circuit_expire_old_circuits_clientside(void) void circuit_expire_old_circuits_serverside(time_t now) { - circuit_t *circ; or_circuit_t *or_circ; time_t cutoff = now - IDLE_ONE_HOP_CIRC_TIMEOUT; - for (circ = global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (circ->marked_for_close || CIRCUIT_IS_ORIGIN(circ)) continue; or_circ = TO_OR_CIRCUIT(circ); @@ -1193,6 +1362,7 @@ circuit_expire_old_circuits_serverside(time_t now) circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } } + SMARTLIST_FOREACH_END(circ); } /** Number of testing circuits we want open before testing our bandwidth. */ @@ -1217,18 +1387,18 @@ reset_bandwidth_test(void) int circuit_enough_testing_circs(void) { - circuit_t *circ; int num = 0; if (have_performed_bandwidth_test) return 1; - for (circ = global_circuitlist; circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circ->marked_for_close && CIRCUIT_IS_ORIGIN(circ) && circ->purpose == CIRCUIT_PURPOSE_TESTING && circ->state == CIRCUIT_STATE_OPEN) num++; } + SMARTLIST_FOREACH_END(circ); return num >= NUM_PARALLEL_TESTING_CIRCS; } @@ -1490,6 +1660,16 @@ circuit_launch(uint8_t purpose, int flags) return circuit_launch_by_extend_info(purpose, NULL, flags); } +/** DOCDOC */ +static int +have_enough_path_info(int need_exit) +{ + if (need_exit) + return router_have_consensus_path() == CONSENSUS_PATH_EXIT; + else + return router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN; +} + /** Launch a new circuit with purpose <b>purpose</b> and exit node * <b>extend_info</b> (or NULL to select a random exit node). If flags * contains CIRCLAUNCH_NEED_UPTIME, choose among routers with high uptime. If @@ -1504,15 +1684,29 @@ circuit_launch_by_extend_info(uint8_t purpose, { origin_circuit_t *circ; int onehop_tunnel = (flags & CIRCLAUNCH_ONEHOP_TUNNEL) != 0; - - if (!onehop_tunnel && !router_have_minimum_dir_info()) { - log_debug(LD_CIRC,"Haven't fetched enough directory info yet; canceling " - "circuit launch."); + int have_path = have_enough_path_info(! (flags & CIRCLAUNCH_IS_INTERNAL) ); + int need_specific_rp = 0; + + if (!onehop_tunnel && (!router_have_minimum_dir_info() || !have_path)) { + log_debug(LD_CIRC,"Haven't %s yet; canceling " + "circuit launch.", + !router_have_minimum_dir_info() ? + "fetched enough directory info" : + "received a consensus with exits"); return NULL; } + /* If Tor2webRendezvousPoints is enabled and we are dealing with an + RP circuit, we want a specific RP node so we shouldn't canibalize + an already existing circuit. */ + if (get_options()->Tor2webRendezvousPoints && + purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND) { + need_specific_rp = 1; + } + if ((extend_info || purpose != CIRCUIT_PURPOSE_C_GENERAL) && - purpose != CIRCUIT_PURPOSE_TESTING && !onehop_tunnel) { + purpose != CIRCUIT_PURPOSE_TESTING && + !onehop_tunnel && !need_specific_rp) { /* see if there are appropriate circs available to cannibalize. */ /* XXX if we're planning to add a hop, perhaps we want to look for * internal circs rather than exit circs? -RD */ @@ -1664,7 +1858,9 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, return 1; /* we're happy */ } - if (!want_onehop && !router_have_minimum_dir_info()) { + int have_path = have_enough_path_info(!need_internal); + + if (!want_onehop && (!router_have_minimum_dir_info() || !have_path)) { if (!connection_get_by_type(CONN_TYPE_DIR)) { int severity = LOG_NOTICE; /* FFFF if this is a tunneled directory fetch, don't yell @@ -1672,14 +1868,20 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (entry_list_is_constrained(options) && entries_known_but_down(options)) { log_fn(severity, LD_APP|LD_DIR, - "Application request when we haven't used client functionality " - "lately. Optimistically trying known %s again.", + "Application request when we haven't %s. " + "Optimistically trying known %s again.", + !router_have_minimum_dir_info() ? + "used client functionality lately" : + "received a consensus with exits", options->UseBridges ? "bridges" : "entrynodes"); entries_retry_all(options); } else if (!options->UseBridges || any_bridge_descriptors_known()) { log_fn(severity, LD_APP|LD_DIR, - "Application request when we haven't used client functionality " - "lately. Optimistically trying directory fetches again."); + "Application request when we haven't %s. " + "Optimistically trying directory fetches again.", + !router_have_minimum_dir_info() ? + "used client functionality lately" : + "received a consensus with exits"); routerlist_retry_directory_downloads(time(NULL)); } } @@ -1834,11 +2036,13 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, else new_circ_purpose = desired_circuit_purpose; +#ifdef ENABLE_TOR2WEB_MODE if (options->Tor2webMode && (new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND || new_circ_purpose == CIRCUIT_PURPOSE_C_INTRODUCING)) { want_onehop = 1; } +#endif { int flags = CIRCLAUNCH_NEED_CAPACITY; @@ -1870,7 +2074,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data); if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && circ->base_.state == CIRCUIT_STATE_OPEN) - rend_client_rendcirc_has_opened(circ); + circuit_has_opened(circ); } } } /* endif (!circ) */ @@ -1928,7 +2132,7 @@ static void link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, crypt_path_t *cpath) { - const node_t *exitnode; + const node_t *exitnode = NULL; /* add it into the linked list of streams on this circuit */ log_debug(LD_APP|LD_CIRC, "attaching new conn to circ. n_circ_id %u.", @@ -1962,23 +2166,25 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, circ->isolation_any_streams_attached = 1; connection_edge_update_circuit_isolation(apconn, circ, 0); + /* Compute the exitnode if possible, for logging below */ + if (cpath->extend_info) + exitnode = node_get_by_id(cpath->extend_info->identity_digest); + /* See if we can use optimistic data on this circuit */ - if (cpath->extend_info && - (exitnode = node_get_by_id(cpath->extend_info->identity_digest)) && - exitnode->rs) { - /* Okay; we know what exit node this is. */ - if (optimistic_data_enabled() && - circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL && - exitnode->rs->version_supports_optimistic_data) - apconn->may_use_optimistic_data = 1; - else - apconn->may_use_optimistic_data = 0; - log_info(LD_APP, "Looks like completed circuit to %s %s allow " - "optimistic data for connection to %s", - safe_str_client(node_describe(exitnode)), - apconn->may_use_optimistic_data ? "does" : "doesn't", - safe_str_client(apconn->socks_request->address)); - } + if (optimistic_data_enabled() && + (circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL || + circ->base_.purpose == CIRCUIT_PURPOSE_C_REND_JOINED)) + apconn->may_use_optimistic_data = 1; + else + apconn->may_use_optimistic_data = 0; + log_info(LD_APP, "Looks like completed circuit to %s %s allow " + "optimistic data for connection to %s", + circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL ? + /* node_describe() does the right thing if exitnode is NULL */ + safe_str_client(node_describe(exitnode)) : + "hidden service", + apconn->may_use_optimistic_data ? "does" : "doesn't", + safe_str_client(apconn->socks_request->address)); } /** Return true iff <b>address</b> is matched by one of the entries in @@ -2180,7 +2386,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) tor_assert(rendcirc); /* one is already established, attach */ log_info(LD_REND, - "rend joined circ %d already here. attaching. " + "rend joined circ %u already here. attaching. " "(stream %d sec old)", (unsigned)rendcirc->base_.n_circ_id, conn_age); /* Mark rendezvous circuits as 'newly dirty' every time you use @@ -2200,6 +2406,18 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) return 1; } + /* At this point we need to re-check the state, since it's possible that + * our call to circuit_get_open_circ_or_launch() changed the connection's + * state from "CIRCUIT_WAIT" to "RENDDESC_WAIT" because we decided to + * re-fetch the descriptor. + */ + if (ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_CIRCUIT_WAIT) { + log_info(LD_REND, "This connection is no longer ready to attach; its " + "state changed." + "(We probably have to re-fetch its descriptor.)"); + return 0; + } + if (rendcirc && (rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)) { log_info(LD_REND, diff --git a/src/or/circuituse.h b/src/or/circuituse.h index 11e5a64163..a59f478ac8 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,11 +16,13 @@ void circuit_expire_building(void); void circuit_remove_handled_ports(smartlist_t *needed_ports); int circuit_stream_is_being_handled(entry_connection_t *conn, uint16_t port, int min); +void circuit_log_ancient_one_hop_circuits(int age); #if 0 int circuit_conforms_to_options(const origin_circuit_t *circ, const or_options_t *options); #endif void circuit_build_needed_circs(time_t now); +void circuit_expire_old_circs_as_needed(time_t now); void circuit_detach_stream(circuit_t *circ, edge_connection_t *conn); void circuit_expire_old_circuits_serverside(time_t now); diff --git a/src/or/command.c b/src/or/command.c index 78fd4fad33..719b10736b 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -53,6 +53,33 @@ static void command_process_created_cell(cell_t *cell, channel_t *chan); static void command_process_relay_cell(cell_t *cell, channel_t *chan); static void command_process_destroy_cell(cell_t *cell, channel_t *chan); +/** Convert the cell <b>command</b> into a lower-case, human-readable + * string. */ +const char * +cell_command_to_string(uint8_t command) +{ + switch (command) { + case CELL_PADDING: return "padding"; + case CELL_CREATE: return "create"; + case CELL_CREATED: return "created"; + case CELL_RELAY: return "relay"; + case CELL_DESTROY: return "destroy"; + case CELL_CREATE_FAST: return "create_fast"; + case CELL_CREATED_FAST: return "created_fast"; + case CELL_VERSIONS: return "versions"; + case CELL_NETINFO: return "netinfo"; + case CELL_RELAY_EARLY: return "relay_early"; + case CELL_CREATE2: return "create2"; + case CELL_CREATED2: return "created2"; + case CELL_VPADDING: return "vpadding"; + case CELL_CERTS: return "certs"; + case CELL_AUTH_CHALLENGE: return "auth_challenge"; + case CELL_AUTHENTICATE: return "authenticate"; + case CELL_AUTHORIZE: return "authorize"; + default: return "unrecognized"; + } +} + #ifdef KEEP_TIMING_STATS /** This is a wrapper function around the actual function that processes the * <b>cell</b> that just arrived on <b>conn</b>. Increment <b>*time</b> @@ -200,6 +227,34 @@ command_process_create_cell(cell_t *cell, channel_t *chan) (unsigned)cell->circ_id, U64_PRINTF_ARG(chan->global_identifier), chan); + /* We check for the conditions that would make us drop the cell before + * we check for the conditions that would make us send a DESTROY back, + * since those conditions would make a DESTROY nonsensical. */ + if (cell->circ_id == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received a create cell (type %d) from %s with zero circID; " + " ignoring.", (int)cell->command, + channel_get_actual_remote_descr(chan)); + return; + } + + if (circuit_id_in_use_on_channel(cell->circ_id, chan)) { + const node_t *node = node_get_by_id(chan->identity_digest); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received CREATE cell (circID %u) for known circ. " + "Dropping (age %d).", + (unsigned)cell->circ_id, + (int)(time(NULL) - channel_when_created(chan))); + if (node) { + char *p = esc_for_log(node_get_platform(node)); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Details: router %s, platform %s.", + node_describe(node), p); + tor_free(p); + } + return; + } + if (we_are_hibernating()) { log_info(LD_OR, "Received create cell but we're shutting down. Sending back " @@ -221,14 +276,6 @@ command_process_create_cell(cell_t *cell, channel_t *chan) return; } - if (cell->circ_id == 0) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received a create cell (type %d) from %s with zero circID; " - " ignoring.", (int)cell->command, - channel_get_actual_remote_descr(chan)); - return; - } - /* If the high bit of the circuit ID is not as expected, close the * circ. */ if (chan->wide_circ_ids) @@ -247,23 +294,6 @@ command_process_create_cell(cell_t *cell, channel_t *chan) return; } - if (circuit_id_in_use_on_channel(cell->circ_id, chan)) { - const node_t *node = node_get_by_id(chan->identity_digest); - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received CREATE cell (circID %u) for known circ. " - "Dropping (age %d).", - (unsigned)cell->circ_id, - (int)(time(NULL) - channel_when_created(chan))); - if (node) { - char *p = esc_for_log(node_get_platform(node)); - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Details: router %s, platform %s.", - node_describe(node), p); - tor_free(p); - } - return; - } - circ = or_circuit_new(cell->circ_id, chan); circ->base_.purpose = CIRCUIT_PURPOSE_OR; circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_ONIONSKIN_PENDING); @@ -280,7 +310,7 @@ command_process_create_cell(cell_t *cell, channel_t *chan) /* hand it off to the cpuworkers, and then return. */ if (connection_or_digest_is_known_relay(chan->identity_digest)) rep_hist_note_circuit_handshake_requested(create_cell->handshake_type); - if (assign_onionskin_to_cpuworker(NULL, circ, create_cell) < 0) { + if (assign_onionskin_to_cpuworker(circ, create_cell) < 0) { log_debug(LD_GENERAL,"Failed to hand off onionskin. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_RESOURCELIMIT); return; @@ -349,7 +379,7 @@ command_process_created_cell(cell_t *cell, channel_t *chan) return; } - if (circ->n_circ_id != cell->circ_id) { + if (circ->n_circ_id != cell->circ_id || circ->n_chan != chan) { log_fn(LOG_PROTOCOL_WARN,LD_PROTOCOL, "got created cell from Tor client? Closing."); circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); @@ -368,7 +398,6 @@ command_process_created_cell(cell_t *cell, channel_t *chan) log_debug(LD_OR,"at OP. Finishing handshake."); if ((err_reason = circuit_finish_handshake(origin_circ, &extended_cell.created_cell)) < 0) { - log_warn(LD_OR,"circuit_finish_handshake failed."); circuit_mark_for_close(circ, -err_reason); return; } @@ -408,6 +437,7 @@ command_process_created_cell(cell_t *cell, channel_t *chan) static void command_process_relay_cell(cell_t *cell, channel_t *chan) { + const or_options_t *options = get_options(); circuit_t *circ; int reason, direction; @@ -434,6 +464,7 @@ command_process_relay_cell(cell_t *cell, channel_t *chan) } if (!CIRCUIT_IS_ORIGIN(circ) && + chan == TO_OR_CIRCUIT(circ)->p_chan && cell->circ_id == TO_OR_CIRCUIT(circ)->p_circ_id) direction = CELL_DIRECTION_OUT; else @@ -480,6 +511,14 @@ command_process_relay_cell(cell_t *cell, channel_t *chan) direction==CELL_DIRECTION_OUT?"forward":"backward"); circuit_mark_for_close(circ, -reason); } + + /* If this is a cell in an RP circuit, count it as part of the + hidden service stats */ + if (options->HiddenServiceStatistics && + !CIRCUIT_IS_ORIGIN(circ) && + TO_OR_CIRCUIT(circ)->circuit_carries_hs_traffic_stats) { + rep_hist_seen_new_rp_cell(); + } } /** Process a 'destroy' <b>cell</b> that just arrived from @@ -514,6 +553,7 @@ command_process_destroy_cell(cell_t *cell, channel_t *chan) circ->received_destroy = 1; if (!CIRCUIT_IS_ORIGIN(circ) && + chan == TO_OR_CIRCUIT(circ)->p_chan && cell->circ_id == TO_OR_CIRCUIT(circ)->p_circ_id) { /* the destroy came from behind */ circuit_set_p_circid_chan(TO_OR_CIRCUIT(circ), 0, NULL); diff --git a/src/or/command.h b/src/or/command.h index 913f46a5cd..bea96261bb 100644 --- a/src/or/command.h +++ b/src/or/command.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,6 +19,8 @@ void command_process_var_cell(channel_t *chan, var_cell_t *cell); void command_setup_channel(channel_t *chan); void command_setup_listener(channel_listener_t *chan_l); +const char *cell_command_to_string(uint8_t command); + extern uint64_t stats_n_padding_cells_processed; extern uint64_t stats_n_create_cells_processed; extern uint64_t stats_n_created_cells_processed; diff --git a/src/or/config.c b/src/or/config.c index 1610cadf33..7444af6fd0 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1,7 +1,7 @@ - /* Copyright (c) 2001 Matej Pfajfar. +/* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,8 +10,8 @@ **/ #define CONFIG_PRIVATE - #include "or.h" +#include "compat.h" #include "addressmap.h" #include "channel.h" #include "circuitbuild.h" @@ -40,20 +40,37 @@ #include "rendservice.h" #include "rephist.h" #include "router.h" +#include "sandbox.h" #include "util.h" #include "routerlist.h" #include "routerset.h" +#include "scheduler.h" #include "statefile.h" #include "transports.h" +#include "ext_orport.h" +#include "torgzip.h" #ifdef _WIN32 #include <shlobj.h> #endif #include "procmon.h" +#ifdef HAVE_SYSTEMD +# if defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) +/* Systemd's use of gcc's __INCLUDE_LEVEL__ extension macro appears to confuse + * Coverity. Here's a kludge to unconfuse it. + */ +# define __INCLUDE_LEVEL__ 2 +# endif +#include <systemd/sd-daemon.h> +#endif + /* From main.c */ extern int quiet_level; +/* Prefix used to indicate a Unix socket in a FooPort configuration. */ +static const char unix_socket_prefix[] = "unix:"; + /** A list of abbreviations and aliases to map command-line options, obsolete * option names, or alternative option names, to their current values. */ static config_abbrev_t option_abbrevs_[] = { @@ -61,15 +78,16 @@ static config_abbrev_t option_abbrevs_[] = { PLURAL(AuthDirBadExitCC), PLURAL(AuthDirInvalidCC), PLURAL(AuthDirRejectCC), - PLURAL(ExitNode), PLURAL(EntryNode), PLURAL(ExcludeNode), + PLURAL(Tor2webRendezvousPoint), PLURAL(FirewallPort), PLURAL(LongLivedPort), PLURAL(HiddenServiceNode), PLURAL(HiddenServiceExcludeNode), PLURAL(NumCPU), PLURAL(RendNode), + PLURAL(RecommendedPackage), PLURAL(RendExcludeNode), PLURAL(StrictEntryNode), PLURAL(StrictExitNode), @@ -83,6 +101,7 @@ static config_abbrev_t option_abbrevs_[] = { { "DirFetchPostPeriod", "StatusFetchPeriod", 0, 0}, { "DirServer", "DirAuthority", 0, 0}, /* XXXX024 later, make this warn? */ { "MaxConn", "ConnLimit", 0, 1}, + { "MaxMemInCellQueues", "MaxMemInQueues", 0, 0}, { "ORBindAddress", "ORListenAddress", 0, 0}, { "DirBindAddress", "DirListenAddress", 0, 0}, { "SocksBindAddress", "SocksListenAddress", 0, 0}, @@ -96,8 +115,6 @@ static config_abbrev_t option_abbrevs_[] = { { "PreferTunnelledDirConns", "PreferTunneledDirConns", 0, 0}, { "BridgeAuthoritativeDirectory", "BridgeAuthoritativeDir", 0, 0}, { "HashedControlPassword", "__HashedControlSessionPassword", 1, 0}, - { "StrictEntryNodes", "StrictNodes", 0, 1}, - { "StrictExitNodes", "StrictNodes", 0, 1}, { "VirtualAddrNetwork", "VirtualAddrNetworkIPv4", 0, 0}, { "_UseFilteringSSLBufferevents", "UseFilteringSSLBufferevents", 0, 1}, { NULL, NULL, 0, 0}, @@ -124,8 +141,8 @@ static config_abbrev_t option_abbrevs_[] = { * be chosen first. */ static config_var_t option_vars_[] = { - OBSOLETE("AccountingMaxKB"), V(AccountingMax, MEMUNIT, "0 bytes"), + VAR("AccountingRule", STRING, AccountingRule_option, "max"), V(AccountingStart, STRING, NULL), V(Address, STRING, NULL), V(AllowDotExit, BOOL, "0"), @@ -135,20 +152,20 @@ static config_var_t option_vars_[] = { V(AllowSingleHopExits, BOOL, "0"), V(AlternateBridgeAuthority, LINELIST, NULL), V(AlternateDirAuthority, LINELIST, NULL), - V(AlternateHSAuthority, LINELIST, NULL), + OBSOLETE("AlternateHSAuthority"), V(AssumeReachable, BOOL, "0"), - V(AuthDirBadDir, LINELIST, NULL), - V(AuthDirBadDirCCs, CSV, ""), + OBSOLETE("AuthDirBadDir"), + OBSOLETE("AuthDirBadDirCCs"), V(AuthDirBadExit, LINELIST, NULL), V(AuthDirBadExitCCs, CSV, ""), V(AuthDirInvalid, LINELIST, NULL), V(AuthDirInvalidCCs, CSV, ""), V(AuthDirFastGuarantee, MEMUNIT, "100 KB"), - V(AuthDirGuardBWGuarantee, MEMUNIT, "250 KB"), + V(AuthDirGuardBWGuarantee, MEMUNIT, "2 MB"), V(AuthDirReject, LINELIST, NULL), V(AuthDirRejectCCs, CSV, ""), - V(AuthDirRejectUnlisted, BOOL, "0"), - V(AuthDirListBadDirs, BOOL, "0"), + OBSOLETE("AuthDirRejectUnlisted"), + OBSOLETE("AuthDirListBadDirs"), V(AuthDirListBadExits, BOOL, "0"), V(AuthDirMaxServersPerAddr, UINT, "2"), V(AuthDirMaxServersPerAuthAddr,UINT, "5"), @@ -188,36 +205,33 @@ static config_var_t option_vars_[] = { V(ControlPortWriteToFile, FILENAME, NULL), V(ControlSocket, LINELIST, NULL), V(ControlSocketsGroupWritable, BOOL, "0"), + V(SocksSocketsGroupWritable, BOOL, "0"), V(CookieAuthentication, BOOL, "0"), V(CookieAuthFileGroupReadable, BOOL, "0"), V(CookieAuthFile, STRING, NULL), V(CountPrivateBandwidth, BOOL, "0"), V(DataDirectory, FILENAME, NULL), - OBSOLETE("DebugLogFile"), V(DisableNetwork, BOOL, "0"), V(DirAllowPrivateAddresses, BOOL, "0"), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "30 minutes"), V(DirListenAddress, LINELIST, NULL), - OBSOLETE("DirFetchPeriod"), V(DirPolicy, LINELIST, NULL), VPORT(DirPort, LINELIST, NULL), V(DirPortFrontPage, FILENAME, NULL), - OBSOLETE("DirPostPeriod"), - OBSOLETE("DirRecordUsageByCountry"), - OBSOLETE("DirRecordUsageGranularity"), - OBSOLETE("DirRecordUsageRetainIPs"), - OBSOLETE("DirRecordUsageSaveInterval"), - V(DirReqStatistics, BOOL, "1"), + VAR("DirReqStatistics", BOOL, DirReqStatistics_option, "1"), VAR("DirAuthority", LINELIST, DirAuthorities, NULL), V(DirAuthorityFallbackRate, DOUBLE, "1.0"), V(DisableAllSwap, BOOL, "0"), V(DisableDebuggerAttachment, BOOL, "1"), V(DisableIOCP, BOOL, "1"), - V(DisableV2DirectoryInfo_, BOOL, "0"), + OBSOLETE("DisableV2DirectoryInfo_"), V(DynamicDHGroups, BOOL, "0"), VPORT(DNSPort, LINELIST, NULL), V(DNSListenAddress, LINELIST, NULL), V(DownloadExtraInfo, BOOL, "0"), + V(TestingEnableConnBwEvent, BOOL, "0"), + V(TestingEnableCellStatsEvent, BOOL, "0"), + V(TestingEnableTbEmptyEvent, BOOL, "0"), V(EnforceDistinctSubnets, BOOL, "1"), V(EntryNodes, ROUTERSET, NULL), V(EntryStatistics, BOOL, "0"), @@ -230,6 +244,10 @@ static config_var_t option_vars_[] = { V(ExitPolicyRejectPrivate, BOOL, "1"), V(ExitPortStatistics, BOOL, "0"), V(ExtendAllowPrivateAddresses, BOOL, "0"), + V(ExitRelay, AUTOBOOL, "auto"), + VPORT(ExtORPort, LINELIST, NULL), + V(ExtORPortCookieAuthFile, STRING, NULL), + V(ExtORPortCookieAuthFileGroupReadable, BOOL, "0"), V(ExtraInfoStatistics, BOOL, "1"), V(FallbackDir, LINELIST, NULL), @@ -242,7 +260,7 @@ static config_var_t option_vars_[] = { V(FetchServerDescriptors, BOOL, "1"), V(FetchHidServDescriptors, BOOL, "1"), V(FetchUselessDescriptors, BOOL, "0"), - V(FetchV2Networkstatus, BOOL, "0"), + OBSOLETE("FetchV2Networkstatus"), V(GeoIPExcludeUnknown, AUTOBOOL, "auto"), #ifdef _WIN32 V(GeoIPFile, FILENAME, "<default>"), @@ -253,7 +271,6 @@ static config_var_t option_vars_[] = { V(GeoIPv6File, FILENAME, SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip6"), #endif - OBSOLETE("GiveGuardFlagTo_CVE_2011_2768_VulnerableRelays"), OBSOLETE("Group"), V(GuardLifetime, INTERVAL, "0 minutes"), V(HardwareAccel, BOOL, "0"), @@ -263,15 +280,14 @@ static config_var_t option_vars_[] = { V(HashedControlPassword, LINELIST, NULL), V(HidServDirectoryV2, BOOL, "1"), VAR("HiddenServiceDir", LINELIST_S, RendConfigLines, NULL), - OBSOLETE("HiddenServiceExcludeNodes"), - OBSOLETE("HiddenServiceNodes"), + VAR("HiddenServiceDirGroupReadable", LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceOptions",LINELIST_V, RendConfigLines, NULL), VAR("HiddenServicePort", LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceVersion",LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceAuthorizeClient",LINELIST_S,RendConfigLines, NULL), + VAR("HiddenServiceAllowUnknownPorts",LINELIST_S, RendConfigLines, NULL), + V(HiddenServiceStatistics, BOOL, "0"), V(HidServAuth, LINELIST, NULL), - V(HSAuthoritativeDir, BOOL, "0"), - OBSOLETE("HSAuthorityRecordStats"), V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"), V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"), V(HTTPProxy, STRING, NULL), @@ -281,37 +297,34 @@ static config_var_t option_vars_[] = { V(IPv6Exit, BOOL, "0"), VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), V(ServerTransportListenAddr, LINELIST, NULL), + V(ServerTransportOptions, LINELIST, NULL), V(Socks4Proxy, STRING, NULL), V(Socks5Proxy, STRING, NULL), V(Socks5ProxyUsername, STRING, NULL), V(Socks5ProxyPassword, STRING, NULL), - OBSOLETE("IgnoreVersion"), V(KeepalivePeriod, INTERVAL, "5 minutes"), VAR("Log", LINELIST, Logs, NULL), V(LogMessageDomains, BOOL, "0"), - OBSOLETE("LinkPadding"), - OBSOLETE("LogLevel"), - OBSOLETE("LogFile"), V(LogTimeGranularity, MSEC_INTERVAL, "1 second"), + V(TruncateLogFile, BOOL, "0"), V(LongLivedPorts, CSV, "21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300"), VAR("MapAddress", LINELIST, AddressMap, NULL), V(MaxAdvertisedBandwidth, MEMUNIT, "1 GB"), V(MaxCircuitDirtiness, INTERVAL, "10 minutes"), V(MaxClientCircuitsPending, UINT, "32"), - V(MaxMemInCellQueues, MEMUNIT, "8 GB"), + VAR("MaxMemInQueues", MEMUNIT, MaxMemInQueues_raw, "0"), OBSOLETE("MaxOnionsPending"), V(MaxOnionQueueDelay, MSEC_INTERVAL, "1750 msec"), V(MinMeasuredBWsForAuthToIgnoreAdvertised, INT, "500"), - OBSOLETE("MonthlyAccountingStart"), V(MyFamily, STRING, NULL), V(NewCircuitPeriod, INTERVAL, "30 seconds"), - VAR("NamingAuthoritativeDirectory",BOOL, NamingAuthoritativeDir, "0"), + OBSOLETE("NamingAuthoritativeDirectory"), V(NATDListenAddress, LINELIST, NULL), VPORT(NATDPort, LINELIST, NULL), V(Nickname, STRING, NULL), + V(PredictedPortsRelevanceTime, INTERVAL, "1 hour"), V(WarnUnsafeSocks, BOOL, "1"), - OBSOLETE("NoPublish"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), V(NumCPUs, UINT, "0"), V(NumDirectoryGuards, UINT, "0"), @@ -337,7 +350,6 @@ static config_var_t option_vars_[] = { V(PathBiasScaleUseThreshold, INT, "-1"), V(PathsNeededToBuildCircuits, DOUBLE, "-1"), - OBSOLETE("PathlenCoinWeight"), V(PerConnBWBurst, MEMUNIT, "0"), V(PerConnBWRate, MEMUNIT, "0"), V(PidFile, STRING, NULL), @@ -347,7 +359,7 @@ static config_var_t option_vars_[] = { V(OptimisticData, AUTOBOOL, "auto"), V(PortForwarding, BOOL, "0"), V(PortForwardingHelper, FILENAME, "tor-fw-helper"), - V(PreferTunneledDirConns, BOOL, "1"), + OBSOLETE("PreferTunneledDirConns"), V(ProtocolWarnings, BOOL, "0"), V(PublishServerDescriptor, CSV, "1"), V(PublishHidServDescriptors, BOOL, "1"), @@ -357,19 +369,16 @@ static config_var_t option_vars_[] = { V(RecommendedVersions, LINELIST, NULL), V(RecommendedClientVersions, LINELIST, NULL), V(RecommendedServerVersions, LINELIST, NULL), - OBSOLETE("RedirectExit"), + V(RecommendedPackages, LINELIST, NULL), V(RefuseUnknownExits, AUTOBOOL, "auto"), V(RejectPlaintextPorts, CSV, ""), V(RelayBandwidthBurst, MEMUNIT, "0"), V(RelayBandwidthRate, MEMUNIT, "0"), - OBSOLETE("RendExcludeNodes"), - OBSOLETE("RendNodes"), V(RendPostPeriod, INTERVAL, "1 hour"), V(RephistTrackTime, INTERVAL, "24 hours"), - OBSOLETE("RouterFile"), V(RunAsDaemon, BOOL, "0"), -// V(RunTesting, BOOL, "0"), OBSOLETE("RunTesting"), // currently unused + V(Sandbox, BOOL, "0"), V(SafeLogging, STRING, "1"), V(SafeSocks, BOOL, "0"), V(ServerDNSAllowBrokenConfig, BOOL, "1"), @@ -380,47 +389,53 @@ static config_var_t option_vars_[] = { V(ServerDNSSearchDomains, BOOL, "0"), V(ServerDNSTestAddresses, CSV, "www.google.com,www.mit.edu,www.yahoo.com,www.slashdot.org"), + V(SchedulerLowWaterMark__, MEMUNIT, "100 MB"), + V(SchedulerHighWaterMark__, MEMUNIT, "101 MB"), + V(SchedulerMaxFlushCells__, UINT, "1000"), V(ShutdownWaitLength, INTERVAL, "30 seconds"), V(SocksListenAddress, LINELIST, NULL), V(SocksPolicy, LINELIST, NULL), VPORT(SocksPort, LINELIST, NULL), V(SocksTimeout, INTERVAL, "2 minutes"), V(SSLKeyLifetime, INTERVAL, "0"), - OBSOLETE("StatusFetchPeriod"), + OBSOLETE("StrictEntryNodes"), + OBSOLETE("StrictExitNodes"), V(StrictNodes, BOOL, "0"), - V(Support022HiddenServices, AUTOBOOL, "auto"), - OBSOLETE("SysLog"), + OBSOLETE("Support022HiddenServices"), V(TestSocks, BOOL, "0"), - OBSOLETE("TestVia"), V(TokenBucketRefillInterval, MSEC_INTERVAL, "100 msec"), V(Tor2webMode, BOOL, "0"), + V(Tor2webRendezvousPoints, ROUTERSET, NULL), V(TLSECGroup, STRING, NULL), V(TrackHostExits, CSV, NULL), V(TrackHostExitsExpire, INTERVAL, "30 minutes"), - OBSOLETE("TrafficShaping"), V(TransListenAddress, LINELIST, NULL), VPORT(TransPort, LINELIST, NULL), - V(TunnelDirConns, BOOL, "1"), + V(TransProxyType, STRING, "default"), + OBSOLETE("TunnelDirConns"), V(UpdateBridgesFromAuthority, BOOL, "0"), V(UseBridges, BOOL, "0"), V(UseEntryGuards, BOOL, "1"), V(UseEntryGuardsAsDirGuards, BOOL, "1"), + V(UseGuardFraction, AUTOBOOL, "auto"), V(UseMicrodescriptors, AUTOBOOL, "auto"), - V(UseNTorHandshake, AUTOBOOL, "auto"), + V(UseNTorHandshake, AUTOBOOL, "1"), V(User, STRING, NULL), V(UserspaceIOCPBuffers, BOOL, "0"), - VAR("V1AuthoritativeDirectory",BOOL, V1AuthoritativeDir, "0"), - VAR("V2AuthoritativeDirectory",BOOL, V2AuthoritativeDir, "0"), + OBSOLETE("V1AuthoritativeDirectory"), + OBSOLETE("V2AuthoritativeDirectory"), VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir, "0"), V(TestingV3AuthInitialVotingInterval, INTERVAL, "30 minutes"), V(TestingV3AuthInitialVoteDelay, INTERVAL, "5 minutes"), V(TestingV3AuthInitialDistDelay, INTERVAL, "5 minutes"), + V(TestingV3AuthVotingStartOffset, INTERVAL, "0"), V(V3AuthVotingInterval, INTERVAL, "1 hour"), V(V3AuthVoteDelay, INTERVAL, "5 minutes"), V(V3AuthDistDelay, INTERVAL, "5 minutes"), V(V3AuthNIntervalsValid, UINT, "3"), V(V3AuthUseLegacyKey, BOOL, "0"), V(V3BandwidthsFile, FILENAME, NULL), + V(GuardfractionFile, FILENAME, NULL), VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"), V(VirtualAddrNetworkIPv4, STRING, "127.192.0.0/10"), V(VirtualAddrNetworkIPv6, STRING, "[FE80::]/10"), @@ -433,8 +448,28 @@ static config_var_t option_vars_[] = { VAR("__HashedControlSessionPassword", LINELIST, HashedControlSessionPassword, NULL), VAR("__OwningControllerProcess",STRING,OwningControllerProcess, NULL), - V(MinUptimeHidServDirectoryV2, INTERVAL, "25 hours"), + V(MinUptimeHidServDirectoryV2, INTERVAL, "96 hours"), V(VoteOnHidServDirectoriesV2, BOOL, "1"), + V(TestingServerDownloadSchedule, CSV_INTERVAL, "0, 0, 0, 60, 60, 120, " + "300, 900, 2147483647"), + V(TestingClientDownloadSchedule, CSV_INTERVAL, "0, 0, 60, 300, 600, " + "2147483647"), + V(TestingServerConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 60, " + "300, 600, 1800, 1800, 1800, 1800, " + "1800, 3600, 7200"), + V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 60, " + "300, 600, 1800, 3600, 3600, 3600, " + "10800, 21600, 43200"), + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "3600, 900, 900, 3600"), + V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"), + V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"), + V(TestingConsensusMaxDownloadTries, UINT, "8"), + V(TestingDescriptorMaxDownloadTries, UINT, "8"), + V(TestingMicrodescMaxDownloadTries, UINT, "8"), + V(TestingCertMaxDownloadTries, UINT, "8"), + V(TestingDirAuthVoteExit, ROUTERSET, NULL), + V(TestingDirAuthVoteGuard, ROUTERSET, NULL), + V(TestingDirAuthVoteHSDir, ROUTERSET, NULL), VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "0"), { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } @@ -457,13 +492,33 @@ static const config_var_t testing_tor_network_defaults[] = { V(V3AuthVotingInterval, INTERVAL, "5 minutes"), V(V3AuthVoteDelay, INTERVAL, "20 seconds"), V(V3AuthDistDelay, INTERVAL, "20 seconds"), - V(TestingV3AuthInitialVotingInterval, INTERVAL, "5 minutes"), + V(TestingV3AuthInitialVotingInterval, INTERVAL, "150 seconds"), V(TestingV3AuthInitialVoteDelay, INTERVAL, "20 seconds"), V(TestingV3AuthInitialDistDelay, INTERVAL, "20 seconds"), + V(TestingV3AuthVotingStartOffset, INTERVAL, "0"), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"), V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"), V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"), + V(TestingServerDownloadSchedule, CSV_INTERVAL, "0, 0, 0, 5, 10, 15, " + "20, 30, 60"), + V(TestingClientDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, 15, 20, " + "30, 60"), + V(TestingServerConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " + "15, 20, 30, 60"), + V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " + "15, 20, 30, 60"), + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "60, 30, 30, 60"), + V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"), + V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"), + V(TestingConsensusMaxDownloadTries, UINT, "80"), + V(TestingDescriptorMaxDownloadTries, UINT, "80"), + V(TestingMicrodescMaxDownloadTries, UINT, "80"), + V(TestingCertMaxDownloadTries, UINT, "80"), + V(TestingEnableConnBwEvent, BOOL, "1"), + V(TestingEnableCellStatsEvent, BOOL, "1"), + V(TestingEnableTbEmptyEvent, BOOL, "1"), VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "1"), + V(RendPostPeriod, INTERVAL, "2 minutes"), { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } }; @@ -475,9 +530,6 @@ static const config_var_t testing_tor_network_defaults[] = { #ifdef _WIN32 static char *get_windows_conf_root(void); #endif -static int options_validate(or_options_t *old_options, - or_options_t *options, - int from_setconf, char **msg); static int options_act_reversible(const or_options_t *old_options, char **msg); static int options_act(const or_options_t *old_options); static int options_transition_allowed(const or_options_t *old, @@ -487,12 +539,7 @@ static int options_transition_affects_workers( const or_options_t *old_options, const or_options_t *new_options); static int options_transition_affects_descriptor( const or_options_t *old_options, const or_options_t *new_options); -static int check_nickname_list(const char *lst, const char *name, char **msg); - -static int parse_bridge_line(const char *line, int validate_only); -static int parse_client_transport_line(const char *line, int validate_only); - -static int parse_server_transport_line(const char *line, int validate_only); +static int check_nickname_list(char **lst, const char *name, char **msg); static char *get_bindaddr_from_transport_listen_line(const char *line, const char *transport); static int parse_dir_authority_line(const char *line, @@ -509,7 +556,8 @@ static int check_server_ports(const smartlist_t *ports, static int validate_data_directory(or_options_t *options); static int write_configuration_file(const char *fname, const or_options_t *options); -static int options_init_logs(or_options_t *options, int validate_only); +static int options_init_logs(const or_options_t *old_options, + or_options_t *options, int validate_only); static void init_libevent(const or_options_t *options); static int opt_streq(const char *s1, const char *s2); @@ -517,18 +565,23 @@ static int parse_outbound_addresses(or_options_t *options, int validate_only, char **msg); static void config_maybe_load_geoip_files_(const or_options_t *options, const or_options_t *old_options); +static int options_validate_cb(void *old_options, void *options, + void *default_options, + int from_setconf, char **msg); +static uint64_t compute_real_max_mem_in_queues(const uint64_t val, + int log_guess); /** Magic value for or_options_t. */ #define OR_OPTIONS_MAGIC 9090909 /** Configuration format for or_options_t. */ -static config_format_t options_format = { +STATIC config_format_t options_format = { sizeof(or_options_t), OR_OPTIONS_MAGIC, STRUCT_OFFSET(or_options_t, magic_), option_abbrevs_, option_vars_, - (validate_fn_t)options_validate, + options_validate_cb, NULL }; @@ -545,8 +598,12 @@ static or_options_t *global_default_options = NULL; static char *torrc_fname = NULL; /** Name of the most recently read torrc-defaults file.*/ static char *torrc_defaults_fname; -/** Configuration Options set by command line. */ +/** Configuration options set by command line. */ static config_line_t *global_cmdline_options = NULL; +/** Non-configuration options set by the command line */ +static config_line_t *global_cmdline_only_options = NULL; +/** Boolean: Have we parsed the command line? */ +static int have_parsed_cmdline = 0; /** Contents of most recently read DirPortFrontPage file. */ static char *global_dirfrontpagecontents = NULL; /** List of port_cfg_t for all configured ports. */ @@ -568,8 +625,8 @@ get_options_mutable(void) } /** Returns the currently configured options */ -const or_options_t * -get_options(void) +MOCK_IMPL(const or_options_t *, +get_options,(void)) { return get_options_mutable(); } @@ -678,7 +735,7 @@ get_short_version(void) /** Release additional memory allocated in options */ -static void +STATIC void or_options_free(or_options_t *options) { if (!options) @@ -691,6 +748,7 @@ or_options_free(or_options_t *options) smartlist_free(options->NodeFamilySets); } tor_free(options->BridgePassword_AuthDigest_); + tor_free(options->command_arg); config_free(&options_format, options); } @@ -707,6 +765,9 @@ config_free_all(void) config_free_lines(global_cmdline_options); global_cmdline_options = NULL; + config_free_lines(global_cmdline_only_options); + global_cmdline_only_options = NULL; + if (configured_ports) { SMARTLIST_FOREACH(configured_ports, port_cfg_t *, p, port_cfg_free(p)); @@ -781,36 +842,41 @@ escaped_safe_str(const char *address) } /** Add the default directory authorities directly into the trusted dir list, - * but only add them insofar as they share bits with <b>type</b>. */ + * but only add them insofar as they share bits with <b>type</b>. + * Each authority's bits are restricted to the bits shared with <b>type</b>. + * If <b>type</b> is ALL_DIRINFO or NO_DIRINFO (zero), add all authorities. */ static void add_default_trusted_dir_authorities(dirinfo_type_t type) { int i; const char *authorities[] = { - "moria1 orport=9101 no-v2 " + "moria1 orport=9101 " "v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 " "128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31", - "tor26 v1 orport=443 v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 " + "tor26 orport=443 " + "v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 " "86.59.21.38:80 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D", - "dizum orport=443 v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 " + "dizum orport=443 " + "v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 " "194.109.206.212:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755", - "Tonga orport=443 bridge no-v2 82.94.251.203:80 " - "4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D", - "gabelmoo orport=443 no-v2 " + "Tonga orport=443 bridge " + "82.94.251.203:80 4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D", + "gabelmoo orport=443 " "v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 " "131.188.40.189:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281", - "dannenberg orport=443 no-v2 " + "dannenberg orport=443 " "v3ident=0232AF901C31A04EE9848595AF9BB7620D4C5B2E " "193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123", - "urras orport=80 no-v2 v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C " + "urras orport=80 " + "v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C " "208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417", - "maatuska orport=80 no-v2 " + "maatuska orport=80 " "v3ident=49015F787433103580E3B66A1707A00E60F2D15B " "171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810", - "Faravahar orport=443 no-v2 " + "Faravahar orport=443 " "v3ident=EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 " "154.35.175.225:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC", - "longclaw orport=443 no-v2 " + "longclaw orport=443 " "v3ident=23D15D965BC35114467363C165C4F724B64B4F66 " "199.254.238.52:80 74A9 1064 6BCE EFBC D2E8 74FC 1DC9 9743 0F96 8145", NULL @@ -850,8 +916,7 @@ validate_dir_servers(or_options_t *options, or_options_t *old_options) config_line_t *cl; if (options->DirAuthorities && - (options->AlternateDirAuthority || options->AlternateBridgeAuthority || - options->AlternateHSAuthority)) { + (options->AlternateDirAuthority || options->AlternateBridgeAuthority)) { log_warn(LD_CONFIG, "You cannot set both DirAuthority and Alternate*Authority."); return -1; @@ -887,9 +952,6 @@ validate_dir_servers(or_options_t *options, or_options_t *old_options) for (cl = options->AlternateDirAuthority; cl; cl = cl->next) if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0) return -1; - for (cl = options->AlternateHSAuthority; cl; cl = cl->next) - if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0) - return -1; for (cl = options->FallbackDir; cl; cl = cl->next) if (parse_dir_fallback_line(cl->value, 1)<0) return -1; @@ -912,9 +974,7 @@ consider_adding_dir_servers(const or_options_t *options, !config_lines_eq(options->AlternateBridgeAuthority, old_options->AlternateBridgeAuthority) || !config_lines_eq(options->AlternateDirAuthority, - old_options->AlternateDirAuthority) || - !config_lines_eq(options->AlternateHSAuthority, - old_options->AlternateHSAuthority); + old_options->AlternateDirAuthority); if (!need_to_update) return 0; /* all done */ @@ -928,11 +988,11 @@ consider_adding_dir_servers(const or_options_t *options, if (!options->AlternateBridgeAuthority) type |= BRIDGE_DIRINFO; if (!options->AlternateDirAuthority) - type |= V1_DIRINFO | V2_DIRINFO | V3_DIRINFO | EXTRAINFO_DIRINFO | - MICRODESC_DIRINFO; - if (!options->AlternateHSAuthority) - type |= HIDSERV_DIRINFO; - add_default_trusted_dir_authorities(type); + type |= V3_DIRINFO | EXTRAINFO_DIRINFO | MICRODESC_DIRINFO; + /* if type == NO_DIRINFO, we don't want to add any of the + * default authorities, because we've replaced them all */ + if (type != NO_DIRINFO) + add_default_trusted_dir_authorities(type); } if (!options->FallbackDir) add_default_fallback_dir_servers(); @@ -946,9 +1006,6 @@ consider_adding_dir_servers(const or_options_t *options, for (cl = options->AlternateDirAuthority; cl; cl = cl->next) if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0) return -1; - for (cl = options->AlternateHSAuthority; cl; cl = cl->next) - if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0) - return -1; for (cl = options->FallbackDir; cl; cl = cl->next) if (parse_dir_fallback_line(cl->value, 0)<0) return -1; @@ -971,7 +1028,8 @@ options_act_reversible(const or_options_t *old_options, char **msg) int running_tor = options->command == CMD_RUN_TOR; int set_conn_limit = 0; int r = -1; - int logs_marked = 0; + int logs_marked = 0, logs_initialized = 0; + int old_min_log_level = get_min_log_level(); /* Daemonize _first_, since we only want to open most of this stuff in * the subprocess. Libevent bases can't be reliably inherited across @@ -981,6 +1039,11 @@ options_act_reversible(const or_options_t *old_options, char **msg) start_daemon(); } +#ifdef HAVE_SYSTEMD + /* Our PID may have changed, inform supervisor */ + sd_notifyf(0, "MAINPID=%ld\n", (long int)getpid()); +#endif + #ifndef HAVE_SYS_UN_H if (options->ControlSocket || options->ControlSocketsGroupWritable) { *msg = tor_strdup("Unix domain sockets (ControlSocket) not supported " @@ -998,18 +1061,32 @@ options_act_reversible(const or_options_t *old_options, char **msg) if (running_tor) { int n_ports=0; /* We need to set the connection limit before we can open the listeners. */ - if (set_max_file_descriptors((unsigned)options->ConnLimit, - &options->ConnLimit_) < 0) { - *msg = tor_strdup("Problem with ConnLimit value. See logs for details."); - goto rollback; + if (! sandbox_is_active()) { + if (set_max_file_descriptors((unsigned)options->ConnLimit, + &options->ConnLimit_) < 0) { + *msg = tor_strdup("Problem with ConnLimit value. " + "See logs for details."); + goto rollback; + } + set_conn_limit = 1; + } else { + tor_assert(old_options); + options->ConnLimit_ = old_options->ConnLimit_; } - set_conn_limit = 1; /* Set up libevent. (We need to do this before we can register the * listeners as listeners.) */ if (running_tor && !libevent_initialized) { init_libevent(options); libevent_initialized = 1; + + /* + * Initialize the scheduler - this has to come after + * options_init_from_torrc() sets up libevent - why yes, that seems + * completely sensible to hide the libevent setup in the option parsing + * code! It also needs to happen before init_keys(), so it needs to + * happen here too. How yucky. */ + scheduler_init(); } /* Adjust the port configuration so we can launch listeners. */ @@ -1039,12 +1116,15 @@ options_act_reversible(const or_options_t *old_options, char **msg) "non-control network connections. Shutting down all existing " "connections."); connection_mark_all_noncontrol_connections(); + /* We can't complete circuits until the network is re-enabled. */ + note_that_we_maybe_cant_complete_circuits(); } } #if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) /* Open /dev/pf before dropping privileges. */ - if (options->TransPort_set) { + if (options->TransPort_set && + options->TransProxyType_parsed == TPT_DEFAULT) { if (get_pf_socket() < 0) { *msg = tor_strdup("Unable to open /dev/pf for transparent proxy."); goto rollback; @@ -1081,23 +1161,6 @@ options_act_reversible(const or_options_t *old_options, char **msg) /* No need to roll back, since you can't change the value. */ } - /* Write control ports to disk as appropriate */ - control_ports_write_to_file(); - - if (directory_caches_v2_dir_info(options)) { - char *fn = NULL; - tor_asprintf(&fn, "%s"PATH_SEPARATOR"cached-status", - options->DataDirectory); - if (check_private_dir(fn, running_tor ? CPD_CREATE : CPD_CHECK, - options->User) < 0) { - tor_asprintf(msg, - "Couldn't access/create private data directory \"%s\"", fn); - tor_free(fn); - goto done; - } - tor_free(fn); - } - /* Bail out at this point if we're not going to be a client or server: * we don't run Tor itself. */ if (!running_tor) @@ -1105,10 +1168,12 @@ options_act_reversible(const or_options_t *old_options, char **msg) mark_logs_temp(); /* Close current logs once new logs are open. */ logs_marked = 1; - if (options_init_logs(options, 0)<0) { /* Configure the tor_log(s) */ + /* Configure the tor_log(s) */ + if (options_init_logs(old_options, options, 0)<0) { *msg = tor_strdup("Failed to init Log options. See logs for details."); goto rollback; } + logs_initialized = 1; commit: r = 0; @@ -1119,13 +1184,47 @@ options_act_reversible(const or_options_t *old_options, char **msg) add_callback_log(severity, control_event_logmsg); control_adjust_event_log_severity(); tor_free(severity); + tor_log_update_sigsafe_err_fds(); + } + if (logs_initialized) { + flush_log_messages_from_startup(); + } + + { + const char *badness = NULL; + int bad_safelog = 0, bad_severity = 0, new_badness = 0; + if (options->SafeLogging_ != SAFELOG_SCRUB_ALL) { + bad_safelog = 1; + if (!old_options || old_options->SafeLogging_ != options->SafeLogging_) + new_badness = 1; + } + if (get_min_log_level() >= LOG_INFO) { + bad_severity = 1; + if (get_min_log_level() != old_min_log_level) + new_badness = 1; + } + if (bad_safelog && bad_severity) + badness = "you disabled SafeLogging, and " + "you're logging more than \"notice\""; + else if (bad_safelog) + badness = "you disabled SafeLogging"; + else + badness = "you're logging more than \"notice\""; + if (new_badness) + log_warn(LD_GENERAL, "Your log may contain sensitive information - %s. " + "Don't log unless it serves an important reason. " + "Overwrite the log afterwards.", badness); } + SMARTLIST_FOREACH(replaced_listeners, connection_t *, conn, { + int marked = conn->marked_for_close; log_notice(LD_NET, "Closing old %s on %s:%d", conn_type_to_string(conn->type), conn->address, conn->port); connection_close_immediate(conn); - connection_mark_for_close(conn); + if (!marked) { + connection_mark_for_close(conn); + } }); goto done; @@ -1167,7 +1266,8 @@ options_need_geoip_info(const or_options_t *options, const char **reason_out) routerset_needs_geoip(options->EntryNodes) || routerset_needs_geoip(options->ExitNodes) || routerset_needs_geoip(options->ExcludeExitNodes) || - routerset_needs_geoip(options->ExcludeNodes); + routerset_needs_geoip(options->ExcludeNodes) || + routerset_needs_geoip(options->Tor2webRendezvousPoints); if (routerset_usage && reason_out) { *reason_out = "We've been configured to use (or avoid) nodes in certain " @@ -1272,6 +1372,9 @@ options_act(const or_options_t *old_options) } } + /* Write control ports to disk as appropriate */ + control_ports_write_to_file(); + if (running_tor && !have_lockfile()) { if (try_locking(options, 1) < 0) return -1; @@ -1297,19 +1400,34 @@ options_act(const or_options_t *old_options) log_err(LD_CONFIG, "This copy of Tor was not compiled to run in " "'tor2web mode'. It cannot be run with the Tor2webMode torrc " "option enabled. To enable Tor2webMode recompile with the " - "--enable-tor2webmode option."); + "--enable-tor2web-mode option."); return -1; } #endif + /* If we are a bridge with a pluggable transport proxy but no + Extended ORPort, inform the user that she is missing out. */ + if (server_mode(options) && options->ServerTransportPlugin && + !options->ExtORPort_lines) { + log_notice(LD_CONFIG, "We use pluggable transports but the Extended " + "ORPort is disabled. Tor and your pluggable transports proxy " + "communicate with each other via the Extended ORPort so it " + "is suggested you enable it: it will also allow your Bridge " + "to collect statistics about its clients that use pluggable " + "transports. Please enable it using the ExtORPort torrc option " + "(e.g. set 'ExtORPort auto')."); + } + if (options->Bridges) { mark_bridge_list(); for (cl = options->Bridges; cl; cl = cl->next) { - if (parse_bridge_line(cl->value, 0)<0) { + bridge_line_t *bridge_line = parse_bridge_line(cl->value); + if (!bridge_line) { log_warn(LD_BUG, "Previously validated Bridge line could not be added!"); return -1; } + bridge_add_from_config(bridge_line); } sweep_bridge_list(); } @@ -1333,32 +1451,47 @@ options_act(const or_options_t *old_options) rep_hist_load_mtbf_data(time(NULL)); } + /* If we have an ExtORPort, initialize its auth cookie. */ + if (running_tor && + init_ext_or_cookie_authentication(!!options->ExtORPort_lines) < 0) { + log_warn(LD_CONFIG,"Error creating Extended ORPort cookie file."); + return -1; + } + mark_transport_list(); pt_prepare_proxy_list_for_config_read(); - if (options->ClientTransportPlugin) { - for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { - if (parse_client_transport_line(cl->value, 0)<0) { - log_warn(LD_BUG, - "Previously validated ClientTransportPlugin line " - "could not be added!"); - return -1; + if (!options->DisableNetwork) { + if (options->ClientTransportPlugin) { + for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { + if (parse_transport_line(options, cl->value, 0, 0) < 0) { + log_warn(LD_BUG, + "Previously validated ClientTransportPlugin line " + "could not be added!"); + return -1; + } } } - } - if (options->ServerTransportPlugin && server_mode(options)) { - for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { - if (parse_server_transport_line(cl->value, 0)<0) { - log_warn(LD_BUG, - "Previously validated ServerTransportPlugin line " - "could not be added!"); - return -1; + if (options->ServerTransportPlugin && server_mode(options)) { + for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { + if (parse_transport_line(options, cl->value, 0, 1) < 0) { + log_warn(LD_BUG, + "Previously validated ServerTransportPlugin line " + "could not be added!"); + return -1; + } } } } sweep_transport_list(); sweep_proxy_list(); + /* Start the PT proxy configuration. By doing this configuration + here, we also figure out which proxies need to be restarted and + which not. */ + if (pt_proxies_configuration_pending() && !net_is_disabled()) + pt_configure_remaining_proxies(); + /* Bail out at this point if we're not going to be a client or server: * we want to not fork, and to log stuff to stderr. */ if (!running_tor) @@ -1408,8 +1541,9 @@ options_act(const or_options_t *old_options) /* Write our PID to the PID file. If we do not have write permissions we * will log a warning */ - if (options->PidFile) + if (options->PidFile && !sandbox_is_active()) { write_pidfile(options->PidFile); + } /* Register addressmap directives */ config_register_addressmaps(options); @@ -1423,8 +1557,8 @@ options_act(const or_options_t *old_options) return -1; } - if (init_cookie_authentication(options->CookieAuthentication) < 0) { - log_warn(LD_CONFIG,"Error creating cookie authentication file."); + if (init_control_cookie_authentication(options->CookieAuthentication) < 0) { + log_warn(LD_CONFIG,"Error creating control cookie authentication file."); return -1; } @@ -1436,6 +1570,12 @@ options_act(const or_options_t *old_options) return -1; } + /* Set up scheduler thresholds */ + scheduler_set_watermarks((uint32_t)options->SchedulerLowWaterMark__, + (uint32_t)options->SchedulerHighWaterMark__, + (options->SchedulerMaxFlushCells__ > 0) ? + options->SchedulerMaxFlushCells__ : 1000); + /* Set up accounting */ if (accounting_parse_options(options, 0)<0) { log_warn(LD_CONFIG,"Error in accounting options"); @@ -1485,11 +1625,25 @@ options_act(const or_options_t *old_options) } if (parse_outbound_addresses(options, 0, &msg) < 0) { - log_warn(LD_BUG, "Failed parsing oubound bind addresses: %s", msg); + log_warn(LD_BUG, "Failed parsing outbound bind addresses: %s", msg); tor_free(msg); return -1; } + config_maybe_load_geoip_files_(options, old_options); + + if (geoip_is_loaded(AF_INET) && options->GeoIPExcludeUnknown) { + /* ExcludeUnknown is true or "auto" */ + const int is_auto = options->GeoIPExcludeUnknown == -1; + int changed; + + changed = routerset_add_unknown_ccs(&options->ExcludeNodes, is_auto); + changed += routerset_add_unknown_ccs(&options->ExcludeExitNodes, is_auto); + + if (changed) + routerset_add_unknown_ccs(&options->ExcludeExitNodesUnion_, is_auto); + } + /* Check for transitions that need action. */ if (old_options) { int revise_trackexithosts = 0; @@ -1503,6 +1657,8 @@ options_act(const or_options_t *old_options) options->ExcludeExitNodes) || !routerset_equal(old_options->EntryNodes, options->EntryNodes) || !routerset_equal(old_options->ExitNodes, options->ExitNodes) || + !routerset_equal(old_options->Tor2webRendezvousPoints, + options->Tor2webRendezvousPoints) || options->StrictNodes != old_options->StrictNodes) { log_info(LD_CIRC, "Changed to using entry guards or bridges, or changed " @@ -1568,11 +1724,12 @@ options_act(const or_options_t *old_options) "Worker-related options changed. Rotating workers."); if (server_mode(options) && !server_mode(old_options)) { + cpu_init(); ip_address_changed(0); - if (can_complete_circuit || !any_predicted_circuits(time(NULL))) + if (have_completed_a_circuit() || !any_predicted_circuits(time(NULL))) inform_testing_reachability(); } - cpuworkers_rotate(); + cpuworkers_rotate_keyinfo(); if (dns_reset()) return -1; } else { @@ -1585,32 +1742,23 @@ options_act(const or_options_t *old_options) connection_or_update_token_buckets(get_connection_array(), options); } - config_maybe_load_geoip_files_(options, old_options); - - if (geoip_is_loaded(AF_INET) && options->GeoIPExcludeUnknown) { - /* ExcludeUnknown is true or "auto" */ - const int is_auto = options->GeoIPExcludeUnknown == -1; - int changed; - - changed = routerset_add_unknown_ccs(&options->ExcludeNodes, is_auto); - changed += routerset_add_unknown_ccs(&options->ExcludeExitNodes, is_auto); - - if (changed) - routerset_add_unknown_ccs(&options->ExcludeExitNodesUnion_, is_auto); - } + /* Only collect directory-request statistics on relays and bridges. */ + options->DirReqStatistics = options->DirReqStatistics_option && + server_mode(options); if (options->CellStatistics || options->DirReqStatistics || options->EntryStatistics || options->ExitPortStatistics || options->ConnDirectionStatistics || + options->HiddenServiceStatistics || options->BridgeAuthoritativeDir) { time_t now = time(NULL); int print_notice = 0; - /* If we aren't acting as a server, we can't collect stats anyway. */ - if (!server_mode(options)) { + /* Only collect other relay-only statistics on relays. */ + if (!public_server_mode(options)) { options->CellStatistics = 0; - options->DirReqStatistics = 0; options->EntryStatistics = 0; + options->HiddenServiceStatistics = 0; options->ExitPortStatistics = 0; } @@ -1625,8 +1773,8 @@ options_act(const or_options_t *old_options) geoip_dirreq_stats_init(now); print_notice = 1; } else { + /* disable statistics collection since we have no geoip file */ options->DirReqStatistics = 0; - /* Don't warn Tor clients, they don't use statistics */ if (options->ORPort_set) log_notice(LD_CONFIG, "Configured to measure directory request " "statistics, but no GeoIP database found. " @@ -1656,6 +1804,11 @@ options_act(const or_options_t *old_options) options->ConnDirectionStatistics) { rep_hist_conn_stats_init(now); } + if ((!old_options || !old_options->HiddenServiceStatistics) && + options->HiddenServiceStatistics) { + log_info(LD_CONFIG, "Configured to measure hidden service statistics."); + rep_hist_hs_stats_init(now); + } if ((!old_options || !old_options->BridgeAuthoritativeDir) && options->BridgeAuthoritativeDir) { rep_hist_desc_stats_init(now); @@ -1667,6 +1820,8 @@ options_act(const or_options_t *old_options) "data directory in 24 hours from now."); } + /* If we used to have statistics enabled but we just disabled them, + stop gathering them. */ if (old_options && old_options->CellStatistics && !options->CellStatistics) rep_hist_buffer_stats_term(); @@ -1676,6 +1831,9 @@ options_act(const or_options_t *old_options) if (old_options && old_options->EntryStatistics && !options->EntryStatistics) geoip_entry_stats_term(); + if (old_options && old_options->HiddenServiceStatistics && + !options->HiddenServiceStatistics) + rep_hist_hs_stats_term(); if (old_options && old_options->ExitPortStatistics && !options->ExitPortStatistics) rep_hist_exit_stats_term(); @@ -1708,7 +1866,7 @@ options_act(const or_options_t *old_options) directory_fetches_dir_info_early(old_options)) || !bool_eq(directory_fetches_dir_info_later(options), directory_fetches_dir_info_later(old_options))) { - /* Make sure update_router_have_min_dir_info gets called. */ + /* Make sure update_router_have_minimum_dir_info() gets called. */ router_dir_info_changed(); /* We might need to download a new consensus status later or sooner than * we had expected. */ @@ -1732,40 +1890,66 @@ options_act(const or_options_t *old_options) return 0; } -/** Helper: Read a list of configuration options from the command line. - * If successful, put them in *<b>result</b> and return 0, and return - * -1 and leave *<b>result</b> alone. */ -static int -config_get_commandlines(int argc, char **argv, config_line_t **result) +static const struct { + const char *name; + int takes_argument; +} CMDLINE_ONLY_OPTIONS[] = { + { "-f", 1 }, + { "--allow-missing-torrc", 0 }, + { "--defaults-torrc", 1 }, + { "--hash-password", 1 }, + { "--dump-config", 1 }, + { "--list-fingerprint", 0 }, + { "--verify-config", 0 }, + { "--ignore-missing-torrc", 0 }, + { "--quiet", 0 }, + { "--hush", 0 }, + { "--version", 0 }, + { "--library-versions", 0 }, + { "-h", 0 }, + { "--help", 0 }, + { "--list-torrc-options", 0 }, + { "--digests", 0 }, + { "--nt-service", 0 }, + { "-nt-service", 0 }, + { NULL, 0 }, +}; + +/** Helper: Read a list of configuration options from the command line. If + * successful, or if ignore_errors is set, put them in *<b>result</b>, put the + * commandline-only options in *<b>cmdline_result</b>, and return 0; + * otherwise, return -1 and leave *<b>result</b> and <b>cmdline_result</b> + * alone. */ +int +config_parse_commandline(int argc, char **argv, int ignore_errors, + config_line_t **result, + config_line_t **cmdline_result) { + config_line_t *param = NULL; + config_line_t *front = NULL; config_line_t **new = &front; - char *s; + + config_line_t *front_cmdline = NULL; + config_line_t **new_cmdline = &front_cmdline; + + char *s, *arg; int i = 1; while (i < argc) { unsigned command = CONFIG_LINE_NORMAL; int want_arg = 1; + int is_cmdline = 0; + int j; - if (!strcmp(argv[i],"-f") || - !strcmp(argv[i],"--defaults-torrc") || - !strcmp(argv[i],"--hash-password")) { - i += 2; /* command-line option with argument. ignore them. */ - continue; - } else if (!strcmp(argv[i],"--list-fingerprint") || - !strcmp(argv[i],"--verify-config") || - !strcmp(argv[i],"--ignore-missing-torrc") || - !strcmp(argv[i],"--quiet") || - !strcmp(argv[i],"--hush")) { - i += 1; /* command-line option. ignore it. */ - continue; - } else if (!strcmp(argv[i],"--nt-service") || - !strcmp(argv[i],"-nt-service")) { - i += 1; - continue; + for (j = 0; CMDLINE_ONLY_OPTIONS[j].name != NULL; ++j) { + if (!strcmp(argv[i], CMDLINE_ONLY_OPTIONS[j].name)) { + is_cmdline = 1; + want_arg = CMDLINE_ONLY_OPTIONS[j].takes_argument; + break; + } } - *new = tor_malloc_zero(sizeof(config_line_t)); s = argv[i]; /* Each keyword may be prefixed with one or two dashes. */ @@ -1785,22 +1969,39 @@ config_get_commandlines(int argc, char **argv, config_line_t **result) } if (want_arg && i == argc-1) { - log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.", - argv[i]); - config_free_lines(front); - return -1; + if (ignore_errors) { + arg = strdup(""); + } else { + log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.", + argv[i]); + config_free_lines(front); + config_free_lines(front_cmdline); + return -1; + } + } else { + arg = want_arg ? tor_strdup(argv[i+1]) : strdup(""); } - (*new)->key = tor_strdup(config_expand_abbrev(&options_format, s, 1, 1)); - (*new)->value = want_arg ? tor_strdup(argv[i+1]) : tor_strdup(""); - (*new)->command = command; - (*new)->next = NULL; + param = tor_malloc_zero(sizeof(config_line_t)); + param->key = is_cmdline ? tor_strdup(argv[i]) : + tor_strdup(config_expand_abbrev(&options_format, s, 1, 1)); + param->value = arg; + param->command = command; + param->next = NULL; log_debug(LD_CONFIG, "command line: parsed keyword '%s', value '%s'", - (*new)->key, (*new)->value); + param->key, param->value); + + if (is_cmdline) { + *new_cmdline = param; + new_cmdline = &((*new_cmdline)->next); + } else { + *new = param; + new = &((*new)->next); + } - new = &((*new)->next); i += want_arg ? 2 : 1; } + *cmdline_result = front_cmdline; *result = front; return 0; } @@ -1852,7 +2053,8 @@ options_trial_assign(config_line_t *list, int use_defaults, return r; } - if (options_validate(get_options_mutable(), trial_options, 1, msg) < 0) { + if (options_validate(get_options_mutable(), trial_options, + global_default_options, 1, msg) < 0) { config_free(&options_format, trial_options); return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ } @@ -1878,7 +2080,7 @@ print_usage(void) printf( "Copyright (c) 2001-2004, Roger Dingledine\n" "Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson\n" -"Copyright (c) 2007-2013, The Tor Project, Inc.\n\n" +"Copyright (c) 2007-2015, The Tor Project, Inc.\n\n" "tor -f <torrc> [args]\n" "See man page for options, or https://www.torproject.org/ for " "documentation.\n"); @@ -1910,8 +2112,41 @@ get_last_resolved_addr(void) return last_resolved_addr; } +/** Reset last_resolved_addr from outside this file. */ +void +reset_last_resolved_addr(void) +{ + last_resolved_addr = 0; +} + /** - * Use <b>options-\>Address</b> to guess our public IP address. + * Attempt getting our non-local (as judged by tor_addr_is_internal() + * function) IP address using following techniques, listed in + * order from best (most desirable, try first) to worst (least + * desirable, try if everything else fails). + * + * First, attempt using <b>options-\>Address</b> to get our + * non-local IP address. + * + * If <b>options-\>Address</b> represents a non-local IP address, + * consider it ours. + * + * If <b>options-\>Address</b> is a DNS name that resolves to + * a non-local IP address, consider this IP address ours. + * + * If <b>options-\>Address</b> is NULL, fall back to getting local + * hostname and using it in above-described ways to try and + * get our IP address. + * + * In case local hostname cannot be resolved to a non-local IP + * address, try getting an IP address of network interface + * in hopes it will be non-local one. + * + * Fail if one or more of the following is true: + * - DNS name in <b>options-\>Address</b> cannot be resolved. + * - <b>options-\>Address</b> is a local host address. + * - Attempt to getting local hostname fails. + * - Attempt to getting network interface address fails. * * Return 0 if all is well, or -1 if we can't find a suitable * public IP address. @@ -1920,6 +2155,11 @@ get_last_resolved_addr(void) * - Put our public IP address (in host order) into *<b>addr_out</b>. * - If <b>method_out</b> is non-NULL, set *<b>method_out</b> to a static * string describing how we arrived at our answer. + * - "CONFIGURED" - parsed from IP address string in + * <b>options-\>Address</b> + * - "RESOLVED" - resolved from DNS name in <b>options-\>Address</b> + * - "GETHOSTNAME" - resolved from a local hostname. + * - "INTERFACE" - retrieved from a network interface. * - If <b>hostname_out</b> is non-NULL, and we resolved a hostname to * get our address, set *<b>hostname_out</b> to a newly allocated string * holding that hostname. (If we didn't get our address by resolving a @@ -1945,6 +2185,7 @@ resolve_my_address(int warn_severity, const or_options_t *options, int notice_severity = warn_severity <= LOG_NOTICE ? LOG_NOTICE : warn_severity; + tor_addr_t myaddr; tor_assert(addr_out); /* @@ -1957,7 +2198,7 @@ resolve_my_address(int warn_severity, const or_options_t *options, explicit_ip = 0; /* it's implicit */ explicit_hostname = 0; /* it's implicit */ - if (gethostname(hostname, sizeof(hostname)) < 0) { + if (tor_gethostname(hostname, sizeof(hostname)) < 0) { log_fn(warn_severity, LD_NET,"Error obtaining local hostname"); return -1; } @@ -1995,24 +2236,26 @@ resolve_my_address(int warn_severity, const or_options_t *options, "local interface. Using that.", fmt_addr32(addr)); strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname)); } else { /* resolved hostname into addr */ + tor_addr_from_ipv4h(&myaddr, addr); + if (!explicit_hostname && - is_internal_IP(addr, 0)) { - uint32_t interface_ip; + tor_addr_is_internal(&myaddr, 0)) { + tor_addr_t interface_ip; log_fn(notice_severity, LD_CONFIG, "Guessed local hostname '%s' " "resolves to a private IP address (%s). Trying something " "else.", hostname, fmt_addr32(addr)); - if (get_interface_address(warn_severity, &interface_ip)) { + if (get_interface_address6(warn_severity, AF_INET, &interface_ip)<0) { log_fn(warn_severity, LD_CONFIG, "Could not get local interface IP address. Too bad."); - } else if (is_internal_IP(interface_ip, 0)) { + } else if (tor_addr_is_internal(&interface_ip, 0)) { log_fn(notice_severity, LD_CONFIG, "Interface IP address '%s' is a private address too. " - "Ignoring.", fmt_addr32(interface_ip)); + "Ignoring.", fmt_addr(&interface_ip)); } else { from_interface = 1; - addr = interface_ip; + addr = tor_addr_to_ipv4h(&interface_ip); log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for local interface." " Using that.", fmt_addr32(addr)); @@ -2030,8 +2273,10 @@ resolve_my_address(int warn_severity, const or_options_t *options, * out if it is and we don't want that. */ + tor_addr_from_ipv4h(&myaddr,addr); + addr_string = tor_dup_ip(addr); - if (is_internal_IP(addr, 0)) { + if (tor_addr_is_internal(&myaddr, 0)) { /* make sure we're ok with publishing an internal IP */ if (!options->DirAuthorities && !options->AlternateDirAuthority) { /* if they are using the default authorities, disallow internal IPs @@ -2120,8 +2365,8 @@ resolve_my_address(int warn_severity, const or_options_t *options, /** Return true iff <b>addr</b> is judged to be on the same network as us, or * on a private network. */ -int -is_local_addr(const tor_addr_t *addr) +MOCK_IMPL(int, +is_local_addr, (const tor_addr_t *addr)) { if (tor_addr_is_internal(addr, 0)) return 1; @@ -2137,7 +2382,7 @@ is_local_addr(const tor_addr_t *addr) * resolve_my_address will never be called at all). In those cases, * last_resolved_addr will be 0, and so checking to see whether ip is on * the same /24 as last_resolved_addr will be the same as checking whether - * it was on net 0, which is already done by is_internal_IP. + * it was on net 0, which is already done by tor_addr_is_internal. */ if ((last_resolved_addr & (uint32_t)0xffffff00ul) == (ip & (uint32_t)0xffffff00ul)) @@ -2166,10 +2411,29 @@ options_init(or_options_t *options) * include options that are the same as Tor's defaults. */ char * -options_dump(const or_options_t *options, int minimal) +options_dump(const or_options_t *options, int how_to_dump) { - return config_dump(&options_format, global_default_options, - options, minimal, 0); + const or_options_t *use_defaults; + int minimal; + switch (how_to_dump) { + case OPTIONS_DUMP_MINIMAL: + use_defaults = global_default_options; + minimal = 1; + break; + case OPTIONS_DUMP_DEFAULTS: + use_defaults = NULL; + minimal = 1; + break; + case OPTIONS_DUMP_ALL: + use_defaults = NULL; + minimal = 0; + break; + default: + log_warn(LD_BUG, "Bogus value for how_to_dump==%d", how_to_dump); + return NULL; + } + + return config_dump(&options_format, use_defaults, options, minimal, 0); } /** Return 0 if every element of sl is a string holding a decimal @@ -2218,7 +2482,7 @@ ensure_bandwidth_cap(uint64_t *value, const char *desc, char **msg) /** Parse an authority type from <b>options</b>-\>PublishServerDescriptor * and write it to <b>options</b>-\>PublishServerDescriptor_. Treat "1" - * as "v2,v3" unless BridgeRelay is 1, in which case treat it as "bridge". + * as "v3" unless BridgeRelay is 1, in which case treat it as "bridge". * Treat "0" as "". * Return 0 on success or -1 if not a recognized authority type (in which * case the value of PublishServerDescriptor_ is undefined). */ @@ -2232,14 +2496,16 @@ compute_publishserverdescriptor(or_options_t *options) return 0; SMARTLIST_FOREACH_BEGIN(list, const char *, string) { if (!strcasecmp(string, "v1")) - *auth |= V1_DIRINFO; + log_warn(LD_CONFIG, "PublishServerDescriptor v1 has no effect, because " + "there are no v1 directory authorities anymore."); else if (!strcmp(string, "1")) if (options->BridgeRelay) *auth |= BRIDGE_DIRINFO; else - *auth |= V2_DIRINFO | V3_DIRINFO; + *auth |= V3_DIRINFO; else if (!strcasecmp(string, "v2")) - *auth |= V2_DIRINFO; + log_warn(LD_CONFIG, "PublishServerDescriptor v2 has no effect, because " + "there are no v2 directory authorities anymore."); else if (!strcasecmp(string, "v3")) *auth |= V3_DIRINFO; else if (!strcasecmp(string, "bridge")) @@ -2259,6 +2525,12 @@ compute_publishserverdescriptor(or_options_t *options) /** Lowest allowable value for RendPostPeriod; if this is too low, hidden * services can overload the directory system. */ #define MIN_REND_POST_PERIOD (10*60) +#define MIN_REND_POST_PERIOD_TESTING (5) + +/** Higest allowable value for PredictedPortsRelevanceTime; if this is + * too high, our selection of exits will decrease for an extended + * period of time to an uncomfortable level .*/ +#define MAX_PREDICTED_CIRCS_RELEVANCE (60*60) /** Highest allowable value for RendPostPeriod. */ #define MAX_DIR_PERIOD (MIN_ONION_KEY_LIFETIME/2) @@ -2286,10 +2558,19 @@ compute_publishserverdescriptor(or_options_t *options) * */ #define RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT (10) -/** Return 0 if every setting in <b>options</b> is reasonable, and a - * permissible transition from <b>old_options</b>. Else return -1. - * Should have no side effects, except for normalizing the contents of - * <b>options</b>. +static int +options_validate_cb(void *old_options, void *options, void *default_options, + int from_setconf, char **msg) +{ + return options_validate(old_options, options, default_options, + from_setconf, msg); +} + +/** Return 0 if every setting in <b>options</b> is reasonable, is a + * permissible transition from <b>old_options</b>, and none of the + * testing-only settings differ from <b>default_options</b> unless in + * testing mode. Else return -1. Should have no side effects, except for + * normalizing the contents of <b>options</b>. * * On error, tor_strdup an error explanation into *<b>msg</b>. * @@ -2298,9 +2579,9 @@ compute_publishserverdescriptor(or_options_t *options) * Log line should stay empty. If it's 0, then give us a default log * if there are no logs defined. */ -static int +STATIC int options_validate(or_options_t *old_options, or_options_t *options, - int from_setconf, char **msg) + or_options_t *default_options, int from_setconf, char **msg) { int i; config_line_t *cl; @@ -2358,7 +2639,8 @@ options_validate(or_options_t *old_options, or_options_t *options, config_line_append(&options->Logs, "Log", "warn stdout"); } - if (options_init_logs(options, 1)<0) /* Validate the tor_log(s) */ + /* Validate the tor_log(s) */ + if (options_init_logs(old_options, options, 1)<0) REJECT("Failed to validate Log options. See logs for details."); if (authdir_mode(options)) { @@ -2368,10 +2650,12 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Failed to resolve/guess local address. See logs for details."); } -#ifndef _WIN32 - if (options->RunAsDaemon && torrc_fname && path_is_relative(torrc_fname)) - REJECT("Can't use a relative path to torrc when RunAsDaemon is set."); -#endif + if (server_mode(options) && options->RendConfigLines) + log_warn(LD_CONFIG, + "Tor is currently configured as a relay and a hidden service. " + "That's not very secure: you should probably run your hidden service " + "in a separate Tor process, at least -- see " + "https://trac.torproject.org/8742"); /* XXXX require that the only port not be DirPort? */ /* XXXX require that at least one port be listened-upon. */ @@ -2381,10 +2665,47 @@ options_validate(or_options_t *old_options, or_options_t *options, "undefined, and there aren't any hidden services configured. " "Tor will still run, but probably won't do anything."); -#ifndef USE_TRANSPARENT - /* XXXX024 I think we can remove this TransListenAddress */ - if (options->TransPort_set || options->TransListenAddress) - REJECT("TransPort and TransListenAddress are disabled in this build."); + options->TransProxyType_parsed = TPT_DEFAULT; +#ifdef USE_TRANSPARENT + if (options->TransProxyType) { + if (!strcasecmp(options->TransProxyType, "default")) { + options->TransProxyType_parsed = TPT_DEFAULT; + } else if (!strcasecmp(options->TransProxyType, "pf-divert")) { +#if !defined(__OpenBSD__) && !defined( DARWIN ) + /* Later versions of OS X have pf */ + REJECT("pf-divert is a OpenBSD-specific " + "and OS X/Darwin-specific feature."); +#else + options->TransProxyType_parsed = TPT_PF_DIVERT; +#endif + } else if (!strcasecmp(options->TransProxyType, "tproxy")) { +#if !defined(__linux__) + REJECT("TPROXY is a Linux-specific feature."); +#else + options->TransProxyType_parsed = TPT_TPROXY; +#endif + } else if (!strcasecmp(options->TransProxyType, "ipfw")) { +#if !defined(__FreeBSD__) && !defined( DARWIN ) + /* Earlier versions of OS X have ipfw */ + REJECT("ipfw is a FreeBSD-specific" + "and OS X/Darwin-specific feature."); +#else + options->TransProxyType_parsed = TPT_IPFW; +#endif + } else { + REJECT("Unrecognized value for TransProxyType"); + } + + if (strcasecmp(options->TransProxyType, "default") && + !options->TransPort_set) { + REJECT("Cannot use TransProxyType without any valid TransPort or " + "TransListenAddress."); + } + } +#else + if (options->TransPort_set) + REJECT("TransPort and TransListenAddress are disabled " + "in this build."); #endif if (options->TokenBucketRefillInterval <= 0 @@ -2392,16 +2713,23 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("TokenBucketRefillInterval must be between 1 and 1000 inclusive."); } - if (options->DisableV2DirectoryInfo_ && ! authdir_mode(options)) { - REJECT("DisableV2DirectoryInfo_ set, but we aren't an authority."); - } - if (options->ExcludeExitNodes || options->ExcludeNodes) { options->ExcludeExitNodesUnion_ = routerset_new(); routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeExitNodes); routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeNodes); } + if (options->SchedulerLowWaterMark__ == 0 || + options->SchedulerLowWaterMark__ > UINT32_MAX) { + log_warn(LD_GENERAL, "Bad SchedulerLowWaterMark__ option"); + return -1; + } else if (options->SchedulerHighWaterMark__ <= + options->SchedulerLowWaterMark__ || + options->SchedulerHighWaterMark__ > UINT32_MAX) { + log_warn(LD_GENERAL, "Bad SchedulerHighWaterMark option"); + return -1; + } + if (options->NodeFamilies) { options->NodeFamilySets = smartlist_new(); for (cl = options->NodeFamilies; cl; cl = cl->next) { @@ -2426,11 +2754,16 @@ options_validate(or_options_t *old_options, or_options_t *options, "features to be broken in unpredictable ways."); } + for (cl = options->RecommendedPackages; cl; cl = cl->next) { + if (! validate_recommended_package_line(cl->value)) { + log_warn(LD_CONFIG, "Invalid RecommendedPackage line %s will be ignored", + escaped(cl->value)); + } + } + if (options->AuthoritativeDir) { if (!options->ContactInfo && !options->TestingTorNetwork) REJECT("Authoritative directory servers must set ContactInfo"); - if (options->V1AuthoritativeDir && !options->RecommendedVersions) - REJECT("V1 authoritative dir servers must set RecommendedVersions."); if (!options->RecommendedClientVersions) options->RecommendedClientVersions = config_lines_dup(options->RecommendedVersions); @@ -2452,15 +2785,18 @@ options_validate(or_options_t *old_options, or_options_t *options, "extra-info documents. Setting DownloadExtraInfo."); options->DownloadExtraInfo = 1; } - if (!(options->BridgeAuthoritativeDir || options->HSAuthoritativeDir || - options->V1AuthoritativeDir || options->V2AuthoritativeDir || + if (!(options->BridgeAuthoritativeDir || options->V3AuthoritativeDir)) REJECT("AuthoritativeDir is set, but none of " - "(Bridge/HS/V1/V2/V3)AuthoritativeDir is set."); + "(Bridge/V3)AuthoritativeDir is set."); /* If we have a v3bandwidthsfile and it's broken, complain on startup */ if (options->V3BandwidthsFile && !old_options) { dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL); } + /* same for guardfraction file */ + if (options->GuardfractionFile && !old_options) { + dirserv_read_guardfraction_file(options->GuardfractionFile, NULL); + } } if (options->AuthoritativeDir && !options->DirPort_set) @@ -2476,10 +2812,6 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("FetchDirInfoExtraEarly requires that you also set " "FetchDirInfoEarly"); - if (options->HSAuthoritativeDir && proxy_mode(options)) - REJECT("Running as authoritative v0 HS directory, but also configured " - "as a client."); - if (options->ConnLimit <= 0) { tor_asprintf(msg, "ConnLimit must be greater than 0, but was set to %d", @@ -2616,11 +2948,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("If EntryNodes is set, UseEntryGuards must be enabled."); } - if (options->MaxMemInCellQueues < (256 << 20)) { - log_warn(LD_CONFIG, "MaxMemInCellQueues must be at least 256 MB for now. " - "Ideally, have it as large as you can afford."); - options->MaxMemInCellQueues = (256 << 20); - } + options->MaxMemInQueues = + compute_real_max_mem_in_queues(options->MaxMemInQueues_raw, + server_mode(options)); + options->MaxMemInQueues_low_threshold = (options->MaxMemInQueues / 4) * 3; options->AllowInvalid_ = 0; @@ -2665,8 +2996,7 @@ options_validate(or_options_t *old_options, or_options_t *options, if ((options->BridgeRelay || options->PublishServerDescriptor_ & BRIDGE_DIRINFO) - && (options->PublishServerDescriptor_ - & (V1_DIRINFO|V2_DIRINFO|V3_DIRINFO))) { + && (options->PublishServerDescriptor_ & V3_DIRINFO)) { REJECT("Bridges are not supposed to publish router descriptors to the " "directory authorities. Please correct your " "PublishServerDescriptor line."); @@ -2686,10 +3016,13 @@ options_validate(or_options_t *old_options, or_options_t *options, options->MinUptimeHidServDirectoryV2 = 0; } - if (options->RendPostPeriod < MIN_REND_POST_PERIOD) { + const int min_rendpostperiod = + options->TestingTorNetwork ? + MIN_REND_POST_PERIOD_TESTING : MIN_REND_POST_PERIOD; + if (options->RendPostPeriod < min_rendpostperiod) { log_warn(LD_CONFIG, "RendPostPeriod option is too short; " - "raising to %d seconds.", MIN_REND_POST_PERIOD); - options->RendPostPeriod = MIN_REND_POST_PERIOD; + "raising to %d seconds.", min_rendpostperiod); + options->RendPostPeriod = min_rendpostperiod;; } if (options->RendPostPeriod > MAX_DIR_PERIOD) { @@ -2698,6 +3031,14 @@ options_validate(or_options_t *old_options, or_options_t *options, options->RendPostPeriod = MAX_DIR_PERIOD; } + if (options->PredictedPortsRelevanceTime > + MAX_PREDICTED_CIRCS_RELEVANCE) { + log_warn(LD_CONFIG, "PredictedPortsRelevanceTime is too large; " + "clipping to %ds.", MAX_PREDICTED_CIRCS_RELEVANCE); + options->PredictedPortsRelevanceTime = MAX_PREDICTED_CIRCS_RELEVANCE; + } + +#ifdef ENABLE_TOR2WEB_MODE if (options->Tor2webMode && options->LearnCircuitBuildTimeout) { /* LearnCircuitBuildTimeout and Tor2webMode are incompatible in * two ways: @@ -2729,6 +3070,11 @@ options_validate(or_options_t *old_options, or_options_t *options, "Tor2WebMode is enabled; disabling UseEntryGuards."); options->UseEntryGuards = 0; } +#endif + + if (options->Tor2webRendezvousPoints && !options->Tor2webMode) { + REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode."); + } if (!(options->UseEntryGuards) && (options->RendConfigLines != NULL)) { @@ -2814,6 +3160,11 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->KeepalivePeriod < 1) REJECT("KeepalivePeriod option must be positive."); + if (options->PortForwarding && options->Sandbox) { + REJECT("PortForwarding is not compatible with Sandbox; at most one can " + "be set"); + } + if (ensure_bandwidth_cap(&options->BandwidthRate, "BandwidthRate", msg) < 0) return -1; @@ -2848,29 +3199,34 @@ options_validate(or_options_t *old_options, or_options_t *options, options->RelayBandwidthRate = options->RelayBandwidthBurst; if (server_mode(options)) { - if (options->BandwidthRate < ROUTER_REQUIRED_MIN_BANDWIDTH) { + const unsigned required_min_bw = + public_server_mode(options) ? + RELAY_REQUIRED_MIN_BANDWIDTH : BRIDGE_REQUIRED_MIN_BANDWIDTH; + const char * const optbridge = + public_server_mode(options) ? "" : "bridge "; + if (options->BandwidthRate < required_min_bw) { tor_asprintf(msg, "BandwidthRate is set to %d bytes/second. " - "For servers, it must be at least %d.", - (int)options->BandwidthRate, - ROUTER_REQUIRED_MIN_BANDWIDTH); + "For %sservers, it must be at least %u.", + (int)options->BandwidthRate, optbridge, + required_min_bw); return -1; } else if (options->MaxAdvertisedBandwidth < - ROUTER_REQUIRED_MIN_BANDWIDTH/2) { + required_min_bw/2) { tor_asprintf(msg, "MaxAdvertisedBandwidth is set to %d bytes/second. " - "For servers, it must be at least %d.", - (int)options->MaxAdvertisedBandwidth, - ROUTER_REQUIRED_MIN_BANDWIDTH/2); + "For %sservers, it must be at least %u.", + (int)options->MaxAdvertisedBandwidth, optbridge, + required_min_bw/2); return -1; } if (options->RelayBandwidthRate && - options->RelayBandwidthRate < ROUTER_REQUIRED_MIN_BANDWIDTH) { + options->RelayBandwidthRate < required_min_bw) { tor_asprintf(msg, "RelayBandwidthRate is set to %d bytes/second. " - "For servers, it must be at least %d.", - (int)options->RelayBandwidthRate, - ROUTER_REQUIRED_MIN_BANDWIDTH); + "For %sservers, it must be at least %u.", + (int)options->RelayBandwidthRate, optbridge, + required_min_bw); return -1; } } @@ -2906,6 +3262,16 @@ options_validate(or_options_t *old_options, or_options_t *options, } } + options->AccountingRule = ACCT_MAX; + if (options->AccountingRule_option) { + if (!strcmp(options->AccountingRule_option, "sum")) + options->AccountingRule = ACCT_SUM; + else if (!strcmp(options->AccountingRule_option, "max")) + options->AccountingRule = ACCT_MAX; + else + REJECT("AccountingRule must be 'sum' or 'max'"); + } + if (options->HTTPProxy) { /* parse it now */ if (tor_addr_port_lookup(options->HTTPProxy, &options->HTTPProxyAddr, &options->HTTPProxyPort) < 0) @@ -2954,11 +3320,11 @@ options_validate(or_options_t *old_options, or_options_t *options, } } - /* Check if more than one proxy type has been enabled. */ + /* Check if more than one exclusive proxy type has been enabled. */ if (!!options->Socks4Proxy + !!options->Socks5Proxy + - !!options->HTTPSProxy + !!options->ClientTransportPlugin > 1) + !!options->HTTPSProxy > 1) REJECT("You have configured more than one proxy type. " - "(Socks4Proxy|Socks5Proxy|HTTPSProxy|ClientTransportPlugin)"); + "(Socks4Proxy|Socks5Proxy|HTTPSProxy)"); /* Check if the proxies will give surprising behavior. */ if (options->HTTPProxy && !(options->Socks4Proxy || @@ -2973,14 +3339,14 @@ options_validate(or_options_t *old_options, or_options_t *options, size_t len; len = strlen(options->Socks5ProxyUsername); - if (len < 1 || len > 255) + if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE) REJECT("Socks5ProxyUsername must be between 1 and 255 characters."); if (!options->Socks5ProxyPassword) REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername."); len = strlen(options->Socks5ProxyPassword); - if (len < 1 || len > 255) + if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE) REJECT("Socks5ProxyPassword must be between 1 and 255 characters."); } else if (options->Socks5ProxyPassword) REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername."); @@ -3037,7 +3403,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "You should also make sure you aren't listing this bridge's " "fingerprint in any other MyFamily."); } - if (check_nickname_list(options->MyFamily, "MyFamily", msg)) + if (check_nickname_list(&options->MyFamily, "MyFamily", msg)) return -1; for (cl = options->NodeFamilies; cl; cl = cl->next) { routerset_t *rs = routerset_new(); @@ -3057,26 +3423,22 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->UseBridges && !options->Bridges) REJECT("If you set UseBridges, you must specify at least one bridge."); - if (options->UseBridges && !options->TunnelDirConns) - REJECT("If you set UseBridges, you must set TunnelDirConns."); - if (options->RendConfigLines && - (!options->TunnelDirConns || !options->PreferTunneledDirConns)) - REJECT("If you are running a hidden service, you must set TunnelDirConns " - "and PreferTunneledDirConns"); for (cl = options->Bridges; cl; cl = cl->next) { - if (parse_bridge_line(cl->value, 1)<0) - REJECT("Bridge line did not parse. See logs for details."); + bridge_line_t *bridge_line = parse_bridge_line(cl->value); + if (!bridge_line) + REJECT("Bridge line did not parse. See logs for details."); + bridge_line_free(bridge_line); } for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { - if (parse_client_transport_line(cl->value, 1)<0) - REJECT("Transport line did not parse. See logs for details."); + if (parse_transport_line(options, cl->value, 1, 0) < 0) + REJECT("Invalid client transport line. See logs for details."); } for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { - if (parse_server_transport_line(cl->value, 1)<0) - REJECT("Server transport line did not parse. See logs for details."); + if (parse_transport_line(options, cl->value, 1, 1) < 0) + REJECT("Invalid server transport line. See logs for details."); } if (options->ServerTransportPlugin && !server_mode(options)) { @@ -3102,6 +3464,19 @@ options_validate(or_options_t *old_options, or_options_t *options, "ServerTransportListenAddr line will be ignored."); } + for (cl = options->ServerTransportOptions; cl; cl = cl->next) { + /** If get_options_from_transport_options_line() fails with + 'transport' being NULL, it means that something went wrong + while parsing the ServerTransportOptions line. */ + smartlist_t *options_sl = + get_options_from_transport_options_line(cl->value, NULL); + if (!options_sl) + REJECT("ServerTransportOptions did not parse. See logs for details."); + + SMARTLIST_FOREACH(options_sl, char *, cp, tor_free(cp)); + smartlist_free(options_sl); + } + if (options->ConstrainedSockets) { /* If the user wants to constrain socket buffer use, make sure the desired * limit is between MIN|MAX_TCPSOCK_BUFFER in k increments. */ @@ -3126,19 +3501,68 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->V3AuthVoteDelay + options->V3AuthDistDelay >= options->V3AuthVotingInterval/2) { - REJECT("V3AuthVoteDelay plus V3AuthDistDelay must be less than half " - "V3AuthVotingInterval"); + /* + This doesn't work, but it seems like it should: + what code is preventing the interval being less than twice the lead-up? + if (options->TestingTorNetwork) { + if (options->V3AuthVoteDelay + options->V3AuthDistDelay >= + options->V3AuthVotingInterval) { + REJECT("V3AuthVoteDelay plus V3AuthDistDelay must be less than " + "V3AuthVotingInterval"); + } else { + COMPLAIN("V3AuthVoteDelay plus V3AuthDistDelay is more than half " + "V3AuthVotingInterval. This may lead to " + "consensus instability, particularly if clocks drift."); + } + } else { + */ + REJECT("V3AuthVoteDelay plus V3AuthDistDelay must be less than half " + "V3AuthVotingInterval"); + /* + } + */ + } + + if (options->V3AuthVoteDelay < MIN_VOTE_SECONDS) { + if (options->TestingTorNetwork) { + if (options->V3AuthVoteDelay < MIN_VOTE_SECONDS_TESTING) { + REJECT("V3AuthVoteDelay is way too low."); + } else { + COMPLAIN("V3AuthVoteDelay is very low. " + "This may lead to failure to vote for a consensus."); + } + } else { + REJECT("V3AuthVoteDelay is way too low."); + } + } + + if (options->V3AuthDistDelay < MIN_DIST_SECONDS) { + if (options->TestingTorNetwork) { + if (options->V3AuthDistDelay < MIN_DIST_SECONDS_TESTING) { + REJECT("V3AuthDistDelay is way too low."); + } else { + COMPLAIN("V3AuthDistDelay is very low. " + "This may lead to missing votes in a consensus."); + } + } else { + REJECT("V3AuthDistDelay is way too low."); + } } - if (options->V3AuthVoteDelay < MIN_VOTE_SECONDS) - REJECT("V3AuthVoteDelay is way too low."); - if (options->V3AuthDistDelay < MIN_DIST_SECONDS) - REJECT("V3AuthDistDelay is way too low."); if (options->V3AuthNIntervalsValid < 2) REJECT("V3AuthNIntervalsValid must be at least 2."); if (options->V3AuthVotingInterval < MIN_VOTE_INTERVAL) { - REJECT("V3AuthVotingInterval is insanely low."); + if (options->TestingTorNetwork) { + if (options->V3AuthVotingInterval < MIN_VOTE_INTERVAL_TESTING) { + REJECT("V3AuthVotingInterval is insanely low."); + } else { + COMPLAIN("V3AuthVotingInterval is very low. " + "This may lead to failure to synchronise for a consensus."); + } + } else { + REJECT("V3AuthVotingInterval is insanely low."); + } } else if (options->V3AuthVotingInterval > 24*60*60) { REJECT("V3AuthVotingInterval is insanely high."); } else if (((24*60*60) % options->V3AuthVotingInterval) != 0) { @@ -3160,24 +3584,6 @@ options_validate(or_options_t *old_options, or_options_t *options, AF_INET6, 1, msg)<0) return -1; - if (options->PreferTunneledDirConns && !options->TunnelDirConns) - REJECT("Must set TunnelDirConns if PreferTunneledDirConns is set."); - - if ((options->Socks4Proxy || options->Socks5Proxy) && - !options->HTTPProxy && !options->PreferTunneledDirConns) - REJECT("When Socks4Proxy or Socks5Proxy is configured, " - "PreferTunneledDirConns and TunnelDirConns must both be " - "set to 1, or HTTPProxy must be configured."); - - if (options->AutomapHostsSuffixes) { - SMARTLIST_FOREACH(options->AutomapHostsSuffixes, char *, suf, - { - size_t len = strlen(suf); - if (len && suf[len-1] == '.') - suf[len-1] = '\0'; - }); - } - if (options->TestingTorNetwork && !(options->DirAuthorities || (options->AlternateDirAuthority && @@ -3194,65 +3600,132 @@ options_validate(or_options_t *old_options, or_options_t *options, "ignore you."); } - /*XXXX checking for defaults manually like this is a bit fragile.*/ - - /* Keep changes to hard-coded values synchronous to man page and default - * values table. */ - if (options->TestingV3AuthInitialVotingInterval != 30*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingV3AuthInitialVotingInterval may only be changed in testing " - "Tor networks!"); - } else if (options->TestingV3AuthInitialVotingInterval < MIN_VOTE_INTERVAL) { +#define CHECK_DEFAULT(arg) \ + STMT_BEGIN \ + if (!options->TestingTorNetwork && \ + !options->UsingTestNetworkDefaults_ && \ + !config_is_same(&options_format,options, \ + default_options,#arg)) { \ + REJECT(#arg " may only be changed in testing Tor " \ + "networks!"); \ + } STMT_END + CHECK_DEFAULT(TestingV3AuthInitialVotingInterval); + CHECK_DEFAULT(TestingV3AuthInitialVoteDelay); + CHECK_DEFAULT(TestingV3AuthInitialDistDelay); + CHECK_DEFAULT(TestingV3AuthVotingStartOffset); + CHECK_DEFAULT(TestingAuthDirTimeToLearnReachability); + CHECK_DEFAULT(TestingEstimatedDescriptorPropagationTime); + CHECK_DEFAULT(TestingServerDownloadSchedule); + CHECK_DEFAULT(TestingClientDownloadSchedule); + CHECK_DEFAULT(TestingServerConsensusDownloadSchedule); + CHECK_DEFAULT(TestingClientConsensusDownloadSchedule); + CHECK_DEFAULT(TestingBridgeDownloadSchedule); + CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest); + CHECK_DEFAULT(TestingDirConnectionMaxStall); + CHECK_DEFAULT(TestingConsensusMaxDownloadTries); + CHECK_DEFAULT(TestingDescriptorMaxDownloadTries); + CHECK_DEFAULT(TestingMicrodescMaxDownloadTries); + CHECK_DEFAULT(TestingCertMaxDownloadTries); +#undef CHECK_DEFAULT + + if (options->TestingV3AuthInitialVotingInterval + < MIN_VOTE_INTERVAL_TESTING_INITIAL) { REJECT("TestingV3AuthInitialVotingInterval is insanely low."); } else if (((30*60) % options->TestingV3AuthInitialVotingInterval) != 0) { REJECT("TestingV3AuthInitialVotingInterval does not divide evenly into " "30 minutes."); } - if (options->TestingV3AuthInitialVoteDelay != 5*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - - REJECT("TestingV3AuthInitialVoteDelay may only be changed in testing " - "Tor networks!"); - } else if (options->TestingV3AuthInitialVoteDelay < MIN_VOTE_SECONDS) { + if (options->TestingV3AuthInitialVoteDelay < MIN_VOTE_SECONDS_TESTING) { REJECT("TestingV3AuthInitialVoteDelay is way too low."); } - if (options->TestingV3AuthInitialDistDelay != 5*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingV3AuthInitialDistDelay may only be changed in testing " - "Tor networks!"); - } else if (options->TestingV3AuthInitialDistDelay < MIN_DIST_SECONDS) { + if (options->TestingV3AuthInitialDistDelay < MIN_DIST_SECONDS_TESTING) { REJECT("TestingV3AuthInitialDistDelay is way too low."); } if (options->TestingV3AuthInitialVoteDelay + options->TestingV3AuthInitialDistDelay >= - options->TestingV3AuthInitialVotingInterval/2) { + options->TestingV3AuthInitialVotingInterval) { REJECT("TestingV3AuthInitialVoteDelay plus TestingV3AuthInitialDistDelay " - "must be less than half TestingV3AuthInitialVotingInterval"); + "must be less than TestingV3AuthInitialVotingInterval"); } - if (options->TestingAuthDirTimeToLearnReachability != 30*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingAuthDirTimeToLearnReachability may only be changed in " - "testing Tor networks!"); - } else if (options->TestingAuthDirTimeToLearnReachability < 0) { + if (options->TestingV3AuthVotingStartOffset > + MIN(options->TestingV3AuthInitialVotingInterval, + options->V3AuthVotingInterval)) { + REJECT("TestingV3AuthVotingStartOffset is higher than the voting " + "interval."); + } else if (options->TestingV3AuthVotingStartOffset < 0) { + REJECT("TestingV3AuthVotingStartOffset must be non-negative."); + } + + if (options->TestingAuthDirTimeToLearnReachability < 0) { REJECT("TestingAuthDirTimeToLearnReachability must be non-negative."); } else if (options->TestingAuthDirTimeToLearnReachability > 2*60*60) { COMPLAIN("TestingAuthDirTimeToLearnReachability is insanely high."); } - if (options->TestingEstimatedDescriptorPropagationTime != 10*60 && - !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { - REJECT("TestingEstimatedDescriptorPropagationTime may only be changed in " - "testing Tor networks!"); - } else if (options->TestingEstimatedDescriptorPropagationTime < 0) { + if (options->TestingEstimatedDescriptorPropagationTime < 0) { REJECT("TestingEstimatedDescriptorPropagationTime must be non-negative."); } else if (options->TestingEstimatedDescriptorPropagationTime > 60*60) { COMPLAIN("TestingEstimatedDescriptorPropagationTime is insanely high."); } + if (options->TestingClientMaxIntervalWithoutRequest < 1) { + REJECT("TestingClientMaxIntervalWithoutRequest is way too low."); + } else if (options->TestingClientMaxIntervalWithoutRequest > 3600) { + COMPLAIN("TestingClientMaxIntervalWithoutRequest is insanely high."); + } + + if (options->TestingDirConnectionMaxStall < 5) { + REJECT("TestingDirConnectionMaxStall is way too low."); + } else if (options->TestingDirConnectionMaxStall > 3600) { + COMPLAIN("TestingDirConnectionMaxStall is insanely high."); + } + + if (options->TestingConsensusMaxDownloadTries < 2) { + REJECT("TestingConsensusMaxDownloadTries must be greater than 1."); + } else if (options->TestingConsensusMaxDownloadTries > 800) { + COMPLAIN("TestingConsensusMaxDownloadTries is insanely high."); + } + + if (options->TestingDescriptorMaxDownloadTries < 2) { + REJECT("TestingDescriptorMaxDownloadTries must be greater than 1."); + } else if (options->TestingDescriptorMaxDownloadTries > 800) { + COMPLAIN("TestingDescriptorMaxDownloadTries is insanely high."); + } + + if (options->TestingMicrodescMaxDownloadTries < 2) { + REJECT("TestingMicrodescMaxDownloadTries must be greater than 1."); + } else if (options->TestingMicrodescMaxDownloadTries > 800) { + COMPLAIN("TestingMicrodescMaxDownloadTries is insanely high."); + } + + if (options->TestingCertMaxDownloadTries < 2) { + REJECT("TestingCertMaxDownloadTries must be greater than 1."); + } else if (options->TestingCertMaxDownloadTries > 800) { + COMPLAIN("TestingCertMaxDownloadTries is insanely high."); + } + + if (options->TestingEnableConnBwEvent && + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { + REJECT("TestingEnableConnBwEvent may only be changed in testing " + "Tor networks!"); + } + + if (options->TestingEnableCellStatsEvent && + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { + REJECT("TestingEnableCellStatsEvent may only be changed in testing " + "Tor networks!"); + } + + if (options->TestingEnableTbEmptyEvent && + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { + REJECT("TestingEnableTbEmptyEvent may only be changed in testing " + "Tor networks!"); + } + if (options->TestingTorNetwork) { log_warn(LD_CONFIG, "TestingTorNetwork is set. This will make your node " "almost unusable in the public Tor network, and is " @@ -3286,6 +3759,68 @@ options_validate(or_options_t *old_options, or_options_t *options, #undef COMPLAIN } +/* Given the value that the user has set for MaxMemInQueues, compute the + * actual maximum value. We clip this value if it's too low, and autodetect + * it if it's set to 0. */ +static uint64_t +compute_real_max_mem_in_queues(const uint64_t val, int log_guess) +{ + uint64_t result; + + if (val == 0) { +#define ONE_GIGABYTE (U64_LITERAL(1) << 30) +#define ONE_MEGABYTE (U64_LITERAL(1) << 20) +#if SIZEOF_VOID_P >= 8 +#define MAX_DEFAULT_MAXMEM (8*ONE_GIGABYTE) +#else +#define MAX_DEFAULT_MAXMEM (2*ONE_GIGABYTE) +#endif + /* The user didn't pick a memory limit. Choose a very large one + * that is still smaller than the system memory */ + static int notice_sent = 0; + size_t ram = 0; + if (get_total_system_memory(&ram) < 0) { + /* We couldn't determine our total system memory! */ +#if SIZEOF_VOID_P >= 8 + /* 64-bit system. Let's hope for 8 GB. */ + result = 8 * ONE_GIGABYTE; +#else + /* (presumably) 32-bit system. Let's hope for 1 GB. */ + result = ONE_GIGABYTE; +#endif + } else { + /* We detected it, so let's pick 3/4 of the total RAM as our limit. */ + const uint64_t avail = (ram / 4) * 3; + + /* Make sure it's in range from 0.25 GB to 8 GB. */ + if (avail > MAX_DEFAULT_MAXMEM) { + /* If you want to use more than this much RAM, you need to configure + it yourself */ + result = MAX_DEFAULT_MAXMEM; + } else if (avail < ONE_GIGABYTE / 4) { + result = ONE_GIGABYTE / 4; + } else { + result = avail; + } + } + if (log_guess && ! notice_sent) { + log_notice(LD_CONFIG, "%sMaxMemInQueues is set to "U64_FORMAT" MB. " + "You can override this by setting MaxMemInQueues by hand.", + ram ? "Based on detected system memory, " : "", + U64_PRINTF_ARG(result / ONE_MEGABYTE)); + notice_sent = 1; + } + return result; + } else if (val < ONE_GIGABYTE / 4) { + log_warn(LD_CONFIG, "MaxMemInQueues must be at least 256 MB for now. " + "Ideally, have it as large as you can afford."); + return ONE_GIGABYTE / 4; + } else { + /* The value was fine all along */ + return val; + } +} + /** Helper: return true iff s1 and s2 are both NULL, or both non-NULL * equal strings. */ static int @@ -3314,6 +3849,12 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->Sandbox != new_val->Sandbox) { + *msg = tor_strdup("While Tor is running, changing Sandbox " + "is not allowed."); + return -1; + } + if (strcmp(old->DataDirectory,new_val->DataDirectory)!=0) { tor_asprintf(msg, "While Tor is running, changing DataDirectory " @@ -3366,6 +3907,38 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (sandbox_is_active()) { +#define SB_NOCHANGE_STR(opt) \ + do { \ + if (! opt_streq(old->opt, new_val->opt)) { \ + *msg = tor_strdup("Can't change " #opt " while Sandbox is active"); \ + return -1; \ + } \ + } while (0) + + SB_NOCHANGE_STR(PidFile); + SB_NOCHANGE_STR(ServerDNSResolvConfFile); + SB_NOCHANGE_STR(DirPortFrontPage); + SB_NOCHANGE_STR(CookieAuthFile); + SB_NOCHANGE_STR(ExtORPortCookieAuthFile); + +#undef SB_NOCHANGE_STR + + if (! config_lines_eq(old->Logs, new_val->Logs)) { + *msg = tor_strdup("Can't change Logs while Sandbox is active"); + return -1; + } + if (old->ConnLimit != new_val->ConnLimit) { + *msg = tor_strdup("Can't change ConnLimit while Sandbox is active"); + return -1; + } + if (server_mode(old) != server_mode(new_val)) { + *msg = tor_strdup("Can't start/stop being a server while " + "Sandbox is active"); + return -1; + } + } + return 0; } @@ -3405,6 +3978,7 @@ options_transition_affects_descriptor(const or_options_t *old_options, !opt_streq(old_options->Nickname,new_options->Nickname) || !opt_streq(old_options->Address,new_options->Address) || !config_lines_eq(old_options->ExitPolicy,new_options->ExitPolicy) || + old_options->ExitRelay != new_options->ExitRelay || old_options->ExitPolicyRejectPrivate != new_options->ExitPolicyRejectPrivate || old_options->IPv6Exit != new_options->IPv6Exit || @@ -3493,7 +4067,10 @@ get_windows_conf_root(void) static const char * get_default_conf_file(int defaults_file) { -#ifdef _WIN32 +#ifdef DISABLE_SYSTEM_TORRC + (void) defaults_file; + return NULL; +#elif defined(_WIN32) if (defaults_file) { static char defaults_path[MAX_PATH+1]; tor_snprintf(defaults_path, MAX_PATH, "%s\\torrc-defaults", @@ -3511,31 +4088,63 @@ get_default_conf_file(int defaults_file) } /** Verify whether lst is a string containing valid-looking comma-separated - * nicknames, or NULL. Return 0 on success. Warn and return -1 on failure. + * nicknames, or NULL. Will normalise <b>lst</b> to prefix '$' to any nickname + * or fingerprint that needs it. Return 0 on success. + * Warn and return -1 on failure. */ static int -check_nickname_list(const char *lst, const char *name, char **msg) +check_nickname_list(char **lst, const char *name, char **msg) { int r = 0; smartlist_t *sl; + int changes = 0; - if (!lst) + if (!*lst) return 0; sl = smartlist_new(); - smartlist_split_string(sl, lst, ",", + smartlist_split_string(sl, *lst, ",", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0); - SMARTLIST_FOREACH(sl, const char *, s, + SMARTLIST_FOREACH_BEGIN(sl, char *, s) { if (!is_legal_nickname_or_hexdigest(s)) { + // check if first char is dollar + if (s[0] != '$') { + // Try again but with a dollar symbol prepended + char *prepended; + tor_asprintf(&prepended, "$%s", s); + + if (is_legal_nickname_or_hexdigest(prepended)) { + // The nickname is valid when it's prepended, swap the current + // version with a prepended one + tor_free(s); + SMARTLIST_REPLACE_CURRENT(sl, s, prepended); + changes = 1; + continue; + } + + // Still not valid, free and fallback to error message + tor_free(prepended); + } + tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name); r = -1; break; } - }); + } + SMARTLIST_FOREACH_END(s); + + // Replace the caller's nickname list with a fixed one + if (changes && r == 0) { + char *newNicknames = smartlist_join_strings(sl, ", ", 0, NULL); + tor_free(*lst); + *lst = newNicknames; + } + SMARTLIST_FOREACH(sl, char *, s, tor_free(s)); smartlist_free(sl); + return r; } @@ -3551,26 +4160,26 @@ check_nickname_list(const char *lst, const char *name, char **msg) * filename if it doesn't exist. */ static char * -find_torrc_filename(int argc, char **argv, +find_torrc_filename(config_line_t *cmd_arg, int defaults_file, int *using_default_fname, int *ignore_missing_torrc) { char *fname=NULL; - int i; + config_line_t *p_index; const char *fname_opt = defaults_file ? "--defaults-torrc" : "-f"; const char *ignore_opt = defaults_file ? NULL : "--ignore-missing-torrc"; if (defaults_file) *ignore_missing_torrc = 1; - for (i = 1; i < argc; ++i) { - if (i < argc-1 && !strcmp(argv[i],fname_opt)) { + for (p_index = cmd_arg; p_index; p_index = p_index->next) { + if (!strcmp(p_index->key, fname_opt)) { if (fname) { log_warn(LD_CONFIG, "Duplicate %s options on command line.", fname_opt); tor_free(fname); } - fname = expand_filename(argv[i+1]); + fname = expand_filename(p_index->value); { char *absfname; @@ -3580,8 +4189,7 @@ find_torrc_filename(int argc, char **argv, } *using_default_fname = 0; - ++i; - } else if (ignore_opt && !strcmp(argv[i],ignore_opt)) { + } else if (ignore_opt && !strcmp(p_index->key,ignore_opt)) { *ignore_missing_torrc = 1; } } @@ -3589,27 +4197,45 @@ find_torrc_filename(int argc, char **argv, if (*using_default_fname) { /* didn't find one, try CONFDIR */ const char *dflt = get_default_conf_file(defaults_file); - if (dflt && file_status(dflt) == FN_FILE) { + file_status_t st = file_status(dflt); + if (dflt && (st == FN_FILE || st == FN_EMPTY)) { fname = tor_strdup(dflt); } else { #ifndef _WIN32 char *fn = NULL; - if (!defaults_file) + if (!defaults_file) { fn = expand_filename("~/.torrc"); - if (fn && file_status(fn) == FN_FILE) { - fname = fn; + } + if (fn) { + file_status_t hmst = file_status(fn); + if (hmst == FN_FILE || hmst == FN_EMPTY || dflt == NULL) { + fname = fn; + } else { + tor_free(fn); + fname = tor_strdup(dflt); + } } else { - tor_free(fn); - fname = tor_strdup(dflt); + fname = dflt ? tor_strdup(dflt) : NULL; } #else - fname = tor_strdup(dflt); + fname = dflt ? tor_strdup(dflt) : NULL; #endif } } return fname; } +/** Read the torrc from standard input and return it as a string. + * Upon failure, return NULL. + */ +static char * +load_torrc_from_stdin(void) +{ + size_t sz_out; + + return read_file_to_str_until_eof(STDIN_FILENO,SIZE_MAX,&sz_out); +} + /** Load a configuration file from disk, setting torrc_fname or * torrc_defaults_fname if successful. * @@ -3618,7 +4244,7 @@ find_torrc_filename(int argc, char **argv, * Return the contents of the file on success, and NULL on failure. */ static char * -load_torrc_from_disk(int argc, char **argv, int defaults_file) +load_torrc_from_disk(config_line_t *cmd_arg, int defaults_file) { char *fname=NULL; char *cf = NULL; @@ -3626,16 +4252,20 @@ load_torrc_from_disk(int argc, char **argv, int defaults_file) int ignore_missing_torrc = 0; char **fname_var = defaults_file ? &torrc_defaults_fname : &torrc_fname; - fname = find_torrc_filename(argc, argv, defaults_file, - &using_default_torrc, &ignore_missing_torrc); - tor_assert(fname); - log_debug(LD_CONFIG, "Opening config file \"%s\"", fname); - - tor_free(*fname_var); - *fname_var = fname; + if (*fname_var == NULL) { + fname = find_torrc_filename(cmd_arg, defaults_file, + &using_default_torrc, &ignore_missing_torrc); + tor_free(*fname_var); + *fname_var = fname; + } else { + fname = *fname_var; + } + log_debug(LD_CONFIG, "Opening config file \"%s\"", fname?fname:"<NULL>"); /* Open config file */ - if (file_status(fname) != FN_FILE || + file_status_t st = fname ? file_status(fname) : FN_EMPTY; + if (fname == NULL || + !(st == FN_FILE || st == FN_EMPTY) || !(cf = read_file_to_str(fname,0,NULL))) { if (using_default_torrc == 1 || ignore_missing_torrc) { if (!defaults_file) @@ -3668,59 +4298,75 @@ int options_init_from_torrc(int argc, char **argv) { char *cf=NULL, *cf_defaults=NULL; - int i, command; + int command; int retval = -1; - static char **backup_argv; - static int backup_argc; char *command_arg = NULL; char *errmsg=NULL; + config_line_t *p_index = NULL; + config_line_t *cmdline_only_options = NULL; - if (argv) { /* first time we're called. save command line args */ - backup_argv = argv; - backup_argc = argc; - } else { /* we're reloading. need to clean up old options first. */ - argv = backup_argv; - argc = backup_argc; + /* Go through command-line variables */ + if (! have_parsed_cmdline) { + /* Or we could redo the list every time we pass this place. + * It does not really matter */ + if (config_parse_commandline(argc, argv, 0, &global_cmdline_options, + &global_cmdline_only_options) < 0) { + goto err; + } + have_parsed_cmdline = 1; } - if (argc > 1 && (!strcmp(argv[1], "-h") || !strcmp(argv[1],"--help"))) { + cmdline_only_options = global_cmdline_only_options; + + if (config_line_find(cmdline_only_options, "-h") || + config_line_find(cmdline_only_options, "--help")) { print_usage(); exit(0); } - if (argc > 1 && !strcmp(argv[1], "--list-torrc-options")) { + if (config_line_find(cmdline_only_options, "--list-torrc-options")) { /* For documenting validating whether we've documented everything. */ list_torrc_options(); exit(0); } - if (argc > 1 && (!strcmp(argv[1],"--version"))) { + if (config_line_find(cmdline_only_options, "--version")) { printf("Tor version %s.\n",get_version()); exit(0); } - if (argc > 1 && (!strcmp(argv[1],"--digests"))) { + + if (config_line_find(cmdline_only_options, "--digests")) { printf("Tor version %s.\n",get_version()); printf("%s", libor_get_digests()); printf("%s", tor_get_digests()); exit(0); } - /* Go through command-line variables */ - if (!global_cmdline_options) { - /* Or we could redo the list every time we pass this place. - * It does not really matter */ - if (config_get_commandlines(argc, argv, &global_cmdline_options) < 0) { - goto err; - } + if (config_line_find(cmdline_only_options, "--library-versions")) { + printf("Tor version %s. \n", get_version()); + printf("Library versions\tCompiled\t\tRuntime\n"); + printf("Libevent\t\t%-15s\t\t%s\n", + tor_libevent_get_header_version_str(), + tor_libevent_get_version_str()); + printf("OpenSSL \t\t%-15s\t\t%s\n", + crypto_openssl_get_header_version_str(), + crypto_openssl_get_version_str()); + printf("Zlib \t\t%-15s\t\t%s\n", + tor_zlib_get_header_version_str(), + tor_zlib_get_version_str()); + //TODO: Hex versions? + exit(0); } command = CMD_RUN_TOR; - for (i = 1; i < argc; ++i) { - if (!strcmp(argv[i],"--list-fingerprint")) { + for (p_index = cmdline_only_options; p_index; p_index = p_index->next) { + if (!strcmp(p_index->key,"--list-fingerprint")) { command = CMD_LIST_FINGERPRINT; - } else if (!strcmp(argv[i],"--hash-password")) { + } else if (!strcmp(p_index->key, "--hash-password")) { command = CMD_HASH_PASSWORD; - command_arg = tor_strdup( (i < argc-1) ? argv[i+1] : ""); - ++i; - } else if (!strcmp(argv[i],"--verify-config")) { + command_arg = p_index->value; + } else if (!strcmp(p_index->key, "--dump-config")) { + command = CMD_DUMP_CONFIG; + command_arg = p_index->value; + } else if (!strcmp(p_index->key, "--verify-config")) { command = CMD_VERIFY_CONFIG; } } @@ -3729,10 +4375,27 @@ options_init_from_torrc(int argc, char **argv) cf_defaults = tor_strdup(""); cf = tor_strdup(""); } else { - cf_defaults = load_torrc_from_disk(argc, argv, 1); - cf = load_torrc_from_disk(argc, argv, 0); - if (!cf) - goto err; + cf_defaults = load_torrc_from_disk(cmdline_only_options, 1); + + const config_line_t *f_line = config_line_find(cmdline_only_options, + "-f"); + + const int read_torrc_from_stdin = + (f_line != NULL && strcmp(f_line->value, "-") == 0); + + if (read_torrc_from_stdin) { + cf = load_torrc_from_stdin(); + } else { + cf = load_torrc_from_disk(cmdline_only_options, 0); + } + + if (!cf) { + if (config_line_find(cmdline_only_options, "--allow-missing-torrc")) { + cf = tor_strdup(""); + } else { + goto err; + } + } } retval = options_init_from_string(cf_defaults, cf, command, command_arg, @@ -3776,7 +4439,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, newoptions->magic_ = OR_OPTIONS_MAGIC; options_init(newoptions); newoptions->command = command; - newoptions->command_arg = command_arg; + newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL; for (i = 0; i < 2; ++i) { const char *body = i==0 ? cf_defaults : cf; @@ -3840,7 +4503,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, newoptions->magic_ = OR_OPTIONS_MAGIC; options_init(newoptions); newoptions->command = command; - newoptions->command_arg = command_arg; + newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL; /* Assign all options a second time. */ for (i = 0; i < 2; ++i) { @@ -3872,7 +4535,8 @@ options_init_from_string(const char *cf_defaults, const char *cf, } /* Validate newoptions */ - if (options_validate(oldoptions, newoptions, 0, msg) < 0) { + if (options_validate(oldoptions, newoptions, newdefaultoptions, + 0, msg) < 0) { err = SETOPT_ERR_PARSE; /*XXX make this a separate return value.*/ goto err; } @@ -3902,7 +4566,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, return err; } -/** Return the location for our configuration file. +/** Return the location for our configuration file. May return NULL. */ const char * get_torrc_fname(int defaults_fname) @@ -4009,7 +4673,8 @@ addressmap_register_auto(const char *from, const char *to, * Initialize the logs based on the configuration file. */ static int -options_init_logs(or_options_t *options, int validate_only) +options_init_logs(const or_options_t *old_options, or_options_t *options, + int validate_only) { config_line_t *opt; int ok; @@ -4102,7 +4767,21 @@ options_init_logs(or_options_t *options, int validate_only) !strcasecmp(smartlist_get(elts,0), "file")) { if (!validate_only) { char *fname = expand_filename(smartlist_get(elts, 1)); - if (add_file_log(severity, fname) < 0) { + /* Truncate if TruncateLogFile is set and we haven't seen this option + line before. */ + int truncate = 0; + if (options->TruncateLogFile) { + truncate = 1; + if (old_options) { + config_line_t *opt2; + for (opt2 = old_options->Logs; opt2; opt2 = opt2->next) + if (!strcmp(opt->value, opt2->value)) { + truncate = 0; + break; + } + } + } + if (add_file_log(severity, fname, truncate) < 0) { log_warn(LD_CONFIG, "Couldn't open file for 'Log %s': %s", opt->value, strerror(errno)); ok = 0; @@ -4129,21 +4808,72 @@ options_init_logs(or_options_t *options, int validate_only) return ok?0:-1; } +/** Given a smartlist of SOCKS arguments to be passed to a transport + * proxy in <b>args</b>, validate them and return -1 if they are + * corrupted. Return 0 if they seem OK. */ +static int +validate_transport_socks_arguments(const smartlist_t *args) +{ + char *socks_string = NULL; + size_t socks_string_len; + + tor_assert(args); + tor_assert(smartlist_len(args) > 0); + + SMARTLIST_FOREACH_BEGIN(args, const char *, s) { + if (!string_is_key_value(LOG_WARN, s)) { /* items should be k=v items */ + log_warn(LD_CONFIG, "'%s' is not a k=v item.", s); + return -1; + } + } SMARTLIST_FOREACH_END(s); + + socks_string = pt_stringify_socks_args(args); + if (!socks_string) + return -1; + + socks_string_len = strlen(socks_string); + tor_free(socks_string); + + if (socks_string_len > MAX_SOCKS5_AUTH_SIZE_TOTAL) { + log_warn(LD_CONFIG, "SOCKS arguments can't be more than %u bytes (%lu).", + MAX_SOCKS5_AUTH_SIZE_TOTAL, + (unsigned long) socks_string_len); + return -1; + } + + return 0; +} + +/** Deallocate a bridge_line_t structure. */ +/* private */ void +bridge_line_free(bridge_line_t *bridge_line) +{ + if (!bridge_line) + return; + + if (bridge_line->socks_args) { + SMARTLIST_FOREACH(bridge_line->socks_args, char*, s, tor_free(s)); + smartlist_free(bridge_line->socks_args); + } + tor_free(bridge_line->transport_name); + tor_free(bridge_line); +} + /** Read the contents of a Bridge line from <b>line</b>. Return 0 * if the line is well-formed, and -1 if it isn't. If * <b>validate_only</b> is 0, and the line is well-formed, then add - * the bridge described in the line to our internal bridge list. */ -static int -parse_bridge_line(const char *line, int validate_only) + * the bridge described in the line to our internal bridge list. + * + * Bridge line format: + * Bridge [transport] IP:PORT [id-fingerprint] [k=v] [k=v] ... + */ +/* private */ bridge_line_t * +parse_bridge_line(const char *line) { smartlist_t *items = NULL; - int r; char *addrport=NULL, *fingerprint=NULL; - char *transport_name=NULL; - char *field1=NULL; - tor_addr_t addr; - uint16_t port = 0; - char digest[DIGEST_LEN]; + char *field=NULL; + bridge_line_t *bridge_line = tor_malloc_zero(sizeof(bridge_line_t)); items = smartlist_new(); smartlist_split_string(items, line, NULL, @@ -4153,107 +4883,143 @@ parse_bridge_line(const char *line, int validate_only) goto err; } - /* field1 is either a transport name or addrport */ - field1 = smartlist_get(items, 0); + /* first field is either a transport name or addrport */ + field = smartlist_get(items, 0); smartlist_del_keeporder(items, 0); - if (!(strstr(field1, ".") || strstr(field1, ":"))) { - /* new-style bridge line */ - transport_name = field1; + if (string_is_C_identifier(field)) { + /* It's a transport name. */ + bridge_line->transport_name = field; if (smartlist_len(items) < 1) { log_warn(LD_CONFIG, "Too few items to Bridge line."); goto err; } - addrport = smartlist_get(items, 0); + addrport = smartlist_get(items, 0); /* Next field is addrport then. */ smartlist_del_keeporder(items, 0); } else { - addrport = field1; + addrport = field; } - if (tor_addr_port_lookup(addrport, &addr, &port)<0) { + if (tor_addr_port_parse(LOG_INFO, addrport, + &bridge_line->addr, &bridge_line->port, 443)<0) { log_warn(LD_CONFIG, "Error parsing Bridge address '%s'", addrport); goto err; } - if (!port) { - log_info(LD_CONFIG, - "Bridge address '%s' has no port; using default port 443.", - addrport); - port = 443; - } + /* If transports are enabled, next field could be a fingerprint or a + socks argument. If transports are disabled, next field must be + a fingerprint. */ if (smartlist_len(items)) { - fingerprint = smartlist_join_strings(items, "", 0, NULL); + if (bridge_line->transport_name) { /* transports enabled: */ + field = smartlist_get(items, 0); + smartlist_del_keeporder(items, 0); + + /* If it's a key=value pair, then it's a SOCKS argument for the + transport proxy... */ + if (string_is_key_value(LOG_DEBUG, field)) { + bridge_line->socks_args = smartlist_new(); + smartlist_add(bridge_line->socks_args, field); + } else { /* ...otherwise, it's the bridge fingerprint. */ + fingerprint = field; + } + + } else { /* transports disabled: */ + fingerprint = smartlist_join_strings(items, "", 0, NULL); + } + } + + /* Handle fingerprint, if it was provided. */ + if (fingerprint) { if (strlen(fingerprint) != HEX_DIGEST_LEN) { log_warn(LD_CONFIG, "Key digest for Bridge is wrong length."); goto err; } - if (base16_decode(digest, DIGEST_LEN, fingerprint, HEX_DIGEST_LEN)<0) { + if (base16_decode(bridge_line->digest, DIGEST_LEN, + fingerprint, HEX_DIGEST_LEN)<0) { log_warn(LD_CONFIG, "Unable to decode Bridge key digest."); goto err; } } - if (!validate_only) { - log_debug(LD_DIR, "Bridge at %s (transport: %s) (%s)", - fmt_addrport(&addr, port), - transport_name ? transport_name : "no transport", - fingerprint ? fingerprint : "no key listed"); - bridge_add_from_config(&addr, port, - fingerprint ? digest : NULL, transport_name); + /* If we are using transports, any remaining items in the smartlist + should be k=v values. */ + if (bridge_line->transport_name && smartlist_len(items)) { + if (!bridge_line->socks_args) + bridge_line->socks_args = smartlist_new(); + + /* append remaining items of 'items' to 'socks_args' */ + smartlist_add_all(bridge_line->socks_args, items); + smartlist_clear(items); + + tor_assert(smartlist_len(bridge_line->socks_args) > 0); + } + + if (bridge_line->socks_args) { + if (validate_transport_socks_arguments(bridge_line->socks_args) < 0) + goto err; } - r = 0; goto done; err: - r = -1; + bridge_line_free(bridge_line); + bridge_line = NULL; done: SMARTLIST_FOREACH(items, char*, s, tor_free(s)); smartlist_free(items); tor_free(addrport); - tor_free(transport_name); tor_free(fingerprint); - return r; + + return bridge_line; } -/** Read the contents of a ClientTransportPlugin line from - * <b>line</b>. Return 0 if the line is well-formed, and -1 if it - * isn't. +/** Read the contents of a ClientTransportPlugin or ServerTransportPlugin + * line from <b>line</b>, depending on the value of <b>server</b>. Return 0 + * if the line is well-formed, and -1 if it isn't. * - * If <b>validate_only</b> is 0, and the line is well-formed: + * If <b>validate_only</b> is 0, the line is well-formed, and the transport is + * needed by some bridge: * - If it's an external proxy line, add the transport described in the line to * our internal transport list. - * - If it's a managed proxy line, launch the managed proxy. */ -static int -parse_client_transport_line(const char *line, int validate_only) + * - If it's a managed proxy line, launch the managed proxy. + */ + +STATIC int +parse_transport_line(const or_options_t *options, + const char *line, int validate_only, + int server) { + smartlist_t *items = NULL; int r; - char *field2=NULL; - - const char *transports=NULL; - smartlist_t *transport_list=NULL; - char *addrport=NULL; + const char *transports = NULL; + smartlist_t *transport_list = NULL; + char *type = NULL; + char *addrport = NULL; tor_addr_t addr; uint16_t port = 0; - int socks_ver=PROXY_NONE; + int socks_ver = PROXY_NONE; /* managed proxy options */ - int is_managed=0; - char **proxy_argv=NULL; - char **tmp=NULL; - int proxy_argc,i; + int is_managed = 0; + char **proxy_argv = NULL; + char **tmp = NULL; + int proxy_argc, i; + int is_useless_proxy = 1; int line_length; + /* Split the line into space-separated tokens */ items = smartlist_new(); smartlist_split_string(items, line, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + line_length = smartlist_len(items); - line_length = smartlist_len(items); if (line_length < 3) { - log_warn(LD_CONFIG, "Too few arguments on ClientTransportPlugin line."); + log_warn(LD_CONFIG, + "Too few arguments on %sTransportPlugin line.", + server ? "Server" : "Client"); goto err; } @@ -4265,59 +5031,109 @@ parse_client_transport_line(const char *line, int validate_only) smartlist_split_string(transport_list, transports, ",", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport_name) { + /* validate transport names */ if (!string_is_C_identifier(transport_name)) { log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", transport_name); goto err; } - } SMARTLIST_FOREACH_END(transport_name); - /* field2 is either a SOCKS version or "exec" */ - field2 = smartlist_get(items, 1); + /* see if we actually need the transports provided by this proxy */ + if (!validate_only && transport_is_needed(transport_name)) + is_useless_proxy = 0; + } SMARTLIST_FOREACH_END(transport_name); - if (!strcmp(field2,"socks4")) { + type = smartlist_get(items, 1); + if (!strcmp(type, "exec")) { + is_managed = 1; + } else if (server && !strcmp(type, "proxy")) { + /* 'proxy' syntax only with ServerTransportPlugin */ + is_managed = 0; + } else if (!server && !strcmp(type, "socks4")) { + /* 'socks4' syntax only with ClientTransportPlugin */ + is_managed = 0; socks_ver = PROXY_SOCKS4; - } else if (!strcmp(field2,"socks5")) { + } else if (!server && !strcmp(type, "socks5")) { + /* 'socks5' syntax only with ClientTransportPlugin */ + is_managed = 0; socks_ver = PROXY_SOCKS5; - } else if (!strcmp(field2,"exec")) { - is_managed=1; } else { - log_warn(LD_CONFIG, "Strange ClientTransportPlugin field '%s'.", - field2); + log_warn(LD_CONFIG, + "Strange %sTransportPlugin type '%s'", + server ? "Server" : "Client", type); goto err; } - if (is_managed) { /* managed */ - if (!validate_only) { /* if we are not just validating, use the - rest of the line as the argv of the proxy - to be launched */ - proxy_argc = line_length-2; + if (is_managed && options->Sandbox) { + log_warn(LD_CONFIG, + "Managed proxies are not compatible with Sandbox mode." + "(%sTransportPlugin line was %s)", + server ? "Server" : "Client", escaped(line)); + goto err; + } + + if (is_managed) { + /* managed */ + + if (!server && !validate_only && is_useless_proxy) { + log_info(LD_GENERAL, + "Pluggable transport proxy (%s) does not provide " + "any needed transports and will not be launched.", + line); + } + + /* + * If we are not just validating, use the rest of the line as the + * argv of the proxy to be launched. Also, make sure that we are + * only launching proxies that contribute useful transports. + */ + + if (!validate_only && (server || !is_useless_proxy)) { + proxy_argc = line_length - 2; tor_assert(proxy_argc > 0); - proxy_argv = tor_malloc_zero(sizeof(char*)*(proxy_argc+1)); + proxy_argv = tor_calloc((proxy_argc + 1), sizeof(char *)); tmp = proxy_argv; - for (i=0;i<proxy_argc;i++) { /* store arguments */ + + for (i = 0; i < proxy_argc; i++) { + /* store arguments */ *tmp++ = smartlist_get(items, 2); smartlist_del_keeporder(items, 2); } - *tmp = NULL; /*terminated with NULL, just like execve() likes it*/ + *tmp = NULL; /* terminated with NULL, just like execve() likes it */ /* kickstart the thing */ - pt_kickstart_client_proxy(transport_list, proxy_argv); + if (server) { + pt_kickstart_server_proxy(transport_list, proxy_argv); + } else { + pt_kickstart_client_proxy(transport_list, proxy_argv); + } } - } else { /* external */ + } else { + /* external */ + + /* ClientTransportPlugins connecting through a proxy is managed only. */ + if (!server && (options->Socks4Proxy || options->Socks5Proxy || + options->HTTPSProxy)) { + log_warn(LD_CONFIG, "You have configured an external proxy with another " + "proxy type. (Socks4Proxy|Socks5Proxy|HTTPSProxy)"); + goto err; + } + if (smartlist_len(transport_list) != 1) { - log_warn(LD_CONFIG, "You can't have an external proxy with " - "more than one transports."); + log_warn(LD_CONFIG, + "You can't have an external proxy with more than " + "one transport."); goto err; } addrport = smartlist_get(items, 2); - if (tor_addr_port_lookup(addrport, &addr, &port)<0) { - log_warn(LD_CONFIG, "Error parsing transport " - "address '%s'", addrport); + if (tor_addr_port_lookup(addrport, &addr, &port) < 0) { + log_warn(LD_CONFIG, + "Error parsing transport address '%s'", addrport); goto err; } + if (!port) { log_warn(LD_CONFIG, "Transport address '%s' has no port.", addrport); @@ -4325,11 +5141,15 @@ parse_client_transport_line(const char *line, int validate_only) } if (!validate_only) { - transport_add_from_config(&addr, port, smartlist_get(transport_list, 0), - socks_ver); - - log_info(LD_DIR, "Transport '%s' found at %s", + log_info(LD_DIR, "%s '%s' at %s.", + server ? "Server transport" : "Transport", transports, fmt_addrport(&addr, port)); + + if (!server) { + transport_add_from_config(&addr, port, + smartlist_get(transport_list, 0), + socks_ver); + } } } @@ -4385,7 +5205,7 @@ get_bindaddr_from_transport_listen_line(const char *line,const char *transport) goto err; /* Validate addrport */ - if (tor_addr_port_parse(LOG_WARN, addrport, &addr, &port)<0) { + if (tor_addr_port_parse(LOG_WARN, addrport, &addr, &port, -1)<0) { log_warn(LD_CONFIG, "Error parsing ServerTransportListenAddr " "address '%s'", addrport); goto err; @@ -4404,151 +5224,109 @@ get_bindaddr_from_transport_listen_line(const char *line,const char *transport) return addrport; } -/** Given the name of a pluggable transport in <b>transport</b>, check - * the configuration file to see if the user has explicitly asked for - * it to listen on a specific port. Return a <address:port> string if - * so, otherwise NULL. */ -char * -get_transport_bindaddr_from_config(const char *transport) -{ - config_line_t *cl; - const or_options_t *options = get_options(); - - for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) { - char *bindaddr = - get_bindaddr_from_transport_listen_line(cl->value, transport); - if (bindaddr) - return bindaddr; - } - - return NULL; -} - -/** Read the contents of a ServerTransportPlugin line from - * <b>line</b>. Return 0 if the line is well-formed, and -1 if it - * isn't. - * If <b>validate_only</b> is 0, the line is well-formed, and it's a - * managed proxy line, launch the managed proxy. */ -static int -parse_server_transport_line(const char *line, int validate_only) +/** Given a ServerTransportOptions <b>line</b>, return a smartlist + * with the options. Return NULL if the line was not well-formed. + * + * If <b>transport</b> is set, return NULL if the line is not + * referring to <b>transport</b>. + * + * The returned smartlist and its strings are allocated on the heap + * and it's the responsibility of the caller to free it. */ +smartlist_t * +get_options_from_transport_options_line(const char *line,const char *transport) { - smartlist_t *items = NULL; - int r; - const char *transports=NULL; - smartlist_t *transport_list=NULL; - char *type=NULL; - char *addrport=NULL; - tor_addr_t addr; - uint16_t port = 0; - - /* managed proxy options */ - int is_managed=0; - char **proxy_argv=NULL; - char **tmp=NULL; - int proxy_argc,i; - - int line_length; + smartlist_t *items = smartlist_new(); + smartlist_t *options = smartlist_new(); + const char *parsed_transport = NULL; - items = smartlist_new(); smartlist_split_string(items, line, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); - line_length = smartlist_len(items); - if (line_length < 3) { - log_warn(LD_CONFIG, "Too few arguments on ServerTransportPlugin line."); + if (smartlist_len(items) < 2) { + log_warn(LD_CONFIG,"Too few arguments on ServerTransportOptions line."); goto err; } - /* Get the first line element, split it to commas into - transport_list (in case it's multiple transports) and validate - the transport names. */ - transports = smartlist_get(items, 0); - transport_list = smartlist_new(); - smartlist_split_string(transport_list, transports, ",", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport_name) { - if (!string_is_C_identifier(transport_name)) { - log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", - transport_name); - goto err; - } - } SMARTLIST_FOREACH_END(transport_name); - - type = smartlist_get(items, 1); - - if (!strcmp(type, "exec")) { - is_managed=1; - } else if (!strcmp(type, "proxy")) { - is_managed=0; - } else { - log_warn(LD_CONFIG, "Strange ServerTransportPlugin type '%s'", type); + parsed_transport = smartlist_get(items, 0); + /* If 'transport' is given, check if it matches the one on the line */ + if (transport && strcmp(transport, parsed_transport)) goto err; - } - - if (is_managed) { /* managed */ - if (!validate_only) { - proxy_argc = line_length-2; - tor_assert(proxy_argc > 0); - proxy_argv = tor_malloc_zero(sizeof(char*)*(proxy_argc+1)); - tmp = proxy_argv; - - for (i=0;i<proxy_argc;i++) { /* store arguments */ - *tmp++ = smartlist_get(items, 2); - smartlist_del_keeporder(items, 2); - } - *tmp = NULL; /*terminated with NULL, just like execve() likes it*/ - - /* kickstart the thing */ - pt_kickstart_server_proxy(transport_list, proxy_argv); - } - } else { /* external */ - if (smartlist_len(transport_list) != 1) { - log_warn(LD_CONFIG, "You can't have an external proxy with " - "more than one transports."); - goto err; - } - addrport = smartlist_get(items, 2); + SMARTLIST_FOREACH_BEGIN(items, const char *, option) { + if (option_sl_idx == 0) /* skip the transport field (first field)*/ + continue; - if (tor_addr_port_lookup(addrport, &addr, &port)<0) { - log_warn(LD_CONFIG, "Error parsing transport " - "address '%s'", addrport); - goto err; - } - if (!port) { - log_warn(LD_CONFIG, - "Transport address '%s' has no port.", addrport); + /* validate that it's a k=v value */ + if (!string_is_key_value(LOG_WARN, option)) { + log_warn(LD_CONFIG, "%s is not a k=v value.", escaped(option)); goto err; } - if (!validate_only) { - log_info(LD_DIR, "Server transport '%s' at %s.", - transports, fmt_addrport(&addr, port)); - } - } + /* add it to the options smartlist */ + smartlist_add(options, tor_strdup(option)); + log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option)); + } SMARTLIST_FOREACH_END(option); - r = 0; goto done; err: - r = -1; + SMARTLIST_FOREACH(options, char*, s, tor_free(s)); + smartlist_free(options); + options = NULL; done: SMARTLIST_FOREACH(items, char*, s, tor_free(s)); smartlist_free(items); - if (transport_list) { - SMARTLIST_FOREACH(transport_list, char*, s, tor_free(s)); - smartlist_free(transport_list); + + return options; +} + +/** Given the name of a pluggable transport in <b>transport</b>, check + * the configuration file to see if the user has explicitly asked for + * it to listen on a specific port. Return a <address:port> string if + * so, otherwise NULL. */ +char * +get_transport_bindaddr_from_config(const char *transport) +{ + config_line_t *cl; + const or_options_t *options = get_options(); + + for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) { + char *bindaddr = + get_bindaddr_from_transport_listen_line(cl->value, transport); + if (bindaddr) + return bindaddr; } - return r; + return NULL; +} + +/** Given the name of a pluggable transport in <b>transport</b>, check + * the configuration file to see if the user has asked us to pass any + * parameters to the pluggable transport. Return a smartlist + * containing the parameters, otherwise NULL. */ +smartlist_t * +get_options_for_server_transport(const char *transport) +{ + config_line_t *cl; + const or_options_t *options = get_options(); + + for (cl = options->ServerTransportOptions; cl; cl = cl->next) { + smartlist_t *options_sl = + get_options_from_transport_options_line(cl->value, transport); + if (options_sl) + return options_sl; + } + + return NULL; } /** Read the contents of a DirAuthority line from <b>line</b>. If * <b>validate_only</b> is 0, and the line is well-formed, and it * shares any bits with <b>required_type</b> or <b>required_type</b> - * is 0, then add the dirserver described in the line (minus whatever - * bits it's missing) as a valid authority. Return 0 on success, + * is NO_DIRINFO (zero), then add the dirserver described in the line + * (minus whatever bits it's missing) as a valid authority. + * Return 0 on success or filtering out by type, * or -1 if the line isn't well-formed or if we can't add it. */ static int parse_dir_authority_line(const char *line, dirinfo_type_t required_type, @@ -4560,8 +5338,7 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, uint16_t dir_port = 0, or_port = 0; char digest[DIGEST_LEN]; char v3_digest[DIGEST_LEN]; - dirinfo_type_t type = V2_DIRINFO; - int is_not_hidserv_authority = 0, is_not_v2_authority = 0; + dirinfo_type_t type = 0; double weight = 1.0; items = smartlist_new(); @@ -4581,16 +5358,15 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, char *flag = smartlist_get(items, 0); if (TOR_ISDIGIT(flag[0])) break; - if (!strcasecmp(flag, "v1")) { - type |= (V1_DIRINFO | HIDSERV_DIRINFO); - } else if (!strcasecmp(flag, "hs")) { - type |= HIDSERV_DIRINFO; - } else if (!strcasecmp(flag, "no-hs")) { - is_not_hidserv_authority = 1; + if (!strcasecmp(flag, "hs") || + !strcasecmp(flag, "no-hs")) { + log_warn(LD_CONFIG, "The DirAuthority options 'hs' and 'no-hs' are " + "obsolete; you don't need them any more."); } else if (!strcasecmp(flag, "bridge")) { type |= BRIDGE_DIRINFO; } else if (!strcasecmp(flag, "no-v2")) { - is_not_v2_authority = 1; + /* obsolete, but may still be contained in DirAuthority lines generated + by various tools */; } else if (!strcasecmpstart(flag, "orport=")) { int ok; char *portstring = flag + strlen("orport="); @@ -4622,10 +5398,6 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, tor_free(flag); smartlist_del_keeporder(items, 0); } - if (is_not_hidserv_authority) - type &= ~HIDSERV_DIRINFO; - if (is_not_v2_authority) - type &= ~V2_DIRINFO; if (smartlist_len(items) < 2) { log_warn(LD_CONFIG, "Too few arguments to DirAuthority line."); @@ -4648,14 +5420,6 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, fingerprint, (int)strlen(fingerprint)); goto err; } - if (!strcmp(fingerprint, "E623F7625FBE0C87820F11EC5F6D5377ED816294")) { - /* a known bad fingerprint. refuse to use it. We can remove this - * clause once Tor 0.1.2.17 is obsolete. */ - log_warn(LD_CONFIG, "Dangerous dirserver line. To correct, erase your " - "torrc file (%s), or reinstall Tor and use the default torrc.", - get_torrc_fname(0)); - goto err; - } if (base16_decode(digest, DIGEST_LEN, fingerprint, HEX_DIGEST_LEN)<0) { log_warn(LD_CONFIG, "Unable to decode DirAuthority key digest."); goto err; @@ -4785,12 +5549,13 @@ parse_dir_fallback_line(const char *line, /** Allocate and return a new port_cfg_t with reasonable defaults. */ static port_cfg_t * -port_cfg_new(void) +port_cfg_new(size_t namelen) { - port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); - cfg->ipv4_traffic = 1; - cfg->cache_ipv4_answers = 1; - cfg->prefer_ipv6_virtaddr = 1; + tor_assert(namelen <= SIZE_T_CEILING - sizeof(port_cfg_t) - 1); + port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t) + namelen + 1); + cfg->entry_cfg.ipv4_traffic = 1; + cfg->entry_cfg.cache_ipv4_answers = 1; + cfg->entry_cfg.prefer_ipv6_virtaddr = 1; return cfg; } @@ -4828,6 +5593,27 @@ warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname, } SMARTLIST_FOREACH_END(port); } +/** Warn for every Extended ORPort port in <b>ports</b> that is on a + * publicly routable address. */ +static void +warn_nonlocal_ext_orports(const smartlist_t *ports, const char *portname) +{ + SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { + if (port->type != CONN_TYPE_EXT_OR_LISTENER) + continue; + if (port->is_unix_addr) + continue; + /* XXX maybe warn even if address is RFC1918? */ + if (!tor_addr_is_internal(&port->addr, 1)) { + log_warn(LD_CONFIG, "You specified a public address '%s' for %sPort. " + "This is not advised; this address is supposed to only be " + "exposed on localhost so that your pluggable transport " + "proxies can connect to it.", + fmt_addrport(&port->addr, port->port), portname); + } + } SMARTLIST_FOREACH_END(port); +} + /** Given a list of port_cfg_t in <b>ports</b>, warn any controller port there * is listening on any non-loopback address. If <b>forbid</b> is true, * then emit a stronger warning and remove the port from the list. @@ -4876,6 +5662,56 @@ warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid) #define CL_PORT_SERVER_OPTIONS (1u<<3) #define CL_PORT_FORBID_NONLOCAL (1u<<4) #define CL_PORT_TAKES_HOSTNAMES (1u<<5) +#define CL_PORT_IS_UNIXSOCKET (1u<<6) + +#ifdef HAVE_SYS_UN_H + +/** Parse the given <b>addrport</b> and set <b>path_out</b> if a Unix socket + * path is found. Return 0 on success. On error, a negative value is + * returned, -ENOENT if no Unix statement found, -EINVAL if the socket path + * is empty and -ENOSYS if AF_UNIX is not supported (see function in the + * #else statement below). */ + +int +config_parse_unix_port(const char *addrport, char **path_out) +{ + tor_assert(path_out); + tor_assert(addrport); + + if (strcmpstart(addrport, unix_socket_prefix)) { + /* Not a Unix socket path. */ + return -ENOENT; + } + + if (strlen(addrport + strlen(unix_socket_prefix)) == 0) { + /* Empty socket path, not very usable. */ + return -EINVAL; + } + + *path_out = tor_strdup(addrport + strlen(unix_socket_prefix)); + return 0; +} + +#else /* defined(HAVE_SYS_UN_H) */ + +int +config_parse_unix_port(const char *addrport, char **path_out) +{ + tor_assert(path_out); + tor_assert(addrport); + + if (strcmpstart(addrport, unix_socket_prefix)) { + /* Not a Unix socket path. */ + return -ENOENT; + } + + log_warn(LD_CONFIG, + "Port configuration %s is for an AF_UNIX socket, but we have no" + "support available on this platform", + escaped(addrport)); + return -ENOSYS; +} +#endif /* defined(HAVE_SYS_UN_H) */ /** * Parse port configuration for a single port type. @@ -4923,11 +5759,12 @@ parse_port_config(smartlist_t *out, int listener_type, const char *defaultaddr, int defaultport, - unsigned flags) + const unsigned flags) { smartlist_t *elts; int retval = -1; const unsigned is_control = (listener_type == CONN_TYPE_CONTROL_LISTENER); + const unsigned is_ext_orport = (listener_type == CONN_TYPE_EXT_OR_LISTENER); const unsigned allow_no_options = flags & CL_PORT_NO_OPTIONS; const unsigned use_server_options = flags & CL_PORT_SERVER_OPTIONS; const unsigned warn_nonlocal = flags & CL_PORT_WARN_NONLOCAL; @@ -4935,7 +5772,9 @@ parse_port_config(smartlist_t *out, const unsigned allow_spurious_listenaddr = flags & CL_PORT_ALLOW_EXTRA_LISTENADDR; const unsigned takes_hostnames = flags & CL_PORT_TAKES_HOSTNAMES; + const unsigned is_unix_socket = flags & CL_PORT_IS_UNIXSOCKET; int got_zero_port=0, got_nonzero_port=0; + char *unix_socket_path = NULL; /* FooListenAddress is deprecated; let's make it work like it used to work, * though. */ @@ -4971,14 +5810,14 @@ parse_port_config(smartlist_t *out, if (use_server_options && out) { /* Add a no_listen port. */ - port_cfg_t *cfg = port_cfg_new(); + port_cfg_t *cfg = port_cfg_new(0); cfg->type = listener_type; cfg->port = mainport; tor_addr_make_unspec(&cfg->addr); /* Server ports default to 0.0.0.0 */ - cfg->no_listen = 1; - cfg->bind_ipv4_only = 1; - cfg->ipv4_traffic = 1; - cfg->prefer_ipv6_virtaddr = 1; + cfg->server_cfg.no_listen = 1; + cfg->server_cfg.bind_ipv4_only = 1; + cfg->entry_cfg.ipv4_traffic = 1; + cfg->entry_cfg.prefer_ipv6_virtaddr = 1; smartlist_add(out, cfg); } @@ -4991,13 +5830,13 @@ parse_port_config(smartlist_t *out, return -1; } if (out) { - port_cfg_t *cfg = port_cfg_new(); + port_cfg_t *cfg = port_cfg_new(0); cfg->type = listener_type; cfg->port = port ? port : mainport; tor_addr_copy(&cfg->addr, &addr); - cfg->session_group = SESSION_GROUP_UNSET; - cfg->isolation_flags = ISO_DEFAULT; - cfg->no_advertise = 1; + cfg->entry_cfg.session_group = SESSION_GROUP_UNSET; + cfg->entry_cfg.isolation_flags = ISO_DEFAULT; + cfg->server_cfg.no_advertise = 1; smartlist_add(out, cfg); } } @@ -5005,6 +5844,8 @@ parse_port_config(smartlist_t *out, if (warn_nonlocal && out) { if (is_control) warn_nonlocal_controller_ports(out, forbid_nonlocal); + else if (is_ext_orport) + warn_nonlocal_ext_orports(out, portname); else warn_nonlocal_client_ports(out, portname, listener_type); } @@ -5014,13 +5855,19 @@ parse_port_config(smartlist_t *out, /* No ListenAddress lines. If there's no FooPort, then maybe make a default * one. */ if (! ports) { - if (defaultport && out) { - port_cfg_t *cfg = port_cfg_new(); + if (defaultport && defaultaddr && out) { + port_cfg_t *cfg = port_cfg_new(is_unix_socket ? strlen(defaultaddr) : 0); cfg->type = listener_type; - cfg->port = defaultport; - tor_addr_parse(&cfg->addr, defaultaddr); - cfg->session_group = SESSION_GROUP_UNSET; - cfg->isolation_flags = ISO_DEFAULT; + if (is_unix_socket) { + tor_addr_make_unspec(&cfg->addr); + memcpy(cfg->unix_addr, defaultaddr, strlen(defaultaddr) + 1); + cfg->is_unix_addr = 1; + } else { + cfg->port = defaultport; + tor_addr_parse(&cfg->addr, defaultaddr); + } + cfg->entry_cfg.session_group = SESSION_GROUP_UNSET; + cfg->entry_cfg.isolation_flags = ISO_DEFAULT; smartlist_add(out, cfg); } return 0; @@ -5032,7 +5879,7 @@ parse_port_config(smartlist_t *out, for (; ports; ports = ports->next) { tor_addr_t addr; - int port; + int port, ret; int sessiongroup = SESSION_GROUP_UNSET; unsigned isolation = ISO_DEFAULT; int prefer_no_auth = 0; @@ -5061,7 +5908,31 @@ parse_port_config(smartlist_t *out, /* Now parse the addr/port value */ addrport = smartlist_get(elts, 0); - if (!strcmp(addrport, "auto")) { + + /* Let's start to check if it's a Unix socket path. */ + ret = config_parse_unix_port(addrport, &unix_socket_path); + if (ret < 0 && ret != -ENOENT) { + if (ret == -EINVAL) { + log_warn(LD_CONFIG, "Empty Unix socket path."); + } + goto err; + } + + if (unix_socket_path && + ! conn_listener_type_supports_af_unix(listener_type)) { + log_warn(LD_CONFIG, "%sPort does not support unix sockets", portname); + goto err; + } + + if (unix_socket_path) { + port = 1; + } else if (is_unix_socket) { + unix_socket_path = tor_strdup(addrport); + if (!strcmp(addrport, "0")) + port = 0; + else + port = 1; + } else if (!strcmp(addrport, "auto")) { port = CFG_AUTO_PORT; tor_addr_parse(&addr, defaultaddr); } else if (!strcasecmpend(addrport, ":auto")) { @@ -5246,28 +6117,36 @@ parse_port_config(smartlist_t *out, } if (out && port) { - port_cfg_t *cfg = port_cfg_new(); - tor_addr_copy(&cfg->addr, &addr); - cfg->port = port; + size_t namelen = unix_socket_path ? strlen(unix_socket_path) : 0; + port_cfg_t *cfg = port_cfg_new(namelen); + if (unix_socket_path) { + tor_addr_make_unspec(&cfg->addr); + memcpy(cfg->unix_addr, unix_socket_path, namelen + 1); + cfg->is_unix_addr = 1; + tor_free(unix_socket_path); + } else { + tor_addr_copy(&cfg->addr, &addr); + cfg->port = port; + } cfg->type = listener_type; - cfg->isolation_flags = isolation; - cfg->session_group = sessiongroup; - cfg->no_advertise = no_advertise; - cfg->no_listen = no_listen; - cfg->all_addrs = all_addrs; - cfg->bind_ipv4_only = bind_ipv4_only; - cfg->bind_ipv6_only = bind_ipv6_only; - cfg->ipv4_traffic = ipv4_traffic; - cfg->ipv6_traffic = ipv6_traffic; - cfg->prefer_ipv6 = prefer_ipv6; - cfg->cache_ipv4_answers = cache_ipv4; - cfg->cache_ipv6_answers = cache_ipv6; - cfg->use_cached_ipv4_answers = use_cached_ipv4; - cfg->use_cached_ipv6_answers = use_cached_ipv6; - cfg->prefer_ipv6_virtaddr = prefer_ipv6_automap; - cfg->socks_prefer_no_auth = prefer_no_auth; + cfg->entry_cfg.isolation_flags = isolation; + cfg->entry_cfg.session_group = sessiongroup; + cfg->server_cfg.no_advertise = no_advertise; + cfg->server_cfg.no_listen = no_listen; + cfg->server_cfg.all_addrs = all_addrs; + cfg->server_cfg.bind_ipv4_only = bind_ipv4_only; + cfg->server_cfg.bind_ipv6_only = bind_ipv6_only; + cfg->entry_cfg.ipv4_traffic = ipv4_traffic; + cfg->entry_cfg.ipv6_traffic = ipv6_traffic; + cfg->entry_cfg.prefer_ipv6 = prefer_ipv6; + cfg->entry_cfg.cache_ipv4_answers = cache_ipv4; + cfg->entry_cfg.cache_ipv6_answers = cache_ipv6; + cfg->entry_cfg.use_cached_ipv4_answers = use_cached_ipv4; + cfg->entry_cfg.use_cached_ipv6_answers = use_cached_ipv6; + cfg->entry_cfg.prefer_ipv6_virtaddr = prefer_ipv6_automap; + cfg->entry_cfg.socks_prefer_no_auth = prefer_no_auth; if (! (isolation & ISO_SOCKSAUTH)) - cfg->socks_prefer_no_auth = 1; + cfg->entry_cfg.socks_prefer_no_auth = 1; smartlist_add(out, cfg); } @@ -5278,6 +6157,8 @@ parse_port_config(smartlist_t *out, if (warn_nonlocal && out) { if (is_control) warn_nonlocal_controller_ports(out, forbid_nonlocal); + else if (is_ext_orport) + warn_nonlocal_ext_orports(out, portname); else warn_nonlocal_client_ports(out, portname, listener_type); } @@ -5293,32 +6174,10 @@ parse_port_config(smartlist_t *out, err: SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); smartlist_free(elts); + tor_free(unix_socket_path); return retval; } -/** Parse a list of config_line_t for an AF_UNIX unix socket listener option - * from <b>cfg</b> and add them to <b>out</b>. No fancy options are - * supported: the line contains nothing but the path to the AF_UNIX socket. */ -static int -parse_unix_socket_config(smartlist_t *out, const config_line_t *cfg, - int listener_type) -{ - - if (!out) - return 0; - - for ( ; cfg; cfg = cfg->next) { - size_t len = strlen(cfg->value); - port_cfg_t *port = tor_malloc_zero(sizeof(port_cfg_t) + len + 1); - port->is_unix_addr = 1; - memcpy(port->unix_addr, cfg->value, len+1); - port->type = listener_type; - smartlist_add(out, port); - } - - return 0; -} - /** Return the number of ports which are actually going to listen with type * <b>listenertype</b>. Do not count no_listen ports. Do not count unix * sockets. */ @@ -5327,7 +6186,7 @@ count_real_listeners(const smartlist_t *ports, int listenertype) { int n = 0; SMARTLIST_FOREACH_BEGIN(ports, port_cfg_t *, port) { - if (port->no_listen || port->is_unix_addr) + if (port->server_cfg.no_listen || port->is_unix_addr) continue; if (port->type != listenertype) continue; @@ -5407,9 +6266,11 @@ parse_ports(or_options_t *options, int validate_only, "configuration"); goto err; } - if (parse_unix_socket_config(ports, - options->ControlSocket, - CONN_TYPE_CONTROL_LISTENER) < 0) { + + if (parse_port_config(ports, options->ControlSocket, NULL, + "ControlSocket", + CONN_TYPE_CONTROL_LISTENER, NULL, 0, + control_port_flags | CL_PORT_IS_UNIXSOCKET) < 0) { *msg = tor_strdup("Invalid ControlSocket configuration"); goto err; } @@ -5424,6 +6285,14 @@ parse_ports(or_options_t *options, int validate_only, goto err; } if (parse_port_config(ports, + options->ExtORPort_lines, NULL, + "ExtOR", CONN_TYPE_EXT_OR_LISTENER, + "127.0.0.1", 0, + CL_PORT_SERVER_OPTIONS|CL_PORT_WARN_NONLOCAL) < 0) { + *msg = tor_strdup("Invalid ExtORPort configuration"); + goto err; + } + if (parse_port_config(ports, options->DirPort_lines, options->DirListenAddress, "Dir", CONN_TYPE_DIR_LISTENER, "0.0.0.0", 0, @@ -5458,6 +6327,8 @@ parse_ports(or_options_t *options, int validate_only, !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER); options->DNSPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_DNS_LISTENER); + options->ExtORPort_set = + !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER); if (!validate_only) { if (configured_ports) { @@ -5493,25 +6364,25 @@ check_server_ports(const smartlist_t *ports, SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { if (port->type == CONN_TYPE_DIR_LISTENER) { - if (! port->no_advertise) + if (! port->server_cfg.no_advertise) ++n_dirport_advertised; - if (! port->no_listen) + if (! port->server_cfg.no_listen) ++n_dirport_listeners; } else if (port->type == CONN_TYPE_OR_LISTENER) { - if (! port->no_advertise) { + if (! port->server_cfg.no_advertise) { ++n_orport_advertised; if (tor_addr_family(&port->addr) == AF_INET || (tor_addr_family(&port->addr) == AF_UNSPEC && - !port->bind_ipv6_only)) + !port->server_cfg.bind_ipv6_only)) ++n_orport_advertised_ipv4; } - if (! port->no_listen) + if (! port->server_cfg.no_listen) ++n_orport_listeners; } else { continue; } #ifndef _WIN32 - if (!port->no_listen && port->port < 1024) + if (!port->server_cfg.no_listen && port->port < 1024) ++n_low_port; #endif } SMARTLIST_FOREACH_END(port); @@ -5589,7 +6460,7 @@ get_first_listener_addrport_string(int listener_type) return NULL; SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) { - if (cfg->no_listen) + if (cfg->server_cfg.no_listen) continue; if (cfg->type == listener_type && @@ -5636,12 +6507,12 @@ get_first_advertised_port_by_type_af(int listener_type, int address_family) return 0; SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) { if (cfg->type == listener_type && - !cfg->no_advertise && + !cfg->server_cfg.no_advertise && (tor_addr_family(&cfg->addr) == address_family || tor_addr_family(&cfg->addr) == AF_UNSPEC)) { if (tor_addr_family(&cfg->addr) != AF_UNSPEC || - (address_family == AF_INET && !cfg->bind_ipv6_only) || - (address_family == AF_INET6 && !cfg->bind_ipv4_only)) { + (address_family == AF_INET && !cfg->server_cfg.bind_ipv6_only) || + (address_family == AF_INET6 && !cfg->server_cfg.bind_ipv4_only)) { return cfg->port; } } @@ -5725,10 +6596,13 @@ write_configuration_file(const char *fname, const or_options_t *options) char *old_val=NULL, *new_val=NULL, *new_conf=NULL; int rename_old = 0, r; - tor_assert(fname); + if (!fname) + return -1; switch (file_status(fname)) { + /* create backups of old config files, even if they're empty */ case FN_FILE: + case FN_EMPTY: old_val = read_file_to_str(fname, 0, NULL); if (!old_val || strcmpstart(old_val, GENERATED_FILE_PREFIX)) { rename_old = 1; @@ -5745,7 +6619,7 @@ write_configuration_file(const char *fname, const or_options_t *options) return -1; } - if (!(new_conf = options_dump(options, 1))) { + if (!(new_conf = options_dump(options, OPTIONS_DUMP_MINIMAL))) { log_warn(LD_BUG, "Couldn't get configuration string"); goto err; } @@ -5764,7 +6638,7 @@ write_configuration_file(const char *fname, const or_options_t *options) ++i; } log_notice(LD_CONFIG, "Renaming old configuration file to \"%s\"", fn_tmp); - if (rename(fname, fn_tmp) < 0) { + if (tor_rename(fname, fn_tmp) < 0) {//XXXX sandbox doesn't allow log_warn(LD_FS, "Couldn't rename configuration file \"%s\" to \"%s\": %s", fname, fn_tmp, strerror(errno)); @@ -5905,6 +6779,43 @@ options_get_datadir_fname2_suffix(const or_options_t *options, return fname; } +/** Check wether the data directory has a private subdirectory + * <b>subdir</b>. If not, try to create it. Return 0 on success, + * -1 otherwise. */ +int +check_or_create_data_subdir(const char *subdir) +{ + char *statsdir = get_datadir_fname(subdir); + int return_val = 0; + + if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { + log_warn(LD_HIST, "Unable to create %s/ directory!", subdir); + return_val = -1; + } + tor_free(statsdir); + return return_val; +} + +/** Create a file named <b>fname</b> with contents <b>str</b> in the + * subdirectory <b>subdir</b> of the data directory. <b>descr</b> + * should be a short description of the file's content and will be + * used for the warning message, if it's present and the write process + * fails. Return 0 on success, -1 otherwise.*/ +int +write_to_data_subdir(const char* subdir, const char* fname, + const char* str, const char* descr) +{ + char *filename = get_datadir_fname2(subdir, fname); + int return_val = 0; + + if (write_str_to_file(filename, str, 0) < 0) { + log_warn(LD_HIST, "Unable to write %s to disk!", descr ? descr : fname); + return_val = -1; + } + tor_free(filename); + return return_val; +} + /** Given a file name check to see whether the file exists but has not been * modified for a very long time. If so, remove it. */ void @@ -5913,12 +6824,17 @@ remove_file_if_very_old(const char *fname, time_t now) #define VERY_OLD_FILE_AGE (28*24*60*60) struct stat st; - if (stat(fname, &st)==0 && st.st_mtime < now-VERY_OLD_FILE_AGE) { + log_debug(LD_FS, "stat()ing %s", fname); + if (stat(sandbox_intern_string(fname), &st)==0 && + st.st_mtime < now-VERY_OLD_FILE_AGE) { char buf[ISO_TIME_LEN+1]; format_local_iso_time(buf, st.st_mtime); log_notice(LD_GENERAL, "Obsolete file %s hasn't been modified since %s. " "Removing it.", fname, buf); - unlink(fname); + if (unlink(fname) != 0) { + log_warn(LD_FS, "Failed to unlink %s: %s", + fname, strerror(errno)); + } } } @@ -5994,6 +6910,7 @@ getinfo_helper_config(control_connection_t *conn, case CONFIG_TYPE_ISOTIME: type = "Time"; break; case CONFIG_TYPE_ROUTERSET: type = "RouterList"; break; case CONFIG_TYPE_CSV: type = "CommaList"; break; + case CONFIG_TYPE_CSV_INTERVAL: type = "TimeIntervalCommaList"; break; case CONFIG_TYPE_LINELIST: type = "LineList"; break; case CONFIG_TYPE_LINELIST_S: type = "Dependant"; break; case CONFIG_TYPE_LINELIST_V: type = "Virtual"; break; @@ -6125,3 +7042,71 @@ config_maybe_load_geoip_files_(const or_options_t *options, config_load_geoip_file_(AF_INET6, options->GeoIPv6File, "geoip6"); } +/** Initialize cookie authentication (used so far by the ControlPort + * and Extended ORPort). + * + * Allocate memory and create a cookie (of length <b>cookie_len</b>) + * in <b>cookie_out</b>. + * Then write it down to <b>fname</b> and prepend it with <b>header</b>. + * + * If <b>group_readable</b> is set, set <b>fname</b> to be readable + * by the default GID. + * + * If the whole procedure was successful, set + * <b>cookie_is_set_out</b> to True. */ +int +init_cookie_authentication(const char *fname, const char *header, + int cookie_len, int group_readable, + uint8_t **cookie_out, int *cookie_is_set_out) +{ + char cookie_file_str_len = strlen(header) + cookie_len; + char *cookie_file_str = tor_malloc(cookie_file_str_len); + int retval = -1; + + /* We don't want to generate a new cookie every time we call + * options_act(). One should be enough. */ + if (*cookie_is_set_out) { + retval = 0; /* we are all set */ + goto done; + } + + /* If we've already set the cookie, free it before re-setting + it. This can happen if we previously generated a cookie, but + couldn't write it to a disk. */ + if (*cookie_out) + tor_free(*cookie_out); + + /* Generate the cookie */ + *cookie_out = tor_malloc(cookie_len); + if (crypto_rand((char *)*cookie_out, cookie_len) < 0) + goto done; + + /* Create the string that should be written on the file. */ + memcpy(cookie_file_str, header, strlen(header)); + memcpy(cookie_file_str+strlen(header), *cookie_out, cookie_len); + if (write_bytes_to_file(fname, cookie_file_str, cookie_file_str_len, 1)) { + log_warn(LD_FS,"Error writing auth cookie to %s.", escaped(fname)); + goto done; + } + +#ifndef _WIN32 + if (group_readable) { + if (chmod(fname, 0640)) { + log_warn(LD_FS,"Unable to make %s group-readable.", escaped(fname)); + } + } +#else + (void) group_readable; +#endif + + /* Success! */ + log_info(LD_GENERAL, "Generated auth cookie file in '%s'.", escaped(fname)); + *cookie_is_set_out = 1; + retval = 0; + + done: + memwipe(cookie_file_str, 0, cookie_file_str_len); + tor_free(cookie_file_str); + return retval; +} + diff --git a/src/or/config.h b/src/or/config.h index ef4acac514..b064f05321 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,8 +12,10 @@ #ifndef TOR_CONFIG_H #define TOR_CONFIG_H +#include "testsupport.h" + const char *get_dirportfrontpage(void); -const or_options_t *get_options(void); +MOCK_DECL(const or_options_t *,get_options,(void)); or_options_t *get_options_mutable(void); int set_options(or_options_t *new_val, char **msg); void config_free_all(void); @@ -27,12 +29,17 @@ setopt_err_t options_trial_assign(config_line_t *list, int use_defaults, int clear_first, char **msg); uint32_t get_last_resolved_addr(void); +void reset_last_resolved_addr(void); int resolve_my_address(int warn_severity, const or_options_t *options, uint32_t *addr_out, const char **method_out, char **hostname_out); -int is_local_addr(const tor_addr_t *addr); +MOCK_DECL(int, is_local_addr, (const tor_addr_t *addr)); void options_init(or_options_t *options); -char *options_dump(const or_options_t *options, int minimal); + +#define OPTIONS_DUMP_MINIMAL 1 +#define OPTIONS_DUMP_DEFAULTS 2 +#define OPTIONS_DUMP_ALL 3 +char *options_dump(const or_options_t *options, int how_to_dump); int options_init_from_torrc(int argc, char **argv); setopt_err_t options_init_from_string(const char *cf_defaults, const char *cf, int command, const char *command_arg, char **msg); @@ -59,6 +66,10 @@ char *options_get_datadir_fname2_suffix(const or_options_t *options, #define get_datadir_fname_suffix(sub1, suffix) \ get_datadir_fname2_suffix((sub1), NULL, (suffix)) +int check_or_create_data_subdir(const char *subdir); +int write_to_data_subdir(const char* subdir, const char* fname, + const char* str, const char* descr); + int get_num_cpus(const or_options_t *options); const smartlist_t *get_configured_ports(void); @@ -86,10 +97,15 @@ uint32_t get_effective_bwburst(const or_options_t *options); char *get_transport_bindaddr_from_config(const char *transport); -#ifdef CONFIG_PRIVATE -/* Used only by config.c and test.c */ +int init_cookie_authentication(const char *fname, const char *header, + int cookie_len, int group_readable, + uint8_t **cookie_out, int *cookie_is_set_out); + or_options_t *options_new(void); -#endif + +int config_parse_commandline(int argc, char **argv, int ignore_errors, + config_line_t **result, + config_line_t **cmdline_result); void config_register_addressmaps(const or_options_t *options); /* XXXX024 move to connection_edge.h */ @@ -97,6 +113,39 @@ int addressmap_register_auto(const char *from, const char *to, time_t expires, addressmap_entry_source_t addrmap_source, const char **msg); +int config_parse_unix_port(const char *addrport, char **path_out); + +/** Represents the information stored in a torrc Bridge line. */ +typedef struct bridge_line_t { + tor_addr_t addr; /* The IP address of the bridge. */ + uint16_t port; /* The TCP port of the bridge. */ + char *transport_name; /* The name of the pluggable transport that + should be used to connect to the bridge. */ + char digest[DIGEST_LEN]; /* The bridge's identity key digest. */ + smartlist_t *socks_args; /* SOCKS arguments for the pluggable + transport proxy. */ +} bridge_line_t; + +void bridge_line_free(bridge_line_t *bridge_line); +bridge_line_t *parse_bridge_line(const char *line); +smartlist_t *get_options_from_transport_options_line(const char *line, + const char *transport); +smartlist_t *get_options_for_server_transport(const char *transport); + +#ifdef CONFIG_PRIVATE +#ifdef TOR_UNIT_TESTS +extern struct config_format_t options_format; +#endif + +STATIC void or_options_free(or_options_t *options); +STATIC int options_validate(or_options_t *old_options, + or_options_t *options, + or_options_t *default_options, + int from_setconf, char **msg); +STATIC int parse_transport_line(const or_options_t *options, + const char *line, int validate_only, + int server); +#endif #endif diff --git a/src/or/confparse.c b/src/or/confparse.c index 8863d92409..ac21df25cb 100644 --- a/src/or/confparse.c +++ b/src/or/confparse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" @@ -79,6 +79,21 @@ config_line_append(config_line_t **lst, (*lst) = newline; } +/** Return the line in <b>lines</b> whose key is exactly <b>key</b>, or NULL + * if no such key exists. For handling commandline-only options only; other + * options should be looked up in the appropriate data structure. */ +const config_line_t * +config_line_find(const config_line_t *lines, + const char *key) +{ + const config_line_t *cl; + for (cl = lines; cl; cl = cl->next) { + if (!strcmp(cl->key, key)) + return cl; + } + return NULL; +} + /** Helper: parse the config string and strdup into key/value * strings. Set *result to the list, or NULL if parsing the string * failed. Return 0 on success, -1 on failure. Warn and ignore any @@ -223,6 +238,8 @@ config_assign_value(const config_format_t *fmt, void *options, int i, ok; const config_var_t *var; void *lvalue; + int *csv_int; + smartlist_t *csv_str; CONFIG_CHECK(fmt, options); @@ -357,6 +374,36 @@ config_assign_value(const config_format_t *fmt, void *options, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); break; + case CONFIG_TYPE_CSV_INTERVAL: + if (*(smartlist_t**)lvalue) { + SMARTLIST_FOREACH(*(smartlist_t**)lvalue, int *, cp, tor_free(cp)); + smartlist_clear(*(smartlist_t**)lvalue); + } else { + *(smartlist_t**)lvalue = smartlist_new(); + } + csv_str = smartlist_new(); + smartlist_split_string(csv_str, c->value, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(csv_str, char *, str) + { + i = config_parse_interval(str, &ok); + if (!ok) { + tor_asprintf(msg, + "Interval in '%s %s' is malformed or out of bounds.", + c->key, c->value); + SMARTLIST_FOREACH(csv_str, char *, cp, tor_free(cp)); + smartlist_free(csv_str); + return -1; + } + csv_int = tor_malloc_zero(sizeof(int)); + *csv_int = i; + smartlist_add(*(smartlist_t**)lvalue, csv_int); + } + SMARTLIST_FOREACH_END(str); + SMARTLIST_FOREACH(csv_str, char *, cp, tor_free(cp)); + smartlist_free(csv_str); + break; + case CONFIG_TYPE_LINELIST: case CONFIG_TYPE_LINELIST_S: { @@ -555,6 +602,7 @@ config_get_assigned_option(const config_format_t *fmt, const void *options, const config_var_t *var; const void *value; config_line_t *result; + smartlist_t *csv_str; tor_assert(options && key); CONFIG_CHECK(fmt, options); @@ -637,6 +685,20 @@ config_get_assigned_option(const config_format_t *fmt, const void *options, else result->value = tor_strdup(""); break; + case CONFIG_TYPE_CSV_INTERVAL: + if (*(smartlist_t**)value) { + csv_str = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(*(smartlist_t**)value, int *, i) + { + smartlist_add_asprintf(csv_str, "%d", *i); + } + SMARTLIST_FOREACH_END(i); + result->value = smartlist_join_strings(csv_str, ",", 0, NULL); + SMARTLIST_FOREACH(csv_str, char *, cp, tor_free(cp)); + smartlist_free(csv_str); + } else + result->value = tor_strdup(""); + break; case CONFIG_TYPE_OBSOLETE: log_fn(LOG_INFO, LD_CONFIG, "You asked me for the value of an obsolete config option '%s'.", @@ -826,6 +888,13 @@ config_clear(const config_format_t *fmt, void *options, *(smartlist_t **)lvalue = NULL; } break; + case CONFIG_TYPE_CSV_INTERVAL: + if (*(smartlist_t**)lvalue) { + SMARTLIST_FOREACH(*(smartlist_t **)lvalue, int *, cp, tor_free(cp)); + smartlist_free(*(smartlist_t **)lvalue); + *(smartlist_t **)lvalue = NULL; + } + break; case CONFIG_TYPE_LINELIST: case CONFIG_TYPE_LINELIST_S: config_free_lines(*(config_line_t **)lvalue); @@ -1005,8 +1074,8 @@ config_dump(const config_format_t *fmt, const void *default_options, /* XXX use a 1 here so we don't add a new log line while dumping */ if (default_options == NULL) { - if (fmt->validate_fn(NULL, defaults_tmp, 1, &msg) < 0) { - log_err(LD_BUG, "Failed to validate default config."); + if (fmt->validate_fn(NULL, defaults_tmp, defaults_tmp, 1, &msg) < 0) { + log_err(LD_BUG, "Failed to validate default config: %s", msg); tor_free(msg); tor_assert(0); } @@ -1072,20 +1141,36 @@ static struct unit_table_t memory_units[] = { { "kbytes", 1<<10 }, { "kilobyte", 1<<10 }, { "kilobytes", 1<<10 }, + { "kilobits", 1<<7 }, + { "kilobit", 1<<7 }, + { "kbits", 1<<7 }, + { "kbit", 1<<7 }, { "m", 1<<20 }, { "mb", 1<<20 }, { "mbyte", 1<<20 }, { "mbytes", 1<<20 }, { "megabyte", 1<<20 }, { "megabytes", 1<<20 }, + { "megabits", 1<<17 }, + { "megabit", 1<<17 }, + { "mbits", 1<<17 }, + { "mbit", 1<<17 }, { "gb", 1<<30 }, { "gbyte", 1<<30 }, { "gbytes", 1<<30 }, { "gigabyte", 1<<30 }, { "gigabytes", 1<<30 }, + { "gigabits", 1<<27 }, + { "gigabit", 1<<27 }, + { "gbits", 1<<27 }, + { "gbit", 1<<27 }, { "tb", U64_LITERAL(1)<<40 }, { "terabyte", U64_LITERAL(1)<<40 }, { "terabytes", U64_LITERAL(1)<<40 }, + { "terabits", U64_LITERAL(1)<<37 }, + { "terabit", U64_LITERAL(1)<<37 }, + { "tbits", U64_LITERAL(1)<<37 }, + { "tbit", U64_LITERAL(1)<<37 }, { NULL, 0 }, }; diff --git a/src/or/confparse.h b/src/or/confparse.h index 1b987f3bf9..83c0f75b52 100644 --- a/src/or/confparse.h +++ b/src/or/confparse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_CONFPARSE_H @@ -26,6 +26,9 @@ typedef enum config_type_t { CONFIG_TYPE_ISOTIME, /**< An ISO-formatted time relative to UTC. */ CONFIG_TYPE_CSV, /**< A list of strings, separated by commas and * optional whitespace. */ + CONFIG_TYPE_CSV_INTERVAL, /**< A list of strings, separated by commas and + * optional whitespace, representing intervals in + * seconds, with optional units */ CONFIG_TYPE_LINELIST, /**< Uninterpreted config lines */ CONFIG_TYPE_LINELIST_S, /**< Uninterpreted, context-sensitive config lines, * mixed with other keywords. */ @@ -68,12 +71,12 @@ typedef struct config_var_description_t { /** Type of a callback to validate whether a given configuration is * well-formed and consistent. See options_trial_assign() for documentation * of arguments. */ -typedef int (*validate_fn_t)(void*,void*,int,char**); +typedef int (*validate_fn_t)(void*,void*,void*,int,char**); /** Information on the keys, value types, key-to-struct-member mappings, * variable descriptions, validation functions, and abbreviations for a * configuration or storage format. */ -typedef struct { +typedef struct config_format_t { size_t size; /**< Size of the struct that everything gets parsed into. */ uint32_t magic; /**< Required 'magic value' to make sure we have a struct * of the right type. */ @@ -100,6 +103,8 @@ void *config_new(const config_format_t *fmt); void config_line_append(config_line_t **lst, const char *key, const char *val); config_line_t *config_lines_dup(const config_line_t *inp); +const config_line_t *config_line_find(const config_line_t *lines, + const char *key); void config_free(const config_format_t *fmt, void *options); int config_lines_eq(config_line_t *a, config_line_t *b); int config_count_key(const config_line_t *a, const char *key); diff --git a/src/or/connection.c b/src/or/connection.c index 4f74a1d04b..721ee20d27 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,6 +10,7 @@ * on connections. **/ +#define CONNECTION_PRIVATE #include "or.h" #include "buffers.h" /* @@ -17,6 +18,7 @@ * part of a subclass (channel_tls_t). */ #define TOR_CHANNEL_INTERNAL_ +#define CONNECTION_PRIVATE #include "channel.h" #include "channeltls.h" #include "circuitbuild.h" @@ -27,12 +29,12 @@ #include "connection_edge.h" #include "connection_or.h" #include "control.h" -#include "cpuworker.h" #include "directory.h" #include "dirserv.h" #include "dns.h" #include "dnsserv.h" #include "entrynodes.h" +#include "ext_orport.h" #include "geoip.h" #include "main.h" #include "policies.h" @@ -44,6 +46,7 @@ #include "router.h" #include "transports.h" #include "routerparse.h" +#include "transports.h" #ifdef USE_BUFFEREVENTS #include <event2/event.h> @@ -53,6 +56,11 @@ #include <pwd.h> #endif +#ifdef HAVE_SYS_UN_H +#include <sys/socket.h> +#include <sys/un.h> +#endif + static connection_t *connection_listener_new( const struct sockaddr *listensockaddr, socklen_t listensocklen, int type, @@ -97,6 +105,7 @@ static smartlist_t *outgoing_addrs = NULL; #define CASE_ANY_LISTENER_TYPE \ case CONN_TYPE_OR_LISTENER: \ + case CONN_TYPE_EXT_OR_LISTENER: \ case CONN_TYPE_AP_LISTENER: \ case CONN_TYPE_DIR_LISTENER: \ case CONN_TYPE_CONTROL_LISTENER: \ @@ -125,9 +134,10 @@ conn_type_to_string(int type) case CONN_TYPE_AP: return "Socks"; case CONN_TYPE_DIR_LISTENER: return "Directory listener"; case CONN_TYPE_DIR: return "Directory"; - case CONN_TYPE_CPUWORKER: return "CPU worker"; case CONN_TYPE_CONTROL_LISTENER: return "Control listener"; case CONN_TYPE_CONTROL: return "Control"; + case CONN_TYPE_EXT_OR: return "Extended OR"; + case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener"; default: log_warn(LD_BUG, "unknown connection type %d", type); tor_snprintf(buf, sizeof(buf), "unknown [%d]", type); @@ -164,6 +174,18 @@ conn_state_to_string(int type, int state) case OR_CONN_STATE_OPEN: return "open"; } break; + case CONN_TYPE_EXT_OR: + switch (state) { + case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: + return "waiting for authentication type"; + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: + return "waiting for client nonce"; + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: + return "waiting for client hash"; + case EXT_OR_CONN_STATE_OPEN: return "open"; + case EXT_OR_CONN_STATE_FLUSHING: return "flushing final OKAY"; + } + break; case CONN_TYPE_EXIT: switch (state) { case EXIT_CONN_STATE_RESOLVING: return "waiting for dest info"; @@ -194,12 +216,6 @@ conn_state_to_string(int type, int state) case DIR_CONN_STATE_SERVER_WRITING: return "writing"; } break; - case CONN_TYPE_CPUWORKER: - switch (state) { - case CPUWORKER_STATE_IDLE: return "idle"; - case CPUWORKER_STATE_BUSY_ONION: return "busy with onion"; - } - break; case CONN_TYPE_CONTROL: switch (state) { case CONTROL_CONN_STATE_OPEN: return "open (protocol v1)"; @@ -228,7 +244,7 @@ connection_type_uses_bufferevent(connection_t *conn) case CONN_TYPE_DIR: case CONN_TYPE_CONTROL: case CONN_TYPE_OR: - case CONN_TYPE_CPUWORKER: + case CONN_TYPE_EXT_OR: return 1; default: return 0; @@ -249,22 +265,22 @@ dir_connection_new(int socket_family) /** Allocate and return a new or_connection_t, initialized as by * connection_init(). * - * Set timestamp_last_added_nonpadding to now. - * - * Assign a pseudorandom next_circ_id between 0 and 2**15. - * * Initialize active_circuit_pqueue. * * Set active_circuit_pqueue_last_recalibrated to current cell_ewma tick. */ or_connection_t * -or_connection_new(int socket_family) +or_connection_new(int type, int socket_family) { or_connection_t *or_conn = tor_malloc_zero(sizeof(or_connection_t)); time_t now = time(NULL); - connection_init(now, TO_CONN(or_conn), CONN_TYPE_OR, socket_family); + tor_assert(type == CONN_TYPE_OR || type == CONN_TYPE_EXT_OR); + connection_init(now, TO_CONN(or_conn), type, socket_family); - or_conn->timestamp_last_added_nonpadding = time(NULL); + connection_or_set_canonical(or_conn, 0); + + if (type == CONN_TYPE_EXT_OR) + connection_or_set_ext_or_identifier(or_conn); return or_conn; } @@ -285,9 +301,11 @@ entry_connection_new(int type, int socket_family) * in a little while. Otherwise, we're doing this as a linked connection * of some kind, and we should set it up here based on the socket family */ if (socket_family == AF_INET) - entry_conn->ipv4_traffic_ok = 1; + entry_conn->entry_cfg.ipv4_traffic = 1; else if (socket_family == AF_INET6) - entry_conn->ipv6_traffic_ok = 1; + entry_conn->entry_cfg.ipv6_traffic = 1; + else if (socket_family == AF_UNIX) + entry_conn->is_socks_socket = 1; return entry_conn; } @@ -311,7 +329,6 @@ control_connection_new(int socket_family) tor_malloc_zero(sizeof(control_connection_t)); connection_init(time(NULL), TO_CONN(control_conn), CONN_TYPE_CONTROL, socket_family); - log_notice(LD_CONTROL, "New control connection opened."); return control_conn; } @@ -334,7 +351,8 @@ connection_new(int type, int socket_family) { switch (type) { case CONN_TYPE_OR: - return TO_CONN(or_connection_new(socket_family)); + case CONN_TYPE_EXT_OR: + return TO_CONN(or_connection_new(type, socket_family)); case CONN_TYPE_EXIT: return TO_CONN(edge_connection_new(type, socket_family)); @@ -376,6 +394,7 @@ connection_init(time_t now, connection_t *conn, int type, int socket_family) switch (type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: conn->magic = OR_CONNECTION_MAGIC; break; case CONN_TYPE_EXIT: @@ -430,11 +449,27 @@ connection_link_connections(connection_t *conn_a, connection_t *conn_b) conn_b->linked_conn = conn_a; } +/** Return true iff the provided connection listener type supports AF_UNIX + * sockets. */ +int +conn_listener_type_supports_af_unix(int type) +{ + /* For now only control ports or SOCKS ports can be Unix domain sockets + * and listeners at the same time */ + switch (type) { + case CONN_TYPE_CONTROL_LISTENER: + case CONN_TYPE_AP_LISTENER: + return 1; + default: + return 0; + } +} + /** Deallocate memory used by <b>conn</b>. Deallocate its buffers if * necessary, close its socket if necessary, and mark the directory as dirty * if <b>conn</b> is an OR or OP connection. */ -static void +STATIC void connection_free_(connection_t *conn) { void *mem; @@ -444,6 +479,7 @@ connection_free_(connection_t *conn) switch (conn->type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: tor_assert(conn->magic == OR_CONNECTION_MAGIC); mem = TO_OR_CONN(conn); memlen = sizeof(or_connection_t); @@ -494,9 +530,9 @@ connection_free_(connection_t *conn) buf_free(conn->outbuf); } else { if (conn->socket_family == AF_UNIX) { - /* For now only control ports can be Unix domain sockets + /* For now only control and SOCKS ports can be Unix domain sockets * and listeners at the same time */ - tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER); + tor_assert(conn_listener_type_supports_af_unix(conn->type)); if (unlink(conn->address) < 0 && errno != ENOENT) { log_warn(LD_NET, "Could not unlink %s: %s", conn->address, @@ -522,8 +558,7 @@ connection_free_(connection_t *conn) or_conn, TLS_CHAN_TO_BASE(or_conn->chan), U64_PRINTF_ARG( TLS_CHAN_TO_BASE(or_conn->chan)->global_identifier)); - if (!(TLS_CHAN_TO_BASE(or_conn->chan)->state == CHANNEL_STATE_CLOSED || - TLS_CHAN_TO_BASE(or_conn->chan)->state == CHANNEL_STATE_ERROR)) { + if (!CHANNEL_FINISHED(TLS_CHAN_TO_BASE(or_conn->chan))) { channel_close_for_error(TLS_CHAN_TO_BASE(or_conn->chan)); } @@ -553,8 +588,10 @@ connection_free_(connection_t *conn) tor_free(control_conn->incoming_cmd); } - tor_free(conn->read_event); /* Probably already freed by connection_free. */ - tor_free(conn->write_event); /* Probably already freed by connection_free. */ + /* Probably already freed by connection_free. */ + tor_event_free(conn->read_event); + tor_event_free(conn->write_event); + conn->read_event = conn->write_event = NULL; 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 @@ -590,6 +627,13 @@ connection_free_(connection_t *conn) log_warn(LD_BUG, "called on OR conn with non-zeroed identity_digest"); connection_or_remove_from_identity_map(TO_OR_CONN(conn)); } + if (conn->type == CONN_TYPE_OR || conn->type == CONN_TYPE_EXT_OR) { + connection_or_remove_from_ext_or_id_map(TO_OR_CONN(conn)); + tor_free(TO_OR_CONN(conn)->ext_or_conn_id); + tor_free(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash); + tor_free(TO_OR_CONN(conn)->ext_or_transport); + } + #ifdef USE_BUFFEREVENTS if (conn->type == CONN_TYPE_OR && TO_OR_CONN(conn)->bucket_cfg) { ev_token_bucket_cfg_free(TO_OR_CONN(conn)->bucket_cfg); @@ -653,6 +697,7 @@ connection_about_to_close_connection(connection_t *conn) connection_dir_about_to_close(TO_DIR_CONN(conn)); break; case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: connection_or_about_to_close(TO_OR_CONN(conn)); break; case CONN_TYPE_AP: @@ -864,9 +909,9 @@ create_unix_sockaddr(const char *listenaddress, char **readable_address, } #endif /* HAVE_SYS_UN_H */ -/** Warn that an accept or a connect has failed because we're running up - * against our ulimit. Rate-limit these warnings so that we don't spam - * the log. */ +/** Warn that an accept or a connect has failed because we're running out of + * TCP sockets we can use on current system. Rate-limit these warnings so + * that we don't spam the log. */ static void warn_too_many_conns(void) { @@ -876,7 +921,7 @@ warn_too_many_conns(void) if ((m = rate_limit_log(&last_warned, approx_time()))) { int n_conns = get_n_open_sockets(); log_warn(LD_NET,"Failing because we have %d connections already. Please " - "raise your ulimit -n.%s", n_conns, m); + "read doc/TUNING for guidance.%s", n_conns, m); tor_free(m); control_event_general_status(LOG_WARN, "TOO_MANY_CONNECTIONS CURRENT=%d", n_conns); @@ -884,29 +929,81 @@ warn_too_many_conns(void) } #ifdef HAVE_SYS_UN_H + +#define UNIX_SOCKET_PURPOSE_CONTROL_SOCKET 0 +#define UNIX_SOCKET_PURPOSE_SOCKS_SOCKET 1 + +/** Check if the purpose isn't one of the ones we know what to do with */ + +static int +is_valid_unix_socket_purpose(int purpose) +{ + int valid = 0; + + switch (purpose) { + case UNIX_SOCKET_PURPOSE_CONTROL_SOCKET: + case UNIX_SOCKET_PURPOSE_SOCKS_SOCKET: + valid = 1; + break; + } + + return valid; +} + +/** Return a string description of a unix socket purpose */ +static const char * +unix_socket_purpose_to_string(int purpose) +{ + const char *s = "unknown-purpose socket"; + + switch (purpose) { + case UNIX_SOCKET_PURPOSE_CONTROL_SOCKET: + s = "control socket"; + break; + case UNIX_SOCKET_PURPOSE_SOCKS_SOCKET: + s = "SOCKS socket"; + break; + } + + return s; +} + /** Check whether we should be willing to open an AF_UNIX socket in * <b>path</b>. Return 0 if we should go ahead and -1 if we shouldn't. */ static int -check_location_for_unix_socket(const or_options_t *options, const char *path) +check_location_for_unix_socket(const or_options_t *options, const char *path, + int purpose) { int r = -1; - char *p = tor_strdup(path); + char *p = NULL; + + tor_assert(is_valid_unix_socket_purpose(purpose)); + + p = tor_strdup(path); cpd_check_t flags = CPD_CHECK_MODE_ONLY; - if (get_parent_directory(p)<0) + if (get_parent_directory(p)<0 || p[0] != '/') { + log_warn(LD_GENERAL, "Bad unix socket address '%s'. Tor does not support " + "relative paths for unix sockets.", path); goto done; + } - if (options->ControlSocketsGroupWritable) + if ((purpose == UNIX_SOCKET_PURPOSE_CONTROL_SOCKET && + options->ControlSocketsGroupWritable) || + (purpose == UNIX_SOCKET_PURPOSE_SOCKS_SOCKET && + options->SocksSocketsGroupWritable)) { flags |= CPD_GROUP_OK; + } if (check_private_dir(p, flags, options->User) < 0) { char *escpath, *escdir; escpath = esc_for_log(path); escdir = esc_for_log(p); - log_warn(LD_GENERAL, "Before Tor can create a control socket in %s, the " - "directory %s needs to exist, and to be accessible only by the " - "user%s account that is running Tor. (On some Unix systems, " - "anybody who can list a socket can connect to it, so Tor is " - "being careful.)", escpath, escdir, + log_warn(LD_GENERAL, "Before Tor can create a %s in %s, the directory " + "%s needs to exist, and to be accessible only by the user%s " + "account that is running Tor. (On some Unix systems, anybody " + "who can list a socket can connect to it, so Tor is being " + "careful.)", + unix_socket_purpose_to_string(purpose), escpath, escdir, options->ControlSocketsGroupWritable ? " and group" : ""); tor_free(escpath); tor_free(escdir); @@ -921,12 +1018,14 @@ check_location_for_unix_socket(const or_options_t *options, const char *path) #endif /** Tell the TCP stack that it shouldn't wait for a long time after - * <b>sock</b> has closed before reusing its port. */ -static void + * <b>sock</b> has closed before reusing its port. Return 0 on success, + * -1 on failure. */ +static int make_socket_reuseable(tor_socket_t sock) { #ifdef _WIN32 (void) sock; + return 0; #else int one=1; @@ -936,9 +1035,9 @@ make_socket_reuseable(tor_socket_t sock) * already has it bound_. So, don't do that on Win32. */ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, (socklen_t)sizeof(one)) == -1) { - log_warn(LD_NET, "Error setting SO_REUSEADDR flag: %s", - tor_socket_strerror(errno)); + return -1; } + return 0; #endif } @@ -971,31 +1070,31 @@ tor_listen(tor_socket_t fd) */ static connection_t * connection_listener_new(const struct sockaddr *listensockaddr, - socklen_t socklen, - int type, const char *address, - const port_cfg_t *port_cfg) + socklen_t socklen, + int type, const char *address, + const port_cfg_t *port_cfg) { listener_connection_t *lis_conn; - connection_t *conn; - tor_socket_t s; /* the socket we're going to make */ + connection_t *conn = NULL; + tor_socket_t s = TOR_INVALID_SOCKET; /* the socket we're going to make */ or_options_t const *options = get_options(); #if defined(HAVE_PWD_H) && defined(HAVE_SYS_UN_H) - struct passwd *pw = NULL; + const struct passwd *pw = NULL; #endif uint16_t usePort = 0, gotPort = 0; int start_reading = 0; static int global_next_session_group = SESSION_GROUP_FIRST_AUTO; tor_addr_t addr; - if (get_n_open_sockets() >= get_options()->ConnLimit_-1) { + if (get_n_open_sockets() >= options->ConnLimit_-1) { warn_too_many_conns(); return NULL; } if (listensockaddr->sa_family == AF_INET || listensockaddr->sa_family == AF_INET6) { - int is_tcp = (type != CONN_TYPE_AP_DNS_LISTENER); - if (is_tcp) + int is_stream = (type != CONN_TYPE_AP_DNS_LISTENER); + if (is_stream) start_reading = 1; tor_addr_from_sockaddr(&addr, listensockaddr, &usePort); @@ -1003,16 +1102,36 @@ connection_listener_new(const struct sockaddr *listensockaddr, log_notice(LD_NET, "Opening %s on %s", conn_type_to_string(type), fmt_addrport(&addr, usePort)); - s = tor_open_socket(tor_addr_family(&addr), - is_tcp ? SOCK_STREAM : SOCK_DGRAM, - is_tcp ? IPPROTO_TCP: IPPROTO_UDP); + s = tor_open_socket_nonblocking(tor_addr_family(&addr), + is_stream ? SOCK_STREAM : SOCK_DGRAM, + is_stream ? IPPROTO_TCP: IPPROTO_UDP); if (!SOCKET_OK(s)) { - log_warn(LD_NET,"Socket creation failed: %s", + log_warn(LD_NET, "Socket creation failed: %s", tor_socket_strerror(tor_socket_errno(-1))); goto err; } - make_socket_reuseable(s); + if (make_socket_reuseable(s) < 0) { + log_warn(LD_NET, "Error setting SO_REUSEADDR flag on %s: %s", + conn_type_to_string(type), + tor_socket_strerror(errno)); + } + +#if defined USE_TRANSPARENT && defined(IP_TRANSPARENT) + if (options->TransProxyType_parsed == TPT_TPROXY && + type == CONN_TYPE_AP_TRANS_LISTENER) { + int one = 1; + if (setsockopt(s, SOL_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) { + const char *extra = ""; + int e = tor_socket_errno(s); + if (e == EPERM) + extra = "TransTPROXY requires root privileges or similar" + " capabilities."; + log_warn(LD_NET, "Error setting IP_TRANSPARENT flag: %s.%s", + tor_socket_strerror(e), extra); + } + } +#endif #ifdef IPV6_V6ONLY if (listensockaddr->sa_family == AF_INET6) { @@ -1025,7 +1144,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, /* We need to set IPV6_V6ONLY so that this socket can't get used for * IPv4 connections. */ if (setsockopt(s,IPPROTO_IPV6, IPV6_V6ONLY, - (void*)&one, sizeof(one))<0) { + (void*)&one, sizeof(one)) < 0) { int e = tor_socket_errno(s); log_warn(LD_NET, "Error setting IPV6_V6ONLY flag: %s", tor_socket_strerror(e)); @@ -1041,15 +1160,13 @@ connection_listener_new(const struct sockaddr *listensockaddr, helpfulhint = ". Is Tor already running?"; log_warn(LD_NET, "Could not bind to %s:%u: %s%s", address, usePort, tor_socket_strerror(e), helpfulhint); - tor_close_socket(s); goto err; } - if (is_tcp) { + if (is_stream) { if (tor_listen(s) < 0) { log_warn(LD_NET, "Could not listen on %s:%u: %s", address, usePort, tor_socket_strerror(tor_socket_errno(s))); - tor_close_socket(s); goto err; } } @@ -1069,15 +1186,21 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_addr_from_sockaddr(&addr2, (struct sockaddr*)&ss, &gotPort); } #ifdef HAVE_SYS_UN_H + /* + * AF_UNIX generic setup stuff + */ } else if (listensockaddr->sa_family == AF_UNIX) { + /* We want to start reading for both AF_UNIX cases */ start_reading = 1; - /* For now only control ports can be Unix domain sockets - * and listeners at the same time */ - tor_assert(type == CONN_TYPE_CONTROL_LISTENER); + tor_assert(conn_listener_type_supports_af_unix(type)); - if (check_location_for_unix_socket(options, address) < 0) - goto err; + if (check_location_for_unix_socket(options, address, + (type == CONN_TYPE_CONTROL_LISTENER) ? + UNIX_SOCKET_PURPOSE_CONTROL_SOCKET : + UNIX_SOCKET_PURPOSE_SOCKS_SOCKET) < 0) { + goto err; + } log_notice(LD_NET, "Opening %s on %s", conn_type_to_string(type), address); @@ -1089,76 +1212,84 @@ connection_listener_new(const struct sockaddr *listensockaddr, strerror(errno)); goto err; } - s = tor_open_socket(AF_UNIX, SOCK_STREAM, 0); + + s = tor_open_socket_nonblocking(AF_UNIX, SOCK_STREAM, 0); if (! SOCKET_OK(s)) { log_warn(LD_NET,"Socket creation failed: %s.", strerror(errno)); goto err; } - if (bind(s, listensockaddr, (socklen_t)sizeof(struct sockaddr_un)) == -1) { + if (bind(s, listensockaddr, + (socklen_t)sizeof(struct sockaddr_un)) == -1) { log_warn(LD_NET,"Bind to %s failed: %s.", address, tor_socket_strerror(tor_socket_errno(s))); - tor_close_socket(s); goto err; } + #ifdef HAVE_PWD_H if (options->User) { - pw = getpwnam(options->User); + pw = tor_getpwnam(options->User); if (pw == NULL) { log_warn(LD_NET,"Unable to chown() %s socket: user %s not found.", address, options->User); - tor_close_socket(s); goto err; } else if (chown(address, pw->pw_uid, pw->pw_gid) < 0) { log_warn(LD_NET,"Unable to chown() %s socket: %s.", address, strerror(errno)); - tor_close_socket(s); goto err; } } #endif - if (options->ControlSocketsGroupWritable) { + + if ((type == CONN_TYPE_CONTROL_LISTENER && + options->ControlSocketsGroupWritable) || + (type == CONN_TYPE_AP_LISTENER && + options->SocksSocketsGroupWritable)) { /* We need to use chmod; fchmod doesn't work on sockets on all * platforms. */ if (chmod(address, 0660) < 0) { log_warn(LD_FS,"Unable to make %s group-writable.", address); - tor_close_socket(s); + goto err; + } + } else if ((type == CONN_TYPE_CONTROL_LISTENER && + !(options->ControlSocketsGroupWritable)) || + (type == CONN_TYPE_AP_LISTENER && + !(options->SocksSocketsGroupWritable))) { + /* We need to use chmod; fchmod doesn't work on sockets on all + * platforms. */ + if (chmod(address, 0600) < 0) { + log_warn(LD_FS,"Unable to make %s group-writable.", address); goto err; } } - if (listen(s,SOMAXCONN) < 0) { + if (listen(s, SOMAXCONN) < 0) { log_warn(LD_NET, "Could not listen on %s: %s", address, tor_socket_strerror(tor_socket_errno(s))); - tor_close_socket(s); goto err; } -#else - (void)options; #endif /* HAVE_SYS_UN_H */ } else { - log_err(LD_BUG,"Got unexpected address family %d.", - listensockaddr->sa_family); - tor_assert(0); - } - - if (set_socket_nonblocking(s) == -1) { - tor_close_socket(s); - goto err; + log_err(LD_BUG, "Got unexpected address family %d.", + listensockaddr->sa_family); + tor_assert(0); } lis_conn = listener_connection_new(type, listensockaddr->sa_family); conn = TO_CONN(lis_conn); conn->socket_family = listensockaddr->sa_family; conn->s = s; + s = TOR_INVALID_SOCKET; /* Prevent double-close */ conn->address = tor_strdup(address); conn->port = gotPort; tor_addr_copy(&conn->addr, &addr); - if (port_cfg->isolation_flags) { - lis_conn->isolation_flags = port_cfg->isolation_flags; - if (port_cfg->session_group >= 0) { - lis_conn->session_group = port_cfg->session_group; + memcpy(&lis_conn->entry_cfg, &port_cfg->entry_cfg, sizeof(entry_port_cfg_t)); + + if (port_cfg->entry_cfg.isolation_flags) { + lis_conn->entry_cfg.isolation_flags = port_cfg->entry_cfg.isolation_flags; + if (port_cfg->entry_cfg.session_group >= 0) { + lis_conn->entry_cfg.session_group = port_cfg->entry_cfg.session_group; } else { /* This can wrap after around INT_MAX listeners are opened. But I don't * believe that matters, since you would need to open a ridiculous @@ -1166,27 +1297,18 @@ connection_listener_new(const struct sockaddr *listensockaddr, * hit this. An OR with a dozen ports open, for example, would have to * close and re-open its listeners every second for 4 years nonstop. */ - lis_conn->session_group = global_next_session_group--; + lis_conn->entry_cfg.session_group = global_next_session_group--; } } - if (type == CONN_TYPE_AP_LISTENER) { - lis_conn->socks_ipv4_traffic = port_cfg->ipv4_traffic; - lis_conn->socks_ipv6_traffic = port_cfg->ipv6_traffic; - lis_conn->socks_prefer_ipv6 = port_cfg->prefer_ipv6; - } else { - lis_conn->socks_ipv4_traffic = 1; - lis_conn->socks_ipv6_traffic = 1; + + if (type != CONN_TYPE_AP_LISTENER) { + lis_conn->entry_cfg.ipv4_traffic = 1; + lis_conn->entry_cfg.ipv6_traffic = 1; + lis_conn->entry_cfg.prefer_ipv6 = 0; } - lis_conn->cache_ipv4_answers = port_cfg->cache_ipv4_answers; - lis_conn->cache_ipv6_answers = port_cfg->cache_ipv6_answers; - lis_conn->use_cached_ipv4_answers = port_cfg->use_cached_ipv4_answers; - lis_conn->use_cached_ipv6_answers = port_cfg->use_cached_ipv6_answers; - lis_conn->prefer_ipv6_virtaddr = port_cfg->prefer_ipv6_virtaddr; - lis_conn->socks_prefer_no_auth = port_cfg->socks_prefer_no_auth; if (connection_add(conn) < 0) { /* no space, forget it */ log_warn(LD_NET,"connection_add for listener failed. Giving up."); - connection_free(conn); goto err; } @@ -1205,6 +1327,11 @@ connection_listener_new(const struct sockaddr *listensockaddr, return conn; err: + if (SOCKET_OK(s)) + tor_close_socket(s); + if (conn) + connection_free(conn); + return NULL; } @@ -1245,6 +1372,8 @@ check_sockaddr(const struct sockaddr *sa, int len, int level) "Address for new connection has address/port equal to zero."); ok = 0; } + } else if (sa->sa_family == AF_UNIX) { + ok = 1; } else { ok = 0; } @@ -1289,7 +1418,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) tor_assert((size_t)remotelen >= sizeof(struct sockaddr_in)); memset(&addrbuf, 0, sizeof(addrbuf)); - news = tor_accept_socket(conn->s,remote,&remotelen); + news = tor_accept_socket_nonblocking(conn->s,remote,&remotelen); if (!SOCKET_OK(news)) { /* accept() error */ int e = tor_socket_errno(conn->s); if (ERRNO_IS_ACCEPT_EAGAIN(e)) { @@ -1308,8 +1437,15 @@ connection_handle_listener_read(connection_t *conn, int new_type) "Connection accepted on socket %d (child of fd %d).", (int)news,(int)conn->s); - make_socket_reuseable(news); - if (set_socket_nonblocking(news) == -1) { + if (make_socket_reuseable(news) < 0) { + if (tor_socket_errno(news) == EINVAL) { + /* This can happen on OSX if we get a badly timed shutdown. */ + log_debug(LD_NET, "make_socket_reuseable returned EINVAL"); + } else { + log_warn(LD_NET, "Error setting SO_REUSEADDR flag on %s: %s", + conn_type_to_string(new_type), + tor_socket_strerror(errno)); + } tor_close_socket(news); return 0; } @@ -1322,7 +1458,8 @@ connection_handle_listener_read(connection_t *conn, int new_type) return 0; } - if (conn->socket_family == AF_INET || conn->socket_family == AF_INET6) { + if (conn->socket_family == AF_INET || conn->socket_family == AF_INET6 || + (conn->socket_family == AF_UNIX && new_type == CONN_TYPE_AP)) { tor_addr_t addr; uint16_t port; if (check_sockaddr(remote, remotelen, LOG_INFO)<0) { @@ -1363,15 +1500,24 @@ connection_handle_listener_read(connection_t *conn, int new_type) newconn->port = port; newconn->address = tor_dup_addr(&addr); - if (new_type == CONN_TYPE_AP) { - TO_ENTRY_CONN(newconn)->socks_request->socks_prefer_no_auth = - TO_LISTENER_CONN(conn)->socks_prefer_no_auth; + if (new_type == CONN_TYPE_AP && conn->socket_family != AF_UNIX) { + log_info(LD_NET, "New SOCKS connection opened from %s.", + fmt_and_decorate_addr(&addr)); + } + if (new_type == CONN_TYPE_AP && conn->socket_family == AF_UNIX) { + newconn->port = 0; + newconn->address = tor_strdup(conn->address); + log_info(LD_NET, "New SOCKS AF_UNIX connection opened"); + } + if (new_type == CONN_TYPE_CONTROL) { + log_notice(LD_CONTROL, "New control connection opened from %s.", + fmt_and_decorate_addr(&addr)); } - } else if (conn->socket_family == AF_UNIX) { - /* For now only control ports can be Unix domain sockets - * and listeners at the same time */ + } else if (conn->socket_family == AF_UNIX && conn->type != CONN_TYPE_AP) { tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER); + tor_assert(new_type == CONN_TYPE_CONTROL); + log_notice(LD_CONTROL, "New control connection opened."); newconn = connection_new(new_type, conn->socket_family); newconn->s = news; @@ -1411,6 +1557,9 @@ connection_init_accepted_conn(connection_t *conn, connection_start_reading(conn); switch (conn->type) { + case CONN_TYPE_EXT_OR: + /* Initiate Extended ORPort authentication. */ + return connection_ext_or_start_auth(TO_OR_CONN(conn)); case CONN_TYPE_OR: control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW, 0); rv = connection_tls_start_handshake(TO_OR_CONN(conn), 1); @@ -1420,25 +1569,16 @@ connection_init_accepted_conn(connection_t *conn, return rv; break; case CONN_TYPE_AP: - TO_ENTRY_CONN(conn)->isolation_flags = listener->isolation_flags; - TO_ENTRY_CONN(conn)->session_group = listener->session_group; + memcpy(&TO_ENTRY_CONN(conn)->entry_cfg, &listener->entry_cfg, + sizeof(entry_port_cfg_t)); TO_ENTRY_CONN(conn)->nym_epoch = get_signewnym_epoch(); TO_ENTRY_CONN(conn)->socks_request->listener_type = listener->base_.type; - TO_ENTRY_CONN(conn)->ipv4_traffic_ok = listener->socks_ipv4_traffic; - TO_ENTRY_CONN(conn)->ipv6_traffic_ok = listener->socks_ipv6_traffic; - TO_ENTRY_CONN(conn)->prefer_ipv6_traffic = listener->socks_prefer_ipv6; - TO_ENTRY_CONN(conn)->cache_ipv4_answers = listener->cache_ipv4_answers; - TO_ENTRY_CONN(conn)->cache_ipv6_answers = listener->cache_ipv6_answers; - TO_ENTRY_CONN(conn)->use_cached_ipv4_answers = - listener->use_cached_ipv4_answers; - TO_ENTRY_CONN(conn)->use_cached_ipv6_answers = - listener->use_cached_ipv6_answers; - TO_ENTRY_CONN(conn)->prefer_ipv6_virtaddr = - listener->prefer_ipv6_virtaddr; switch (TO_CONN(listener)->type) { case CONN_TYPE_AP_LISTENER: conn->state = AP_CONN_STATE_SOCKS_WAIT; + TO_ENTRY_CONN(conn)->socks_request->socks_prefer_no_auth = + listener->entry_cfg.socks_prefer_no_auth; break; case CONN_TYPE_AP_TRANS_LISTENER: TO_ENTRY_CONN(conn)->is_transparent_ap = 1; @@ -1461,26 +1601,21 @@ connection_init_accepted_conn(connection_t *conn, return 0; } -/** Take conn, make a nonblocking socket; try to connect to - * addr:port (they arrive in *host order*). If fail, return -1 and if - * applicable put your best guess about errno into *<b>socket_error</b>. - * Else assign s to conn-\>s: if connected return 1, if EAGAIN return 0. - * - * address is used to make the logs useful. - * - * On success, add conn to the list of polled connections. - */ -int -connection_connect(connection_t *conn, const char *address, - const tor_addr_t *addr, uint16_t port, int *socket_error) +static int +connection_connect_sockaddr(connection_t *conn, + const struct sockaddr *sa, + socklen_t sa_len, + const struct sockaddr *bindaddr, + socklen_t bindaddr_len, + int *socket_error) { tor_socket_t s; int inprogress = 0; - struct sockaddr_storage addrbuf; - struct sockaddr *dest_addr; - int dest_addr_len; const or_options_t *options = get_options(); - int protocol_family; + + tor_assert(conn); + tor_assert(sa); + tor_assert(socket_error); if (get_n_open_sockets() >= get_options()->ConnLimit_-1) { warn_too_many_conns(); @@ -1488,11 +1623,6 @@ connection_connect(connection_t *conn, const char *address, return -1; } - if (tor_addr_family(addr) == AF_INET6) - protocol_family = PF_INET6; - else - protocol_family = PF_INET; - if (get_options()->DisableNetwork) { /* We should never even try to connect anyplace if DisableNetwork is set. * Warn if we do, and refuse to make the connection. */ @@ -1504,7 +1634,11 @@ connection_connect(connection_t *conn, const char *address, return -1; } - s = tor_open_socket(protocol_family,SOCK_STREAM,IPPROTO_TCP); + const int protocol_family = sa->sa_family; + const int proto = (sa->sa_family == AF_INET6 || + sa->sa_family == AF_INET) ? IPPROTO_TCP : 0; + + s = tor_open_socket_nonblocking(protocol_family, SOCK_STREAM, proto); if (! SOCKET_OK(s)) { *socket_error = tor_socket_errno(-1); log_warn(LD_NET,"Error creating network socket: %s", @@ -1512,66 +1646,31 @@ connection_connect(connection_t *conn, const char *address, return -1; } - make_socket_reuseable(s); - - if (!tor_addr_is_loopback(addr)) { - const tor_addr_t *ext_addr = NULL; - if (protocol_family == AF_INET && - !tor_addr_is_null(&options->OutboundBindAddressIPv4_)) - ext_addr = &options->OutboundBindAddressIPv4_; - else if (protocol_family == AF_INET6 && - !tor_addr_is_null(&options->OutboundBindAddressIPv6_)) - ext_addr = &options->OutboundBindAddressIPv6_; - if (ext_addr) { - struct sockaddr_storage ext_addr_sa; - socklen_t ext_addr_len = 0; - memset(&ext_addr_sa, 0, sizeof(ext_addr_sa)); - ext_addr_len = tor_addr_to_sockaddr(ext_addr, 0, - (struct sockaddr *) &ext_addr_sa, - sizeof(ext_addr_sa)); - if (ext_addr_len == 0) { - log_warn(LD_NET, - "Error converting OutboundBindAddress %s into sockaddr. " - "Ignoring.", fmt_and_decorate_addr(ext_addr)); - } else { - if (bind(s, (struct sockaddr *) &ext_addr_sa, ext_addr_len) < 0) { - *socket_error = tor_socket_errno(s); - log_warn(LD_NET,"Error binding network socket to %s: %s", - fmt_and_decorate_addr(ext_addr), - tor_socket_strerror(*socket_error)); - tor_close_socket(s); - return -1; - } - } - } + if (make_socket_reuseable(s) < 0) { + log_warn(LD_NET, "Error setting SO_REUSEADDR flag on new connection: %s", + tor_socket_strerror(errno)); } - if (set_socket_nonblocking(s) == -1) { + if (bindaddr && bind(s, bindaddr, bindaddr_len) < 0) { *socket_error = tor_socket_errno(s); + log_warn(LD_NET,"Error binding network socket: %s", + tor_socket_strerror(*socket_error)); tor_close_socket(s); return -1; } + tor_assert(options); if (options->ConstrainedSockets) set_constrained_socket_buffers(s, (int)options->ConstrainedSockSize); - memset(&addrbuf,0,sizeof(addrbuf)); - dest_addr = (struct sockaddr*) &addrbuf; - dest_addr_len = tor_addr_to_sockaddr(addr, port, dest_addr, sizeof(addrbuf)); - tor_assert(dest_addr_len > 0); - - log_debug(LD_NET, "Connecting to %s:%u.", - escaped_safe_str_client(address), port); - - if (connect(s, dest_addr, (socklen_t)dest_addr_len) < 0) { + if (connect(s, sa, sa_len) < 0) { int e = tor_socket_errno(s); if (!ERRNO_IS_CONN_EINPROGRESS(e)) { /* yuck. kill it. */ *socket_error = e; log_info(LD_NET, - "connect() to %s:%u failed: %s", - escaped_safe_str_client(address), - port, tor_socket_strerror(e)); + "connect() to socket failed: %s", + tor_socket_strerror(e)); tor_close_socket(s); return -1; } else { @@ -1580,19 +1679,121 @@ connection_connect(connection_t *conn, const char *address, } /* it succeeded. we're connected. */ - log_fn(inprogress?LOG_DEBUG:LOG_INFO, LD_NET, - "Connection to %s:%u %s (sock "TOR_SOCKET_T_FORMAT").", - escaped_safe_str_client(address), - port, inprogress?"in progress":"established", s); + log_fn(inprogress ? LOG_DEBUG : LOG_INFO, LD_NET, + "Connection to socket %s (sock "TOR_SOCKET_T_FORMAT").", + inprogress ? "in progress" : "established", s); conn->s = s; if (connection_add_connecting(conn) < 0) { /* no space, forget it */ *socket_error = SOCK_ERRNO(ENOBUFS); return -1; } + return inprogress ? 0 : 1; } +/** Take conn, make a nonblocking socket; try to connect to + * addr:port (they arrive in *host order*). If fail, return -1 and if + * applicable put your best guess about errno into *<b>socket_error</b>. + * Else assign s to conn-\>s: if connected return 1, if EAGAIN return 0. + * + * address is used to make the logs useful. + * + * On success, add conn to the list of polled connections. + */ +int +connection_connect(connection_t *conn, const char *address, + const tor_addr_t *addr, uint16_t port, int *socket_error) +{ + struct sockaddr_storage addrbuf; + struct sockaddr_storage bind_addr_ss; + struct sockaddr *bind_addr = NULL; + struct sockaddr *dest_addr; + int dest_addr_len, bind_addr_len = 0; + const or_options_t *options = get_options(); + int protocol_family; + + if (tor_addr_family(addr) == AF_INET6) + protocol_family = PF_INET6; + else + protocol_family = PF_INET; + + if (!tor_addr_is_loopback(addr)) { + const tor_addr_t *ext_addr = NULL; + if (protocol_family == AF_INET && + !tor_addr_is_null(&options->OutboundBindAddressIPv4_)) + ext_addr = &options->OutboundBindAddressIPv4_; + else if (protocol_family == AF_INET6 && + !tor_addr_is_null(&options->OutboundBindAddressIPv6_)) + ext_addr = &options->OutboundBindAddressIPv6_; + if (ext_addr) { + memset(&bind_addr_ss, 0, sizeof(bind_addr_ss)); + bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0, + (struct sockaddr *) &bind_addr_ss, + sizeof(bind_addr_ss)); + if (bind_addr_len == 0) { + log_warn(LD_NET, + "Error converting OutboundBindAddress %s into sockaddr. " + "Ignoring.", fmt_and_decorate_addr(ext_addr)); + } else { + bind_addr = (struct sockaddr *)&bind_addr_ss; + } + } + } + + memset(&addrbuf,0,sizeof(addrbuf)); + dest_addr = (struct sockaddr*) &addrbuf; + dest_addr_len = tor_addr_to_sockaddr(addr, port, dest_addr, sizeof(addrbuf)); + tor_assert(dest_addr_len > 0); + + log_debug(LD_NET, "Connecting to %s:%u.", + escaped_safe_str_client(address), port); + + return connection_connect_sockaddr(conn, dest_addr, dest_addr_len, + bind_addr, bind_addr_len, socket_error); +} + +#ifdef HAVE_SYS_UN_H + +/** Take conn, make a nonblocking socket; try to connect to + * an AF_UNIX socket at socket_path. If fail, return -1 and if applicable + * put your best guess about errno into *<b>socket_error</b>. Else assign s + * to conn-\>s: if connected return 1, if EAGAIN return 0. + * + * On success, add conn to the list of polled connections. + */ +int +connection_connect_unix(connection_t *conn, const char *socket_path, + int *socket_error) +{ + struct sockaddr_un dest_addr; + + tor_assert(socket_path); + + /* Check that we'll be able to fit it into dest_addr later */ + if (strlen(socket_path) + 1 > sizeof(dest_addr.sun_path)) { + log_warn(LD_NET, + "Path %s is too long for an AF_UNIX socket\n", + escaped_safe_str_client(socket_path)); + *socket_error = SOCK_ERRNO(ENAMETOOLONG); + return -1; + } + + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.sun_family = AF_UNIX; + strlcpy(dest_addr.sun_path, socket_path, sizeof(dest_addr.sun_path)); + + log_debug(LD_NET, + "Connecting to AF_UNIX socket at %s.", + escaped_safe_str_client(socket_path)); + + return connection_connect_sockaddr(conn, + (struct sockaddr *)&dest_addr, sizeof(dest_addr), + NULL, 0, socket_error); +} + +#endif /* defined(HAVE_SYS_UN_H) */ + /** Convert state number to string representation for logging purposes. */ static const char * @@ -1617,6 +1818,32 @@ connection_proxy_state_to_string(int state) return states[state]; } +/** Returns the global proxy type used by tor. Use this function for + * logging or high-level purposes, don't use it to fill the + * <b>proxy_type</b> field of or_connection_t; use the actual proxy + * protocol instead.*/ +static int +get_proxy_type(void) +{ + const or_options_t *options = get_options(); + + if (options->ClientTransportPlugin) + return PROXY_PLUGGABLE; + else if (options->HTTPSProxy) + return PROXY_CONNECT; + else if (options->Socks4Proxy) + return PROXY_SOCKS4; + else if (options->Socks5Proxy) + return PROXY_SOCKS5; + else + return PROXY_NONE; +} + +/* One byte for the version, one for the command, two for the + port, and four for the addr... and, one more for the + username NUL: */ +#define SOCKS4_STANDARD_BUFFER_SIZE (1 + 1 + 2 + 4 + 1) + /** Write a proxy request of <b>type</b> (socks4, socks5, https) to conn * for conn->addr:conn->port, authenticating with the auth details given * in the configuration (if available). SOCKS 5 and HTTP CONNECT proxies @@ -1671,17 +1898,45 @@ connection_proxy_connect(connection_t *conn, int type) } case PROXY_SOCKS4: { - unsigned char buf[9]; + unsigned char *buf; uint16_t portn; uint32_t ip4addr; + size_t buf_size = 0; + char *socks_args_string = NULL; - /* Send a SOCKS4 connect request with empty user id */ + /* Send a SOCKS4 connect request */ if (tor_addr_family(&conn->addr) != AF_INET) { log_warn(LD_NET, "SOCKS4 client is incompatible with IPv6"); return -1; } + { /* If we are here because we are trying to connect to a + pluggable transport proxy, check if we have any SOCKS + arguments to transmit. If we do, compress all arguments to + a single string in 'socks_args_string': */ + + if (get_proxy_type() == PROXY_PLUGGABLE) { + socks_args_string = + pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port); + if (socks_args_string) + log_debug(LD_NET, "Sending out '%s' as our SOCKS argument string.", + socks_args_string); + } + } + + { /* Figure out the buffer size we need for the SOCKS message: */ + + buf_size = SOCKS4_STANDARD_BUFFER_SIZE; + + /* If we have a SOCKS argument string, consider its size when + calculating the buffer size: */ + if (socks_args_string) + buf_size += strlen(socks_args_string); + } + + buf = tor_malloc_zero(buf_size); + ip4addr = tor_addr_to_ipv4n(&conn->addr); portn = htons(conn->port); @@ -1689,9 +1944,23 @@ connection_proxy_connect(connection_t *conn, int type) buf[1] = SOCKS_COMMAND_CONNECT; /* command */ memcpy(buf + 2, &portn, 2); /* port */ memcpy(buf + 4, &ip4addr, 4); /* addr */ - buf[8] = 0; /* userid (empty) */ - connection_write_to_buf((char *)buf, sizeof(buf), conn); + /* Next packet field is the userid. If we have pluggable + transport SOCKS arguments, we have to embed them + there. Otherwise, we use an empty userid. */ + if (socks_args_string) { /* place the SOCKS args string: */ + tor_assert(strlen(socks_args_string) > 0); + tor_assert(buf_size >= + SOCKS4_STANDARD_BUFFER_SIZE + strlen(socks_args_string)); + strlcpy((char *)buf + 8, socks_args_string, buf_size - 8); + tor_free(socks_args_string); + } else { + buf[8] = 0; /* no userid */ + } + + connection_write_to_buf((char *)buf, buf_size, conn); + tor_free(buf); + conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; break; } @@ -1703,8 +1972,13 @@ connection_proxy_connect(connection_t *conn, int type) buf[0] = 5; /* version */ + /* We have to use SOCKS5 authentication, if we have a + Socks5ProxyUsername or if we want to pass arguments to our + pluggable transport proxy: */ + if ((options->Socks5ProxyUsername) || + (get_proxy_type() == PROXY_PLUGGABLE && + (get_socks_args_by_bridge_addrport(&conn->addr, conn->port)))) { /* number of auth methods */ - if (options->Socks5ProxyUsername) { buf[1] = 2; buf[2] = 0x00; /* no authentication */ buf[3] = 0x02; /* rfc1929 Username/Passwd auth */ @@ -1898,15 +2172,49 @@ connection_read_proxy_handshake(connection_t *conn) unsigned char buf[1024]; size_t reqsize, usize, psize; const char *user, *pass; + char *socks_args_string = NULL; + + if (get_proxy_type() == PROXY_PLUGGABLE) { + socks_args_string = + pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port); + if (!socks_args_string) { + log_warn(LD_NET, "Could not create SOCKS args string."); + ret = -1; + break; + } + + log_debug(LD_NET, "SOCKS5 arguments: %s", socks_args_string); + tor_assert(strlen(socks_args_string) > 0); + tor_assert(strlen(socks_args_string) <= MAX_SOCKS5_AUTH_SIZE_TOTAL); + + if (strlen(socks_args_string) > MAX_SOCKS5_AUTH_FIELD_SIZE) { + user = socks_args_string; + usize = MAX_SOCKS5_AUTH_FIELD_SIZE; + pass = socks_args_string + MAX_SOCKS5_AUTH_FIELD_SIZE; + psize = strlen(socks_args_string) - MAX_SOCKS5_AUTH_FIELD_SIZE; + } else { + user = socks_args_string; + usize = strlen(socks_args_string); + pass = "\0"; + psize = 1; + } + } else if (get_options()->Socks5ProxyUsername) { + user = get_options()->Socks5ProxyUsername; + pass = get_options()->Socks5ProxyPassword; + tor_assert(user && pass); + usize = strlen(user); + psize = strlen(pass); + } else { + log_err(LD_BUG, "We entered %s for no reason!", __func__); + tor_fragile_assert(); + ret = -1; + break; + } - user = get_options()->Socks5ProxyUsername; - pass = get_options()->Socks5ProxyPassword; - tor_assert(user && pass); - - /* XXX len of user and pass must be <= 255 !!! */ - usize = strlen(user); - psize = strlen(pass); - tor_assert(usize <= 255 && psize <= 255); + /* Username and password lengths should have been checked + above and during torrc parsing. */ + tor_assert(usize <= MAX_SOCKS5_AUTH_FIELD_SIZE && + psize <= MAX_SOCKS5_AUTH_FIELD_SIZE); reqsize = 3 + usize + psize; buf[0] = 1; /* negotiation version */ @@ -1915,6 +2223,9 @@ connection_read_proxy_handshake(connection_t *conn) buf[2 + usize] = psize; memcpy(buf + 3 + usize, pass, psize); + if (socks_args_string) + tor_free(socks_args_string); + connection_write_to_buf((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK; @@ -2013,7 +2324,7 @@ retry_listener_ports(smartlist_t *old_conns, (conn->socket_family == AF_UNIX && ! wanted->is_unix_addr)) continue; - if (wanted->no_listen) + if (wanted->server_cfg.no_listen) continue; /* We don't want to open a listener for this one */ if (wanted->is_unix_addr) { @@ -2054,7 +2365,7 @@ retry_listener_ports(smartlist_t *old_conns, connection_t *conn; int real_port = port->port == CFG_AUTO_PORT ? 0 : port->port; tor_assert(real_port <= UINT16_MAX); - if (port->no_listen) + if (port->server_cfg.no_listen) continue; if (port->is_unix_addr) { @@ -2072,7 +2383,7 @@ retry_listener_ports(smartlist_t *old_conns, if (listensockaddr) { conn = connection_listener_new(listensockaddr, listensocklen, - port->type, address, port); + port->type, address, port); tor_free(listensockaddr); tor_free(address); } else { @@ -2176,7 +2487,6 @@ connection_mark_all_noncontrol_connections(void) if (conn->marked_for_close) continue; switch (conn->type) { - case CONN_TYPE_CPUWORKER: case CONN_TYPE_CONTROL_LISTENER: case CONN_TYPE_CONTROL: break; @@ -2184,6 +2494,20 @@ connection_mark_all_noncontrol_connections(void) connection_mark_unattached_ap(TO_ENTRY_CONN(conn), END_STREAM_REASON_HIBERNATING); break; + case CONN_TYPE_OR: + { + or_connection_t *orconn = TO_OR_CONN(conn); + if (orconn->chan) { + connection_or_close_normally(orconn, 0); + } else { + /* + * There should have been one, but mark for close and hope + * for the best.. + */ + connection_mark_for_close(conn); + } + } + break; default: connection_mark_for_close(conn); break; @@ -2205,6 +2529,7 @@ connection_is_rate_limited(connection_t *conn) return 0; /* Internal connection */ else if (! options->CountPrivateBandwidth && (tor_addr_family(&conn->addr) == AF_UNSPEC || /* no address */ + tor_addr_family(&conn->addr) == AF_UNIX || /* no address */ tor_addr_is_internal(&conn->addr, 0))) return 0; /* Internal address */ else @@ -2358,9 +2683,8 @@ connection_bucket_write_limit(connection_t *conn, time_t now) * shouldn't send <b>attempt</b> bytes of low-priority directory stuff * out to <b>conn</b>. Else return 0. - * Priority is 1 for v1 requests (directories and running-routers), - * and 2 for v2 requests (statuses and descriptors). But see FFFF in - * directory_handle_command_get() for why we don't use priority 2 yet. + * Priority was 1 for v1 requests (directories and running-routers), + * and 2 for v2 requests and later (statuses and descriptors). * * There are a lot of parameters we could use here: * - global_relayed_write_bucket. Low is bad. @@ -2466,7 +2790,58 @@ record_num_bytes_transferred(connection_t *conn, } #endif +/** Helper: convert given <b>tvnow</b> time value to milliseconds since + * midnight. */ +static uint32_t +msec_since_midnight(const struct timeval *tvnow) +{ + return (uint32_t)(((tvnow->tv_sec % 86400L) * 1000L) + + ((uint32_t)tvnow->tv_usec / (uint32_t)1000L)); +} + +/** Helper: return the time in milliseconds since <b>last_empty_time</b> + * when a bucket ran empty that previously had <b>tokens_before</b> tokens + * now has <b>tokens_after</b> tokens after refilling at timestamp + * <b>tvnow</b>, capped at <b>milliseconds_elapsed</b> milliseconds since + * last refilling that bucket. Return 0 if the bucket has not been empty + * since the last refill or has not been refilled. */ +uint32_t +bucket_millis_empty(int tokens_before, uint32_t last_empty_time, + int tokens_after, int milliseconds_elapsed, + const struct timeval *tvnow) +{ + uint32_t result = 0, refilled; + if (tokens_before <= 0 && tokens_after > tokens_before) { + refilled = msec_since_midnight(tvnow); + result = (uint32_t)((refilled + 86400L * 1000L - last_empty_time) % + (86400L * 1000L)); + if (result > (uint32_t)milliseconds_elapsed) + result = (uint32_t)milliseconds_elapsed; + } + return result; +} + +/** Check if a bucket which had <b>tokens_before</b> tokens and which got + * <b>tokens_removed</b> tokens removed at timestamp <b>tvnow</b> has run + * out of tokens, and if so, note the milliseconds since midnight in + * <b>timestamp_var</b> for the next TB_EMPTY event. */ +void +connection_buckets_note_empty_ts(uint32_t *timestamp_var, + int tokens_before, size_t tokens_removed, + const struct timeval *tvnow) +{ + if (tokens_before > 0 && (uint32_t)tokens_before <= tokens_removed) + *timestamp_var = msec_since_midnight(tvnow); +} + #ifndef USE_BUFFEREVENTS +/** Last time at which the global or relay buckets were emptied in msec + * since midnight. */ +static uint32_t global_relayed_read_emptied = 0, + global_relayed_write_emptied = 0, + global_read_emptied = 0, + global_write_emptied = 0; + /** We just read <b>num_read</b> and wrote <b>num_written</b> bytes * onto <b>conn</b>. Decrement buckets appropriately. */ static void @@ -2489,6 +2864,30 @@ connection_buckets_decrement(connection_t *conn, time_t now, if (!connection_is_rate_limited(conn)) return; /* local IPs are free */ + /* If one or more of our token buckets ran dry just now, note the + * timestamp for TB_EMPTY events. */ + if (get_options()->TestingEnableTbEmptyEvent) { + struct timeval tvnow; + tor_gettimeofday_cached(&tvnow); + if (connection_counts_as_relayed_traffic(conn, now)) { + connection_buckets_note_empty_ts(&global_relayed_read_emptied, + global_relayed_read_bucket, num_read, &tvnow); + connection_buckets_note_empty_ts(&global_relayed_write_emptied, + global_relayed_write_bucket, num_written, &tvnow); + } + connection_buckets_note_empty_ts(&global_read_emptied, + global_read_bucket, num_read, &tvnow); + connection_buckets_note_empty_ts(&global_write_emptied, + global_write_bucket, num_written, &tvnow); + if (connection_speaks_cells(conn) && conn->state == OR_CONN_STATE_OPEN) { + or_connection_t *or_conn = TO_OR_CONN(conn); + connection_buckets_note_empty_ts(&or_conn->read_emptied_time, + or_conn->read_bucket, num_read, &tvnow); + connection_buckets_note_empty_ts(&or_conn->write_emptied_time, + or_conn->write_bucket, num_written, &tvnow); + } + } + if (connection_counts_as_relayed_traffic(conn, now)) { global_relayed_read_bucket -= (int)num_read; global_relayed_write_bucket -= (int)num_written; @@ -2508,6 +2907,9 @@ connection_consider_empty_read_buckets(connection_t *conn) { const char *reason; + if (!connection_is_rate_limited(conn)) + return; /* Always okay. */ + if (global_read_bucket <= 0) { reason = "global read bucket exhausted. Pausing."; } else if (connection_counts_as_relayed_traffic(conn, approx_time()) && @@ -2520,9 +2922,6 @@ connection_consider_empty_read_buckets(connection_t *conn) } else return; /* all good, no need to stop it */ - if (conn->type == CONN_TYPE_CPUWORKER) - return; /* Always okay. */ - LOG_FN_CONN(conn, (LOG_DEBUG, LD_NET, "%s", reason)); conn->read_blocked_on_bw = 1; connection_stop_reading(conn); @@ -2535,6 +2934,9 @@ connection_consider_empty_write_buckets(connection_t *conn) { const char *reason; + if (!connection_is_rate_limited(conn)) + return; /* Always okay. */ + if (global_write_bucket <= 0) { reason = "global write bucket exhausted. Pausing."; } else if (connection_counts_as_relayed_traffic(conn, approx_time()) && @@ -2547,9 +2949,6 @@ connection_consider_empty_write_buckets(connection_t *conn) } else return; /* all good, no need to stop it */ - if (conn->type == CONN_TYPE_CPUWORKER) - return; /* Always okay. */ - LOG_FN_CONN(conn, (LOG_DEBUG, LD_NET, "%s", reason)); conn->write_blocked_on_bw = 1; connection_stop_writing(conn); @@ -2609,6 +3008,12 @@ connection_bucket_refill(int milliseconds_elapsed, time_t now) smartlist_t *conns = get_connection_array(); int bandwidthrate, bandwidthburst, relayrate, relayburst; + int prev_global_read = global_read_bucket; + int prev_global_write = global_write_bucket; + int prev_relay_read = global_relayed_read_bucket; + int prev_relay_write = global_relayed_write_bucket; + struct timeval tvnow; /*< Only used if TB_EMPTY events are enabled. */ + bandwidthrate = (int)options->BandwidthRate; bandwidthburst = (int)options->BandwidthBurst; @@ -2643,12 +3048,42 @@ connection_bucket_refill(int milliseconds_elapsed, time_t now) milliseconds_elapsed, "global_relayed_write_bucket"); + /* If buckets were empty before and have now been refilled, tell any + * interested controllers. */ + if (get_options()->TestingEnableTbEmptyEvent) { + uint32_t global_read_empty_time, global_write_empty_time, + relay_read_empty_time, relay_write_empty_time; + tor_gettimeofday_cached(&tvnow); + global_read_empty_time = bucket_millis_empty(prev_global_read, + global_read_emptied, global_read_bucket, + milliseconds_elapsed, &tvnow); + global_write_empty_time = bucket_millis_empty(prev_global_write, + global_write_emptied, global_write_bucket, + milliseconds_elapsed, &tvnow); + control_event_tb_empty("GLOBAL", global_read_empty_time, + global_write_empty_time, milliseconds_elapsed); + relay_read_empty_time = bucket_millis_empty(prev_relay_read, + global_relayed_read_emptied, + global_relayed_read_bucket, + milliseconds_elapsed, &tvnow); + relay_write_empty_time = bucket_millis_empty(prev_relay_write, + global_relayed_write_emptied, + global_relayed_write_bucket, + milliseconds_elapsed, &tvnow); + control_event_tb_empty("RELAY", relay_read_empty_time, + relay_write_empty_time, milliseconds_elapsed); + } + /* refill the per-connection buckets */ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (connection_speaks_cells(conn)) { or_connection_t *or_conn = TO_OR_CONN(conn); int orbandwidthrate = or_conn->bandwidthrate; int orbandwidthburst = or_conn->bandwidthburst; + + int prev_conn_read = or_conn->read_bucket; + int prev_conn_write = or_conn->write_bucket; + if (connection_bucket_should_increase(or_conn->read_bucket, or_conn)) { connection_bucket_refill_helper(&or_conn->read_bucket, orbandwidthrate, @@ -2663,6 +3098,27 @@ connection_bucket_refill(int milliseconds_elapsed, time_t now) milliseconds_elapsed, "or_conn->write_bucket"); } + + /* If buckets were empty before and have now been refilled, tell any + * interested controllers. */ + if (get_options()->TestingEnableTbEmptyEvent) { + char *bucket; + uint32_t conn_read_empty_time, conn_write_empty_time; + tor_asprintf(&bucket, "ORCONN ID="U64_FORMAT, + U64_PRINTF_ARG(or_conn->base_.global_identifier)); + conn_read_empty_time = bucket_millis_empty(prev_conn_read, + or_conn->read_emptied_time, + or_conn->read_bucket, + milliseconds_elapsed, &tvnow); + conn_write_empty_time = bucket_millis_empty(prev_conn_write, + or_conn->write_emptied_time, + or_conn->write_bucket, + milliseconds_elapsed, &tvnow); + control_event_tb_empty(bucket, conn_read_empty_time, + conn_write_empty_time, + milliseconds_elapsed); + tor_free(bucket); + } } if (conn->read_blocked_on_bw == 1 /* marked to turn reading back on now */ @@ -2819,6 +3275,8 @@ connection_handle_read_impl(connection_t *conn) switch (conn->type) { case CONN_TYPE_OR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_OR); + case CONN_TYPE_EXT_OR_LISTENER: + return connection_handle_listener_read(conn, CONN_TYPE_EXT_OR); case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: case CONN_TYPE_AP_NATD_LISTENER: @@ -3071,14 +3529,37 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* change *max_to_read */ *max_to_read = at_most - n_read; - /* Update edge_conn->n_read */ + /* Update edge_conn->n_read and ocirc->n_read_circ_bw */ if (conn->type == CONN_TYPE_AP) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + circuit_t *circ = circuit_get_by_edge_conn(edge_conn); + origin_circuit_t *ocirc; + /* Check for overflow: */ if (PREDICT_LIKELY(UINT32_MAX - edge_conn->n_read > n_read)) edge_conn->n_read += (int)n_read; else edge_conn->n_read = UINT32_MAX; + + if (circ && CIRCUIT_IS_ORIGIN(circ)) { + ocirc = TO_ORIGIN_CIRCUIT(circ); + if (PREDICT_LIKELY(UINT32_MAX - ocirc->n_read_circ_bw > n_read)) + ocirc->n_read_circ_bw += (int)n_read; + else + ocirc->n_read_circ_bw = UINT32_MAX; + } + } + + /* If CONN_BW events are enabled, update conn->n_read_conn_bw for + * OR/DIR/EXIT connections, checking for overflow. */ + if (get_options()->TestingEnableConnBwEvent && + (conn->type == CONN_TYPE_OR || + conn->type == CONN_TYPE_DIR || + conn->type == CONN_TYPE_EXIT)) { + if (PREDICT_LIKELY(UINT32_MAX - conn->n_read_conn_bw > n_read)) + conn->n_read_conn_bw += (int)n_read; + else + conn->n_read_conn_bw = UINT32_MAX; } } @@ -3331,8 +3812,8 @@ connection_outbuf_too_full(connection_t *conn) /** Try to flush more bytes onto <b>conn</b>-\>s. * - * This function gets called either from conn_write() in main.c - * when poll() has declared that conn wants to write, or below + * This function gets called either from conn_write_callback() in main.c + * when libevent tells us that conn wants to write, or below * from connection_write_to_buf() when an entire TLS record is ready. * * Update <b>conn</b>-\>timestamp_lastwritten to now, and call flush_buf @@ -3374,9 +3855,15 @@ connection_handle_write_impl(connection_t *conn, int force) if (connection_state_is_connecting(conn)) { if (getsockopt(conn->s, SOL_SOCKET, SO_ERROR, (void*)&e, &len) < 0) { log_warn(LD_BUG, "getsockopt() syscall failed"); - if (CONN_IS_EDGE(conn)) - connection_edge_end_errno(TO_EDGE_CONN(conn)); - connection_mark_for_close(conn); + if (conn->type == CONN_TYPE_OR) { + or_connection_t *orconn = TO_OR_CONN(conn); + connection_or_close_for_error(orconn, 0); + } else { + if (CONN_IS_EDGE(conn)) { + connection_edge_end_errno(TO_EDGE_CONN(conn)); + } + connection_mark_for_close(conn); + } return -1; } if (e) { @@ -3491,6 +3978,8 @@ connection_handle_write_impl(connection_t *conn, int force) tor_tls_get_n_raw_bytes(or_conn->tls, &n_read, &n_written); log_debug(LD_GENERAL, "After TLS write of %d: %ld read, %ld written", result, (long)n_read, (long)n_written); + or_conn->bytes_xmitted += result; + or_conn->bytes_xmitted_by_tls += n_written; /* So we notice bytes were written even on error */ /* XXXX024 This cast is safe since we can never write INT_MAX bytes in a * single set of TLS operations. But it looks kinda ugly. If we refactor @@ -3518,12 +4007,34 @@ connection_handle_write_impl(connection_t *conn, int force) if (n_written && conn->type == CONN_TYPE_AP) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + circuit_t *circ = circuit_get_by_edge_conn(edge_conn); + origin_circuit_t *ocirc; /* Check for overflow: */ if (PREDICT_LIKELY(UINT32_MAX - edge_conn->n_written > n_written)) edge_conn->n_written += (int)n_written; else edge_conn->n_written = UINT32_MAX; + + if (circ && CIRCUIT_IS_ORIGIN(circ)) { + ocirc = TO_ORIGIN_CIRCUIT(circ); + if (PREDICT_LIKELY(UINT32_MAX - ocirc->n_written_circ_bw > n_written)) + ocirc->n_written_circ_bw += (int)n_written; + else + ocirc->n_written_circ_bw = UINT32_MAX; + } + } + + /* If CONN_BW events are enabled, update conn->n_written_conn_bw for + * OR/DIR/EXIT connections, checking for overflow. */ + if (n_written && get_options()->TestingEnableConnBwEvent && + (conn->type == CONN_TYPE_OR || + conn->type == CONN_TYPE_DIR || + conn->type == CONN_TYPE_EXIT)) { + if (PREDICT_LIKELY(UINT32_MAX - conn->n_written_conn_bw > n_written)) + conn->n_written_conn_bw += (int)n_written; + else + conn->n_written_conn_bw = UINT32_MAX; } connection_buckets_decrement(conn, approx_time(), n_read, n_written); @@ -3609,9 +4120,9 @@ connection_flush(connection_t *conn) * it all, so we don't end up with many megabytes of controller info queued at * once. */ -void -connection_write_to_buf_impl_(const char *string, size_t len, - connection_t *conn, int zlib) +MOCK_IMPL(void, +connection_write_to_buf_impl_,(const char *string, size_t len, + connection_t *conn, int zlib)) { /* XXXX This function really needs to return -1 on failure. */ int r; @@ -3656,6 +4167,12 @@ connection_write_to_buf_impl_(const char *string, size_t len, "write_to_buf failed. Closing circuit (fd %d).", (int)conn->s); circuit_mark_for_close(circuit_get_by_edge_conn(TO_EDGE_CONN(conn)), END_CIRC_REASON_INTERNAL); + } else if (conn->type == CONN_TYPE_OR) { + or_connection_t *orconn = TO_OR_CONN(conn); + log_warn(LD_NET, + "write_to_buf failed on an orconn; notifying of error " + "(fd %d)", (int)(conn->s)); + connection_or_close_for_error(orconn, 0); } else { log_warn(LD_NET, "write_to_buf failed. Closing connection (fd %d).", @@ -3830,20 +4347,29 @@ connection_dir_get_by_purpose_and_resource(int purpose, return NULL; } -/** Return an open, non-marked connection of a given type and purpose, or NULL - * if no such connection exists. */ -connection_t * -connection_get_by_type_purpose(int type, int purpose) +/** Return 1 if there are any active OR connections apart from + * <b>this_conn</b>. + * + * We use this to guess if we should tell the controller that we + * didn't manage to connect to any of our bridges. */ +int +any_other_active_or_conns(const or_connection_t *this_conn) { smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == type && - !conn->marked_for_close && - (purpose == conn->purpose)) - return conn; - }); - return NULL; + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + if (conn == TO_CONN(this_conn)) { /* don't consider this conn */ + continue; + } + + if (conn->type == CONN_TYPE_OR && + !conn->marked_for_close) { + log_debug(LD_DIR, "%s: Found an OR connection: %s", + __func__, conn->address); + return 1; + } + } SMARTLIST_FOREACH_END(conn); + + return 0; } /** Return 1 if <b>conn</b> is a listener conn, else return 0. */ @@ -3851,6 +4377,7 @@ int connection_is_listener(connection_t *conn) { if (conn->type == CONN_TYPE_OR_LISTENER || + conn->type == CONN_TYPE_EXT_OR_LISTENER || conn->type == CONN_TYPE_AP_LISTENER || conn->type == CONN_TYPE_AP_TRANS_LISTENER || conn->type == CONN_TYPE_AP_DNS_LISTENER || @@ -3873,6 +4400,7 @@ connection_state_is_open(connection_t *conn) return 0; if ((conn->type == CONN_TYPE_OR && conn->state == OR_CONN_STATE_OPEN) || + (conn->type == CONN_TYPE_EXT_OR) || (conn->type == CONN_TYPE_AP && conn->state == AP_CONN_STATE_OPEN) || (conn->type == CONN_TYPE_EXIT && conn->state == EXIT_CONN_STATE_OPEN) || (conn->type == CONN_TYPE_CONTROL && @@ -3999,6 +4527,8 @@ client_check_address_changed(tor_socket_t sock) SMARTLIST_FOREACH(outgoing_addrs, tor_addr_t*, a_ptr, tor_free(a_ptr)); smartlist_clear(outgoing_addrs); smartlist_add(outgoing_addrs, tor_memdup(&out_addr, sizeof(tor_addr_t))); + /* We'll need to resolve ourselves again. */ + reset_last_resolved_addr(); /* Okay, now change our keys. */ ip_address_changed(1); } @@ -4042,14 +4572,14 @@ connection_process_inbuf(connection_t *conn, int package_partial) switch (conn->type) { case CONN_TYPE_OR: return connection_or_process_inbuf(TO_OR_CONN(conn)); + case CONN_TYPE_EXT_OR: + return connection_ext_or_process_inbuf(TO_OR_CONN(conn)); case CONN_TYPE_EXIT: case CONN_TYPE_AP: return connection_edge_process_inbuf(TO_EDGE_CONN(conn), package_partial); case CONN_TYPE_DIR: return connection_dir_process_inbuf(TO_DIR_CONN(conn)); - case CONN_TYPE_CPUWORKER: - return connection_cpu_process_inbuf(conn); case CONN_TYPE_CONTROL: return connection_control_process_inbuf(TO_CONTROL_CONN(conn)); default: @@ -4102,13 +4632,13 @@ connection_finished_flushing(connection_t *conn) switch (conn->type) { case CONN_TYPE_OR: return connection_or_finished_flushing(TO_OR_CONN(conn)); + case CONN_TYPE_EXT_OR: + return connection_ext_or_finished_flushing(TO_OR_CONN(conn)); case CONN_TYPE_AP: case CONN_TYPE_EXIT: return connection_edge_finished_flushing(TO_EDGE_CONN(conn)); case CONN_TYPE_DIR: return connection_dir_finished_flushing(TO_DIR_CONN(conn)); - case CONN_TYPE_CPUWORKER: - return connection_cpu_finished_flushing(conn); case CONN_TYPE_CONTROL: return connection_control_finished_flushing(TO_CONTROL_CONN(conn)); default: @@ -4157,14 +4687,13 @@ connection_reached_eof(connection_t *conn) { switch (conn->type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: return connection_or_reached_eof(TO_OR_CONN(conn)); case CONN_TYPE_AP: case CONN_TYPE_EXIT: return connection_edge_reached_eof(TO_EDGE_CONN(conn)); case CONN_TYPE_DIR: return connection_dir_reached_eof(TO_DIR_CONN(conn)); - case CONN_TYPE_CPUWORKER: - return connection_cpu_reached_eof(conn); case CONN_TYPE_CONTROL: return connection_control_reached_eof(TO_CONTROL_CONN(conn)); default: @@ -4243,6 +4772,7 @@ assert_connection_ok(connection_t *conn, time_t now) switch (conn->type) { case CONN_TYPE_OR: + case CONN_TYPE_EXT_OR: tor_assert(conn->magic == OR_CONNECTION_MAGIC); break; case CONN_TYPE_AP: @@ -4348,6 +4878,10 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(conn->state >= OR_CONN_STATE_MIN_); tor_assert(conn->state <= OR_CONN_STATE_MAX_); break; + case CONN_TYPE_EXT_OR: + tor_assert(conn->state >= EXT_OR_CONN_STATE_MIN_); + tor_assert(conn->state <= EXT_OR_CONN_STATE_MAX_); + break; case CONN_TYPE_EXIT: tor_assert(conn->state >= EXIT_CONN_STATE_MIN_); tor_assert(conn->state <= EXIT_CONN_STATE_MAX_); @@ -4365,10 +4899,6 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(conn->purpose >= DIR_PURPOSE_MIN_); tor_assert(conn->purpose <= DIR_PURPOSE_MAX_); break; - case CONN_TYPE_CPUWORKER: - tor_assert(conn->state >= CPUWORKER_STATE_MIN_); - tor_assert(conn->state <= CPUWORKER_STATE_MAX_); - break; case CONN_TYPE_CONTROL: tor_assert(conn->state >= CONTROL_CONN_STATE_MIN_); tor_assert(conn->state <= CONTROL_CONN_STATE_MAX_); @@ -4390,6 +4920,27 @@ get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, { const or_options_t *options = get_options(); + /* Client Transport Plugins can use another proxy, but that should be hidden + * from the rest of tor (as the plugin is responsible for dealing with the + * proxy), check it first, then check the rest of the proxy types to allow + * the config to have unused ClientTransportPlugin entries. + */ + if (options->ClientTransportPlugin) { + const transport_t *transport = NULL; + int r; + r = get_transport_by_bridge_addrport(&conn->addr, conn->port, &transport); + if (r<0) + return -1; + if (transport) { /* transport found */ + tor_addr_copy(addr, &transport->addr); + *port = transport->port; + *proxy_type = transport->socks_version; + return 0; + } + + /* Unused ClientTransportPlugin. */ + } + if (options->HTTPSProxy) { tor_addr_copy(addr, &options->HTTPSProxyAddr); *port = options->HTTPSProxyPort; @@ -4405,43 +4956,14 @@ get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, *port = options->Socks5ProxyPort; *proxy_type = PROXY_SOCKS5; return 0; - } else if (options->ClientTransportPlugin || - options->Bridges) { - const transport_t *transport = NULL; - int r; - r = find_transport_by_bridge_addrport(&conn->addr, conn->port, &transport); - if (r<0) - return -1; - if (transport) { /* transport found */ - tor_addr_copy(addr, &transport->addr); - *port = transport->port; - *proxy_type = transport->socks_version; - return 0; - } } + tor_addr_make_unspec(addr); + *port = 0; *proxy_type = PROXY_NONE; return 0; } -/** Returns the global proxy type used by tor. */ -static int -get_proxy_type(void) -{ - const or_options_t *options = get_options(); - - if (options->HTTPSProxy) - return PROXY_CONNECT; - else if (options->Socks4Proxy) - return PROXY_SOCKS4; - else if (options->Socks5Proxy) - return PROXY_SOCKS5; - else if (options->ClientTransportPlugin) - return PROXY_PLUGGABLE; - else - return PROXY_NONE; -} - /** Log a failed connection to a proxy server. * <b>conn</b> is the connection we use the proxy server for. */ void @@ -4457,7 +4979,7 @@ log_failed_proxy_connection(connection_t *conn) log_warn(LD_NET, "The connection to the %s proxy server at %s just failed. " "Make sure that the proxy server is up and running.", - proxy_type_to_string(get_proxy_type()), + proxy_type_to_string(proxy_type), fmt_addrport(&proxy_addr, proxy_port)); } @@ -4477,9 +4999,7 @@ proxy_type_to_string(int proxy_type) } /** Call connection_free_() on every connection in our array, and release all - * storage held by connection.c. This is used by cpuworkers and dnsworkers - * when they fork, so they don't keep resources held open (especially - * sockets). + * storage held by connection.c. * * Don't do the checks in connection_free(), because they will * fail. @@ -4498,6 +5018,7 @@ connection_free_all(void) /* Unlink everything from the identity map. */ connection_or_clear_identity_map(); + connection_or_clear_ext_or_id_map(); /* Clear out our list of broken connections */ clear_broken_connection_map(0); diff --git a/src/or/connection.h b/src/or/connection.h index c78fe6e652..d0a34ece5c 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,9 +17,10 @@ const char *conn_type_to_string(int type); const char *conn_state_to_string(int type, int state); +int conn_listener_type_supports_af_unix(int type); dir_connection_t *dir_connection_new(int socket_family); -or_connection_t *or_connection_new(int socket_family); +or_connection_t *or_connection_new(int type, int socket_family); edge_connection_t *edge_connection_new(int type, int socket_family); entry_connection_t *entry_connection_new(int type, int socket_family); control_connection_t *control_connection_new(int socket_family); @@ -89,6 +90,21 @@ int connection_connect(connection_t *conn, const char *address, const tor_addr_t *addr, uint16_t port, int *socket_error); +#ifdef HAVE_SYS_UN_H + +int connection_connect_unix(connection_t *conn, const char *socket_path, + int *socket_error); + +#endif /* defined(HAVE_SYS_UN_H) */ + +/** Maximum size of information that we can fit into SOCKS5 username + or password fields. */ +#define MAX_SOCKS5_AUTH_FIELD_SIZE 255 + +/** Total maximum size of information that we can fit into SOCKS5 + username and password fields. */ +#define MAX_SOCKS5_AUTH_SIZE_TOTAL 2*MAX_SOCKS5_AUTH_FIELD_SIZE + int connection_proxy_connect(connection_t *conn, int type); int connection_read_proxy_handshake(connection_t *conn); void log_failed_proxy_connection(connection_t *conn); @@ -122,8 +138,8 @@ int connection_outbuf_too_full(connection_t *conn); int connection_handle_write(connection_t *conn, int force); int connection_flush(connection_t *conn); -void connection_write_to_buf_impl_(const char *string, size_t len, - connection_t *conn, int zlib); +MOCK_DECL(void, connection_write_to_buf_impl_, + (const char *string, size_t len, connection_t *conn, int zlib)); /* DOCDOC connection_write_to_buf */ static void connection_write_to_buf(const char *string, size_t len, connection_t *conn); @@ -170,7 +186,6 @@ connection_get_outbuf_len(connection_t *conn) connection_t *connection_get_by_global_id(uint64_t id); connection_t *connection_get_by_type(int type); -connection_t *connection_get_by_type_purpose(int type, int purpose); connection_t *connection_get_by_type_addr_port_purpose(int type, const tor_addr_t *addr, uint16_t port, int purpose); @@ -180,7 +195,10 @@ connection_t *connection_get_by_type_state_rendquery(int type, int state, 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 any_other_active_or_conns(const or_connection_t *this_conn); + +/* || 0 is for -Wparentheses-equality (-Wall?) appeasement under clang */ +#define connection_speaks_cells(conn) (((conn)->type == CONN_TYPE_OR) || 0) int connection_is_listener(connection_t *conn); int connection_state_is_open(connection_t *conn); int connection_state_is_connecting(connection_t *conn); @@ -206,5 +224,18 @@ void connection_enable_rate_limiting(connection_t *conn); #define connection_type_uses_bufferevent(c) (0) #endif +#ifdef CONNECTION_PRIVATE +STATIC void connection_free_(connection_t *conn); + +/* Used only by connection.c and test*.c */ +uint32_t bucket_millis_empty(int tokens_before, uint32_t last_empty_time, + int tokens_after, int milliseconds_elapsed, + const struct timeval *tvnow); +void connection_buckets_note_empty_ts(uint32_t *timestamp_var, + int tokens_before, + size_t tokens_removed, + const struct timeval *tvnow); +#endif + #endif diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 39f8af61f6..2a1a2f0fd2 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,6 +14,7 @@ #include "addressmap.h" #include "buffers.h" #include "channel.h" +#include "circpathbias.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" @@ -45,6 +46,19 @@ #ifdef HAVE_LINUX_NETFILTER_IPV4_H #include <linux/netfilter_ipv4.h> #define TRANS_NETFILTER +#define TRANS_NETFILTER_IPV4 +#endif + +#ifdef HAVE_LINUX_IF_H +#include <linux/if.h> +#endif + +#ifdef HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H +#include <linux/netfilter_ipv6/ip6_tables.h> +#if defined(IP6T_SO_ORIGINAL_DST) +#define TRANS_NETFILTER +#define TRANS_NETFILTER_IPV6 +#endif #endif #if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) @@ -53,6 +67,10 @@ #define TRANS_PF #endif +#ifdef IP_TRANSPARENT +#define TRANS_TPROXY +#endif + #define SOCKS4_GRANTED 90 #define SOCKS4_REJECT 91 @@ -61,19 +79,14 @@ static int connection_ap_process_natd(entry_connection_t *conn); static int connection_exit_connect_dir(edge_connection_t *exitconn); static int consider_plaintext_ports(entry_connection_t *conn, uint16_t port); static int connection_ap_supports_optimistic_data(const entry_connection_t *); -static void connection_ap_handshake_socks_resolved_addr( - entry_connection_t *conn, - const tor_addr_t *answer, - int ttl, - time_t expires); /** An AP stream has failed/finished. If it hasn't already sent back * a socks reply, send one now (based on endreason). Also set * has_sent_end to 1, and mark the conn. */ -void -connection_mark_unattached_ap_(entry_connection_t *conn, int endreason, - int line, const char *file) +MOCK_IMPL(void, +connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason, + int line, const char *file)) { connection_t *base_conn = ENTRY_TO_CONN(conn); edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); @@ -412,7 +425,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) * that the name resolution that led us to <b>addr</b> will be valid for * <b>ttl</b> seconds. Return -1 on error, or the number of bytes used on * success. */ -/* private */int +STATIC int connected_cell_format_payload(uint8_t *payload_out, const tor_addr_t *addr, uint32_t ttl) @@ -748,8 +761,9 @@ connection_ap_fail_onehop(const char *failed_digest, /* we don't know the digest; have to compare addr:port */ tor_addr_t addr; if (!build_state || !build_state->chosen_exit || - !entry_conn->socks_request) + !entry_conn->socks_request) { continue; + } if (tor_addr_parse(&addr, entry_conn->socks_request->address)<0 || !tor_addr_eq(&build_state->chosen_exit->addr, &addr) || build_state->chosen_exit->port != entry_conn->socks_request->port) @@ -898,78 +912,102 @@ connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, return connection_ap_handshake_rewrite_and_attach(conn, circ, cpath); } -/** Connection <b>conn</b> just finished its socks handshake, or the - * controller asked us to take care of it. If <b>circ</b> is defined, - * then that's where we'll want to attach it. Otherwise we have to - * figure it out ourselves. - * - * First, parse whether it's a .exit address, remap it, and so on. Then - * if it's for a general circuit, try to attach it to a circuit (or launch - * one as needed), else if it's for a rendezvous circuit, fetch a - * rendezvous descriptor first (or attach/launch a circuit if the - * rendezvous descriptor is already here and fresh enough). - * - * The stream will exit from the hop - * indicated by <b>cpath</b>, or from the last hop in circ's cpath if - * <b>cpath</b> is NULL. +/* Try to perform any map-based rewriting of the target address in + * <b>conn</b>, filling in the fields of <b>out</b> as we go, and modifying + * conn->socks_request.address as appropriate. */ -int -connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath) +STATIC void +connection_ap_handshake_rewrite(entry_connection_t *conn, + rewrite_result_t *out) { socks_request_t *socks = conn->socks_request; - hostname_type_t addresstype; const or_options_t *options = get_options(); tor_addr_t addr_tmp; - /* We set this to true if this is an address we should automatically - * remap to a local address in VirtualAddrNetwork */ - int automap = 0; - char orig_address[MAX_SOCKS_ADDR_LEN]; - time_t map_expires = TIME_MAX; - time_t now = time(NULL); - connection_t *base_conn = ENTRY_TO_CONN(conn); - addressmap_entry_source_t exit_source = ADDRMAPSRC_NONE; - tor_strlower(socks->address); /* normalize it */ - strlcpy(orig_address, socks->address, sizeof(orig_address)); + /* Initialize all the fields of 'out' to reasonable defaults */ + out->automap = 0; + out->exit_source = ADDRMAPSRC_NONE; + out->map_expires = TIME_MAX; + out->end_reason = 0; + out->should_close = 0; + out->orig_address[0] = 0; + + /* We convert all incoming addresses to lowercase. */ + tor_strlower(socks->address); + /* Remember the original address. */ + strlcpy(out->orig_address, socks->address, sizeof(out->orig_address)); log_debug(LD_APP,"Client asked for %s:%d", safe_str_client(socks->address), socks->port); + /* Check for whether this is a .exit address. By default, those are + * disallowed when they're coming straight from the client, but you're + * allowed to have them in MapAddress commands and so forth. */ if (!strcmpend(socks->address, ".exit") && !options->AllowDotExit) { log_warn(LD_APP, "The \".exit\" notation is disabled in Tor due to " "security risks. Set AllowDotExit in your torrc to enable " "it (at your own risk)."); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; + out->end_reason = END_STREAM_REASON_TORPROTOCOL; + out->should_close = 1; + return; } - if (! conn->original_dest_address) + /* Remember the original address so we can tell the user about what + * they actually said, not just what it turned into. */ + if (! conn->original_dest_address) { + /* Is the 'if' necessary here? XXXX */ conn->original_dest_address = tor_strdup(conn->socks_request->address); + } + /* First, apply MapAddress and MAPADDRESS mappings. We need to do + * these only for non-reverse lookups, since they don't exist for those. + * We need to do this before we consider automapping, since we might + * e.g. resolve irc.oftc.net into irconionaddress.onion, at which point + * we'd need to automap it. */ + if (socks->command != SOCKS_COMMAND_RESOLVE_PTR) { + const unsigned rewrite_flags = AMR_FLAG_USE_MAPADDRESS; + if (addressmap_rewrite(socks->address, sizeof(socks->address), + rewrite_flags, &out->map_expires, &out->exit_source)) { + control_event_stream_status(conn, STREAM_EVENT_REMAP, + REMAP_STREAM_SOURCE_CACHE); + } + } + + /* Now, handle automapping. Automapping happens when we're asked to + * resolve a hostname, and AutomapHostsOnResolve is set, and + * the hostname has a suffix listed in AutomapHostsSuffixes. + */ if (socks->command == SOCKS_COMMAND_RESOLVE && tor_addr_parse(&addr_tmp, socks->address)<0 && options->AutomapHostsOnResolve) { - automap = addressmap_address_should_automap(socks->address, options); - if (automap) { + /* Check the suffix... */ + out->automap = addressmap_address_should_automap(socks->address, options); + if (out->automap) { + /* If we get here, then we should apply an automapping for this. */ const char *new_addr; + /* We return an IPv4 address by default, or an IPv6 address if we + * are allowed to do so. */ int addr_type = RESOLVED_TYPE_IPV4; if (conn->socks_request->socks_version != 4) { - if (!conn->ipv4_traffic_ok || - (conn->ipv6_traffic_ok && conn->prefer_ipv6_traffic) || - conn->prefer_ipv6_virtaddr) + if (!conn->entry_cfg.ipv4_traffic || + (conn->entry_cfg.ipv6_traffic && conn->entry_cfg.prefer_ipv6) || + conn->entry_cfg.prefer_ipv6_virtaddr) addr_type = RESOLVED_TYPE_IPV6; } + /* Okay, register the target address as automapped, and find the new + * address we're supposed to give as a resolve answer. (Return a cached + * value if we've looked up this address before. + */ new_addr = addressmap_register_virtual_address( addr_type, tor_strdup(socks->address)); if (! new_addr) { log_warn(LD_APP, "Unable to automap address %s", escaped_safe_str(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_INTERNAL); - return -1; + out->end_reason = END_STREAM_REASON_INTERNAL; + out->should_close = 1; + return; } log_info(LD_APP, "Automapping %s to %s", escaped_safe_str_client(socks->address), @@ -978,28 +1016,35 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } + /* Now handle reverse lookups, if they're in the cache. This doesn't + * happen too often, since client-side DNS caching is off by default. */ if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) { unsigned rewrite_flags = 0; - if (conn->use_cached_ipv4_answers) + if (conn->entry_cfg.use_cached_ipv4_answers) rewrite_flags |= AMR_FLAG_USE_IPV4_DNS; - if (conn->use_cached_ipv6_answers) + if (conn->entry_cfg.use_cached_ipv6_answers) rewrite_flags |= AMR_FLAG_USE_IPV6_DNS; if (addressmap_rewrite_reverse(socks->address, sizeof(socks->address), - rewrite_flags, &map_expires)) { + rewrite_flags, &out->map_expires)) { char *result = tor_strdup(socks->address); /* remember _what_ is supposed to have been resolved. */ tor_snprintf(socks->address, sizeof(socks->address), "REVERSE[%s]", - orig_address); + out->orig_address); connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_HOSTNAME, strlen(result), (uint8_t*)result, -1, - map_expires); - connection_mark_unattached_ap(conn, - END_STREAM_REASON_DONE | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return 0; + out->map_expires); + tor_free(result); + out->end_reason = END_STREAM_REASON_DONE | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED; + out->should_close = 1; + return; } + + /* Hang on, did we find an answer saying that this is a reverse lookup for + * an internal address? If so, we should reject it if we're condigured to + * do so. */ if (options->ClientDNSRejectInternalAddresses) { /* Don't let people try to do a reverse lookup on 10.0.0.1. */ tor_addr_t addr; @@ -1009,43 +1054,108 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (ok == 1 && tor_addr_is_internal(&addr, 0)) { connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_ERROR, 0, NULL, -1, TIME_MAX); - connection_mark_unattached_ap(conn, - END_STREAM_REASON_SOCKSPROTOCOL | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return -1; + out->end_reason = END_STREAM_REASON_SOCKSPROTOCOL | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED; + out->should_close = 1; + return; } } - } else if (!automap) { - /* For address map controls, remap the address. */ - unsigned rewrite_flags = 0; - if (conn->use_cached_ipv4_answers) + } + + /* If we didn't automap it before, then this is still the address + * that came straight from the user, mapped according to any + * MapAddress/MAPADDRESS commands. Now other mappings, including + * previously registered Automap entries, TrackHostExits entries, + * and client-side DNS cache entries (not recommended). + */ + if (socks->command != SOCKS_COMMAND_RESOLVE_PTR && + !out->automap) { + unsigned rewrite_flags = AMR_FLAG_USE_AUTOMAP | AMR_FLAG_USE_TRACKEXIT; + addressmap_entry_source_t exit_source2; + if (conn->entry_cfg.use_cached_ipv4_answers) rewrite_flags |= AMR_FLAG_USE_IPV4_DNS; - if (conn->use_cached_ipv6_answers) + if (conn->entry_cfg.use_cached_ipv6_answers) rewrite_flags |= AMR_FLAG_USE_IPV6_DNS; if (addressmap_rewrite(socks->address, sizeof(socks->address), - rewrite_flags, &map_expires, &exit_source)) { + rewrite_flags, &out->map_expires, &exit_source2)) { control_event_stream_status(conn, STREAM_EVENT_REMAP, REMAP_STREAM_SOURCE_CACHE); } + if (out->exit_source == ADDRMAPSRC_NONE) { + /* If it wasn't a .exit before, maybe it turned into a .exit. Remember + * the original source of a .exit. */ + out->exit_source = exit_source2; + } } - if (!automap && address_is_in_virtual_range(socks->address)) { - /* This address was probably handed out by client_dns_get_unmapped_address, - * but the mapping was discarded for some reason. We *don't* want to send - * the address through Tor; that's likely to fail, and may leak - * information. + /* Check to see whether we're about to use an address in the virtual + * range without actually having gotten it from an Automap. */ + if (!out->automap && address_is_in_virtual_range(socks->address)) { + /* This address was probably handed out by + * client_dns_get_unmapped_address, but the mapping was discarded for some + * reason. Or the user typed in a virtual address range manually. We + * *don't* want to send the address through Tor; that's likely to fail, + * and may leak information. */ log_warn(LD_APP,"Missing mapping for virtual address '%s'. Refusing.", safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_INTERNAL); - return -1; + out->end_reason = END_STREAM_REASON_INTERNAL; + out->should_close = 1; + return; } +} + +/** Connection <b>conn</b> just finished its socks handshake, or the + * controller asked us to take care of it. If <b>circ</b> is defined, + * then that's where we'll want to attach it. Otherwise we have to + * figure it out ourselves. + * + * First, parse whether it's a .exit address, remap it, and so on. Then + * if it's for a general circuit, try to attach it to a circuit (or launch + * one as needed), else if it's for a rendezvous circuit, fetch a + * rendezvous descriptor first (or attach/launch a circuit if the + * rendezvous descriptor is already here and fresh enough). + * + * The stream will exit from the hop + * indicated by <b>cpath</b>, or from the last hop in circ's cpath if + * <b>cpath</b> is NULL. + */ +int +connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath) +{ + socks_request_t *socks = conn->socks_request; + const or_options_t *options = get_options(); + connection_t *base_conn = ENTRY_TO_CONN(conn); + time_t now = time(NULL); + rewrite_result_t rr; + + memset(&rr, 0, sizeof(rr)); + connection_ap_handshake_rewrite(conn,&rr); + + if (rr.should_close) { + /* connection_ap_handshake_rewrite told us to close the connection, + * either because it sent back an answer, or because it sent back an + * error */ + connection_mark_unattached_ap(conn, rr.end_reason); + if (END_STREAM_REASON_DONE == (rr.end_reason & END_STREAM_REASON_MASK)) + return 0; + else + return -1; + } + + const time_t map_expires = rr.map_expires; + const int automap = rr.automap; + const addressmap_entry_source_t exit_source = rr.exit_source; /* Parse the address provided by SOCKS. Modify it in-place if it * specifies a hidden-service (.onion) or particular exit node (.exit). */ - addresstype = parse_extended_hostname(socks->address); + const hostname_type_t addresstype = parse_extended_hostname(socks->address); + /* Now see whether the hostname is bogus. This could happen because of an + * onion hostname whose format we don't recognize. */ if (addresstype == BAD_HOSTNAME) { control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); @@ -1053,16 +1163,21 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* If this is a .exit hostname, strip off the .name.exit part, and + * see whether we're going to connect there, and otherwise handle it. + * (The ".exit" part got stripped off by "parse_extended_hostname"). + * + * We'll set chosen_exit_name and/or close the connection as appropriate. + */ if (addresstype == EXIT_HOSTNAME) { - /* foo.exit -- modify conn->chosen_exit_node to specify the exit - * node, and conn->address to hold only the address portion. */ - char *s = strrchr(socks->address,'.'); - - /* If StrictNodes is not set, then .exit overrides ExcludeNodes. */ + /* If StrictNodes is not set, then .exit overrides ExcludeNodes but + * not ExcludeExitNodes. */ routerset_t *excludeset = options->StrictNodes ? options->ExcludeExitNodesUnion_ : options->ExcludeExitNodes; - const node_t *node; + const node_t *node = NULL; + /* If this .exit was added by an AUTOMAP, then it came straight from + * a user. Make sure that options->AllowDotExit permits that. */ if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) { /* Whoops; this one is stale. It must have gotten added earlier, * when AllowDotExit was on. */ @@ -1075,6 +1190,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* Double-check to make sure there are no .exits coming from + * impossible/weird sources. */ if (exit_source == ADDRMAPSRC_DNS || (exit_source == ADDRMAPSRC_NONE && !options->AllowDotExit)) { /* It shouldn't be possible to get a .exit address from any of these @@ -1089,9 +1206,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } tor_assert(!automap); + /* Now, find the character before the .(name) part. */ + char *s = strrchr(socks->address,'.'); if (s) { /* The address was of the form "(stuff).(name).exit */ if (s[1] != '\0') { + /* Looks like a real .exit one. */ conn->chosen_exit_name = tor_strdup(s+1); node = node_get_by_nickname(conn->chosen_exit_name, 1); @@ -1110,7 +1230,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } } else { - /* It looks like they just asked for "foo.exit". */ + /* It looks like they just asked for "foo.exit". That's a special + * form that means (foo's address).foo.exit. */ conn->chosen_exit_name = tor_strdup(socks->address); node = node_get_by_nickname(conn->chosen_exit_name, 1); @@ -1119,6 +1240,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, node_get_address_string(node, socks->address, sizeof(socks->address)); } } + /* Now make sure that the chosen exit exists... */ if (!node) { log_warn(LD_APP, @@ -1140,8 +1262,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, implies no. */ } + /* Now, handle everything that isn't a .onion address. */ if (addresstype != ONION_HOSTNAME) { - /* not a hidden-service request (i.e. normal or .exit) */ + /* Not a hidden-service request. It's either a hostname or an IP, + * possibly with a .exit that we stripped off. */ + + /* Check for funny characters in the address. */ if (address_is_invalid_destination(socks->address, 1)) { control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); @@ -1152,6 +1278,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } +#ifdef ENABLE_TOR2WEB_MODE + /* If we're running in Tor2webMode, we don't allow anything BUT .onion + * addresses. */ if (options->Tor2webMode) { log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname %s " "because tor2web mode is enabled.", @@ -1159,13 +1288,17 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } +#endif + /* See if this is a hostname lookup that we can answer immediately. + * (For example, an attempt to look up the IP address for an IP address.) + */ if (socks->command == SOCKS_COMMAND_RESOLVE) { tor_addr_t answer; /* Reply to resolves immediately if we can. */ if (tor_addr_parse(&answer, socks->address) >= 0) {/* is it an IP? */ /* remember _what_ is supposed to have been resolved. */ - strlcpy(socks->address, orig_address, sizeof(socks->address)); + strlcpy(socks->address, rr.orig_address, sizeof(socks->address)); connection_ap_handshake_socks_resolved_addr(conn, &answer, -1, map_expires); connection_mark_unattached_ap(conn, @@ -1176,14 +1309,22 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, tor_assert(!automap); rep_hist_note_used_resolve(now); /* help predict this next time */ } else if (socks->command == SOCKS_COMMAND_CONNECT) { + /* Special handling for attempts to connect */ tor_assert(!automap); + /* Don't allow connections to port 0. */ if (socks->port == 0) { log_notice(LD_APP,"Application asked to connect to port 0. Refusing."); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return -1; } + /* You can't make connections to internal addresses, by default. + * Exceptions are begindir requests (where the address is meaningless, + * or cases where you've hand-configured a particular exit, thereby + * making the local address meaningful. */ if (options->ClientRejectInternalAddresses && !conn->use_begindir && !conn->chosen_exit_name && !circ) { + /* If we reach this point then we don't want to allow internal + * addresses. Check if we got one. */ tor_addr_t addr; if (tor_addr_hostname_is_local(socks->address) || (tor_addr_parse(&addr, socks->address) >= 0 && @@ -1218,39 +1359,58 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, connection_mark_unattached_ap(conn, END_STREAM_REASON_PRIVATE_ADDR); return -1; } - } + } /* end "if we should check for internal addresses" */ + /* Okay. We're still doing a CONNECT, and it wasn't a private + * address. Do special handling for literal IP addresses */ { tor_addr_t addr; /* XXX Duplicate call to tor_addr_parse. */ if (tor_addr_parse(&addr, socks->address) >= 0) { + /* If we reach this point, it's an IPv4 or an IPv6 address. */ sa_family_t family = tor_addr_family(&addr); - if ((family == AF_INET && ! conn->ipv4_traffic_ok) || - (family == AF_INET6 && ! conn->ipv4_traffic_ok)) { + + if ((family == AF_INET && ! conn->entry_cfg.ipv4_traffic) || + (family == AF_INET6 && ! conn->entry_cfg.ipv6_traffic)) { + /* You can't do an IPv4 address on a v6-only socks listener, + * or vice versa. */ log_warn(LD_NET, "Rejecting SOCKS request for an IP address " "family that this listener does not support."); connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } else if (family == AF_INET6 && socks->socks_version == 4) { + /* You can't make a socks4 request to an IPv6 address. Socks4 + * doesn't support that. */ log_warn(LD_NET, "Rejecting SOCKS4 request for an IPv6 address."); connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; - } else if (socks->socks_version == 4 && !conn->ipv4_traffic_ok) { + } else if (socks->socks_version == 4 && + !conn->entry_cfg.ipv4_traffic) { + /* You can't do any kind of Socks4 request when IPv4 is forbidden. + * + * XXX raise this check outside the enclosing block? */ log_warn(LD_NET, "Rejecting SOCKS4 request on a listener with " "no IPv4 traffic supported."); connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } else if (family == AF_INET6) { - conn->ipv4_traffic_ok = 0; + /* Tell the exit: we won't accept any ipv4 connection to an IPv6 + * address. */ + conn->entry_cfg.ipv4_traffic = 0; } else if (family == AF_INET) { - conn->ipv6_traffic_ok = 0; + /* Tell the exit: we won't accept any ipv6 connection to an IPv4 + * address. */ + conn->entry_cfg.ipv6_traffic = 0; } } } if (socks->socks_version == 4) - conn->ipv6_traffic_ok = 0; + conn->entry_cfg.ipv6_traffic = 0; + /* Still handling CONNECT. Now, check for exit enclaves. (Which we + * don't do on BEGINDIR, or there is a chosen exit.) + */ if (!conn->use_begindir && !conn->chosen_exit_name && !circ) { /* see if we can find a suitable enclave exit */ const node_t *r = @@ -1267,11 +1427,13 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } - /* warn or reject if it's using a dangerous port */ + /* Still handling CONNECT: warn or reject if it's using a dangerous + * port. */ if (!conn->use_begindir && !conn->chosen_exit_name && !circ) if (consider_plaintext_ports(conn, socks->port) < 0) return -1; + /* Remember the port so that we do predicted requests there. */ if (!conn->use_begindir) { /* help predict this next time */ rep_hist_note_used_port(now, socks->port); @@ -1280,25 +1442,41 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, rep_hist_note_used_resolve(now); /* help predict this next time */ /* no extra processing needed */ } else { + /* We should only be doing CONNECT or RESOLVE! */ tor_fragile_assert(); } + + /* Okay. At this point we've set chosen_exit_name if needed, rewritten the + * address, and decided not to reject it for any number of reasons. Now + * mark the connection as waiting for a circuit, and try to attach it! + */ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - if ((circ && connection_ap_handshake_attach_chosen_circuit( - conn, circ, cpath) < 0) || - (!circ && - connection_ap_handshake_attach_circuit(conn) < 0)) { + + /* If we were given a circuit to attach to, try to attach. Otherwise, + * try to find a good one and attach to that. */ + int rv; + if (circ) + rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath); + else + rv = connection_ap_handshake_attach_circuit(conn); + + /* If the above function returned 0 then we're waiting for a circuit. + * if it returned 1, we're attached. Both are okay. But if it returned + * -1, there was an error, so make sure the connection is marked, and + * return -1. */ + if (rv < 0) { if (!base_conn->marked_for_close) connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); return -1; } + return 0; } else { - /* it's a hidden-service request */ - rend_cache_entry_t *entry; - int r; - rend_service_authorization_t *client_auth; - rend_data_t *rend_data; + /* If we get here, it's a request for a .onion address! */ tor_assert(!automap); + + /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those + * for hidden service addresses. */ if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { /* if it's a resolve request, fail it right now, rather than * building all the circuits and then realizing it won't work. */ @@ -1312,6 +1490,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* If we were passed a circuit, then we need to fail. .onion addresses + * only work when we launch our own circuits for now. */ if (circ) { log_warn(LD_CONTROL, "Attachstream to a circuit is not " "supported for .onion addresses currently. Failing."); @@ -1319,15 +1499,22 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } - ENTRY_TO_EDGE_CONN(conn)->rend_data = rend_data = + /* Fill in the rend_data field so we can start doing a connection to + * a hidden service. */ + rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = tor_malloc_zero(sizeof(rend_data_t)); strlcpy(rend_data->onion_address, socks->address, sizeof(rend_data->onion_address)); log_info(LD_REND,"Got a hidden service request for ID '%s'", safe_str_client(rend_data->onion_address)); - /* see if we already have it cached */ - r = rend_cache_lookup_entry(rend_data->onion_address, -1, &entry); - if (r<0) { + + /* see if we already have a hidden service descriptor cached for this + * address. */ + rend_cache_entry_t *entry = NULL; + const int rend_cache_lookup_result = + rend_cache_lookup_entry(rend_data->onion_address, -1, &entry); + if (rend_cache_lookup_result < 0) { + /* We should already have rejected this address! */ log_warn(LD_BUG,"Invalid service name '%s'", safe_str_client(rend_data->onion_address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); @@ -1338,8 +1525,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, * a stable circuit yet, but we know we'll need *something*. */ rep_hist_note_used_internal(now, 0, 1); - /* Look up if we have client authorization for it. */ - client_auth = rend_client_lookup_service_authorization( + /* Look up if we have client authorization configured for this hidden + * service. If we do, associate it with the rend_data. */ + rend_service_authorization_t *client_auth = + rend_client_lookup_service_authorization( rend_data->onion_address); if (client_auth) { log_info(LD_REND, "Using previously configured client authorization " @@ -1348,12 +1537,16 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, client_auth->descriptor_cookie, REND_DESC_COOKIE_LEN); rend_data->auth_type = client_auth->auth_type; } - if (r==0) { + + /* Now, we either launch an attempt to connect to the hidden service, + * or we launch an attempt to look up its descriptor, depending on + * whether we had the descriptor. */ + if (rend_cache_lookup_result == 0) { base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; log_info(LD_REND, "Unknown descriptor %s. Fetching.", safe_str_client(rend_data->onion_address)); rend_client_refetch_v2_renddesc(rend_data); - } else { /* r > 0 */ + } else { /* rend_cache_lookup_result > 0 */ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; log_info(LD_REND, "Descriptor is here. Great."); if (connection_ap_handshake_attach_circuit(conn) < 0) { @@ -1364,6 +1557,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } return 0; } + return 0; /* unreached but keeps the compiler happy */ } @@ -1395,35 +1589,82 @@ get_pf_socket(void) } #endif -/** Fetch the original destination address and port from a - * system-specific interface and put them into a - * socks_request_t as if they came from a socks request. - * - * Return -1 if an error prevents fetching the destination, - * else return 0. - */ +#if defined(TRANS_NETFILTER) || defined(TRANS_PF) || defined(TRANS_TPROXY) +/** Try fill in the address of <b>req</b> from the socket configured + * with <b>conn</b>. */ static int -connection_ap_get_original_destination(entry_connection_t *conn, - socks_request_t *req) +destination_from_socket(entry_connection_t *conn, socks_request_t *req) { -#ifdef TRANS_NETFILTER - /* Linux 2.4+ */ struct sockaddr_storage orig_dst; socklen_t orig_dst_len = sizeof(orig_dst); tor_addr_t addr; - if (getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IP, SO_ORIGINAL_DST, - (struct sockaddr*)&orig_dst, &orig_dst_len) < 0) { +#ifdef TRANS_TRPOXY + if (options->TransProxyType_parsed == TPT_TPROXY) { + if (getsockname(ENTRY_TO_CONN(conn)->s, (struct sockaddr*)&orig_dst, + &orig_dst_len) < 0) { + int e = tor_socket_errno(ENTRY_TO_CONN(conn)->s); + log_warn(LD_NET, "getsockname() failed: %s", tor_socket_strerror(e)); + return -1; + } + goto done; + } +#endif + +#ifdef TRANS_NETFILTER + int rv = -1; + switch (ENTRY_TO_CONN(conn)->socket_family) { +#ifdef TRANS_NETFILTER_IPV4 + case AF_INET: + rv = getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IP, SO_ORIGINAL_DST, + (struct sockaddr*)&orig_dst, &orig_dst_len); + break; +#endif +#ifdef TRANS_NETFILTER_IPV6 + case AF_INET6: + rv = getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IPV6, IP6T_SO_ORIGINAL_DST, + (struct sockaddr*)&orig_dst, &orig_dst_len); + break; +#endif + default: + log_warn(LD_BUG, + "Received transparent data from an unsuported socket family %d", + ENTRY_TO_CONN(conn)->socket_family); + return -1; + } + if (rv < 0) { int e = tor_socket_errno(ENTRY_TO_CONN(conn)->s); log_warn(LD_NET, "getsockopt() failed: %s", tor_socket_strerror(e)); return -1; } + goto done; +#elif defined(TRANS_PF) + if (getsockname(ENTRY_TO_CONN(conn)->s, (struct sockaddr*)&orig_dst, + &orig_dst_len) < 0) { + int e = tor_socket_errno(ENTRY_TO_CONN(conn)->s); + log_warn(LD_NET, "getsockname() failed: %s", tor_socket_strerror(e)); + return -1; + } + goto done; +#else + (void)conn; + (void)req; + log_warn(LD_BUG, "Unable to determine destination from socket."); + return -1; +#endif + done: tor_addr_from_sockaddr(&addr, (struct sockaddr*)&orig_dst, &req->port); tor_addr_to_str(req->address, &addr, sizeof(req->address), 1); return 0; -#elif defined(TRANS_PF) +} +#endif + +#ifdef TRANS_PF +static int +destination_from_pf(entry_connection_t *conn, socks_request_t *req) +{ struct sockaddr_storage proxy_addr; socklen_t proxy_addr_len = sizeof(proxy_addr); struct sockaddr *proxy_sa = (struct sockaddr*) &proxy_addr; @@ -1439,6 +1680,21 @@ connection_ap_get_original_destination(entry_connection_t *conn, return -1; } +#ifdef __FreeBSD__ + if (get_options()->TransProxyType_parsed == TPT_IPFW) { + /* ipfw(8) is used and in this case getsockname returned the original + destination */ + if (tor_addr_from_sockaddr(&addr, proxy_sa, &req->port) < 0) { + tor_fragile_assert(); + return -1; + } + + tor_addr_to_str(req->address, &addr, sizeof(req->address), 0); + + return 0; + } +#endif + memset(&pnl, 0, sizeof(pnl)); pnl.proto = IPPROTO_TCP; pnl.direction = PF_OUT; @@ -1485,6 +1741,37 @@ connection_ap_get_original_destination(entry_connection_t *conn, req->port = ntohs(pnl.rdport); return 0; +} +#endif + +/** Fetch the original destination address and port from a + * system-specific interface and put them into a + * socks_request_t as if they came from a socks request. + * + * Return -1 if an error prevents fetching the destination, + * else return 0. + */ +static int +connection_ap_get_original_destination(entry_connection_t *conn, + socks_request_t *req) +{ +#ifdef TRANS_NETFILTER + return destination_from_socket(conn, req); +#elif defined(TRANS_PF) + const or_options_t *options = get_options(); + + if (options->TransProxyType_parsed == TPT_PF_DIVERT) + return destination_from_socket(conn, req); + + if (options->TransProxyType_parsed == TPT_DEFAULT || + options->TransProxyType_parsed == TPT_IPFW) + return destination_from_pf(conn, req); + + (void)conn; + (void)req; + log_warn(LD_BUG, "Proxy destination determination mechanism %s unknown.", + options->TransProxyType); + return -1; #else (void)conn; (void)req; @@ -1713,7 +2000,8 @@ connection_ap_supports_optimistic_data(const entry_connection_t *conn) general circuit. */ if (edge_conn->on_circuit == NULL || edge_conn->on_circuit->state != CIRCUIT_STATE_OPEN || - edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_GENERAL) + (edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_GENERAL && + edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_REND_JOINED)) return 0; return conn->may_use_optimistic_data; @@ -1738,19 +2026,19 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) return 0; /* If only IPv4 is supported, no flags */ - if (ap_conn->ipv4_traffic_ok && !ap_conn->ipv6_traffic_ok) + if (ap_conn->entry_cfg.ipv4_traffic && !ap_conn->entry_cfg.ipv6_traffic) return 0; if (! cpath_layer || ! cpath_layer->extend_info) return 0; - if (!ap_conn->ipv4_traffic_ok) + if (!ap_conn->entry_cfg.ipv4_traffic) flags |= BEGIN_FLAG_IPV4_NOT_OK; exitnode = node_get_by_id(cpath_layer->extend_info->identity_digest); - if (ap_conn->ipv6_traffic_ok && exitnode) { + if (ap_conn->entry_cfg.ipv6_traffic && exitnode) { tor_addr_t a; tor_addr_make_null(&a, AF_INET6); if (compare_tor_addr_to_node_policy(&a, ap_conn->socks_request->port, @@ -1765,7 +2053,7 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) if (flags == BEGIN_FLAG_IPV6_OK) { /* When IPv4 and IPv6 are both allowed, consider whether to say we * prefer IPv6. Otherwise there's no point in declaring a preference */ - if (ap_conn->prefer_ipv6_traffic) + if (ap_conn->entry_cfg.prefer_ipv6) flags |= BEGIN_FLAG_IPV6_PREFERRED; } @@ -2002,8 +2290,8 @@ connection_ap_make_link(connection_t *partner, /* Populate isolation fields. */ conn->socks_request->listener_type = CONN_TYPE_DIR_LISTENER; conn->original_dest_address = tor_strdup(address); - conn->session_group = session_group; - conn->isolation_flags = isolation_flags; + conn->entry_cfg.session_group = session_group; + conn->entry_cfg.isolation_flags = isolation_flags; base_conn->address = tor_strdup("(Tor_internal)"); tor_addr_make_unspec(&base_conn->addr); @@ -2064,7 +2352,7 @@ tell_controller_about_resolved_result(entry_connection_t *conn, * As connection_ap_handshake_socks_resolved, but take a tor_addr_t to send * as the answer. */ -static void +void connection_ap_handshake_socks_resolved_addr(entry_connection_t *conn, const tor_addr_t *answer, int ttl, @@ -2097,13 +2385,13 @@ connection_ap_handshake_socks_resolved_addr(entry_connection_t *conn, **/ /* XXXX the use of the ttl and expires fields is nutty. Let's make this * interface and those that use it less ugly. */ -void -connection_ap_handshake_socks_resolved(entry_connection_t *conn, +MOCK_IMPL(void, +connection_ap_handshake_socks_resolved,(entry_connection_t *conn, int answer_type, size_t answer_len, const uint8_t *answer, int ttl, - time_t expires) + time_t expires)) { char buf[384]; size_t replylen; @@ -2241,13 +2529,21 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, endreason == END_STREAM_REASON_RESOURCELIMIT) { if (!conn->edge_.on_circuit || !CIRCUIT_IS_ORIGIN(conn->edge_.on_circuit)) { - // DNS remaps can trigger this. So can failed hidden service - // lookups. - log_info(LD_BUG, - "No origin circuit for successful SOCKS stream "U64_FORMAT - ". Reason: %d", - U64_PRINTF_ARG(ENTRY_TO_CONN(conn)->global_identifier), - endreason); + if (endreason != END_STREAM_REASON_RESOLVEFAILED) { + log_info(LD_BUG, + "No origin circuit for successful SOCKS stream "U64_FORMAT + ". Reason: %d", + U64_PRINTF_ARG(ENTRY_TO_CONN(conn)->global_identifier), + endreason); + } + /* + * Else DNS remaps and failed hidden service lookups can send us + * here with END_STREAM_REASON_RESOLVEFAILED; ignore it + * + * Perhaps we could make the test more precise; we can tell hidden + * services by conn->edge_.renddata != NULL; anything analogous for + * the DNS remap case? + */ } else { // XXX: Hrmm. It looks like optimistic data can't go through this // codepath, but someone should probably test it and make sure. @@ -2272,13 +2568,24 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, /* leave version, destport, destip zero */ connection_write_to_buf(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); } else if (conn->socks_request->socks_version == 5) { - buf[0] = 5; /* version 5 */ - buf[1] = (char)status; - buf[2] = 0; - buf[3] = 1; /* ipv4 addr */ - memset(buf+4,0,6); /* Set external addr/port to 0. - The spec doesn't seem to say what to do here. -RD */ - connection_write_to_buf(buf,10,ENTRY_TO_CONN(conn)); + size_t buf_len; + memset(buf,0,sizeof(buf)); + if (tor_addr_family(&conn->edge_.base_.addr) == AF_INET) { + buf[0] = 5; /* version 5 */ + buf[1] = (char)status; + buf[2] = 0; + buf[3] = 1; /* ipv4 addr */ + /* 4 bytes for the header, 2 bytes for the port, 4 for the address. */ + buf_len = 10; + } else { /* AF_INET6. */ + buf[0] = 5; /* version 5 */ + buf[1] = (char)status; + buf[2] = 0; + buf[3] = 4; /* ipv6 addr */ + /* 4 bytes for the header, 2 bytes for the port, 16 for the address. */ + buf_len = 22; + } + connection_write_to_buf(buf,buf_len,ENTRY_TO_CONN(conn)); } /* If socks_version isn't 4 or 5, don't send anything. * This can happen in the case of AP bridges. */ @@ -2294,7 +2601,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, * Return -1 in the case where want to send a RELAY_END cell, and < -1 when * we don't. **/ -/* static */ int +STATIC int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, uint8_t *end_reason_out) { @@ -2387,7 +2694,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) relay_header_unpack(&rh, cell->payload); if (rh.length > RELAY_PAYLOAD_SIZE) - return -1; + return -END_CIRC_REASON_TORPROTOCOL; /* Note: we have to use relay_send_command_from_edge here, not * connection_edge_end or connection_edge_send_command, since those require @@ -2405,7 +2712,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) r = begin_cell_parse(cell, &bcell, &end_reason); if (r < -1) { - return -1; + return -END_CIRC_REASON_TORPROTOCOL; } else if (r == -1) { tor_free(bcell.address); relay_send_end_cell_from_edge(rh.stream_id, circ, end_reason, NULL); @@ -2503,15 +2810,31 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->rend_data = rend_data_dup(origin_circ->rend_data); tor_assert(connection_edge_is_rendezvous_stream(n_stream)); assert_circuit_ok(circ); - if (rend_service_set_connection_addr_port(n_stream, origin_circ) < 0) { + + const int r = rend_service_set_connection_addr_port(n_stream, origin_circ); + if (r < 0) { log_info(LD_REND,"Didn't find rendezvous service (port %d)", n_stream->base_.port); + /* Send back reason DONE because we want to make hidden service port + * scanning harder thus instead of returning that the exit policy + * didn't match, which makes it obvious that the port is closed, + * return DONE and kill the circuit. That way, a user (malicious or + * not) needs one circuit per bad port unless it matches the policy of + * the hidden service. */ relay_send_end_cell_from_edge(rh.stream_id, circ, - END_STREAM_REASON_EXITPOLICY, + END_STREAM_REASON_DONE, origin_circ->cpath->prev); connection_free(TO_CONN(n_stream)); tor_free(address); - return 0; + + /* Drop the circuit here since it might be someone deliberately + * scanning the hidden service ports. Note that this mitigates port + * scanning by adding more work on the attacker side to successfully + * scan but does not fully solve it. */ + if (r < -1) + return END_CIRC_AT_ORIGIN; + else + return 0; } assert_circuit_ok(circ); log_debug(LD_REND,"Finished assigning addr/port"); @@ -2639,7 +2962,7 @@ connection_exit_connect(edge_connection_t *edge_conn) const tor_addr_t *addr; uint16_t port; connection_t *conn = TO_CONN(edge_conn); - int socket_error = 0; + int socket_error = 0, result; if ( (!connection_edge_is_rendezvous_stream(edge_conn) && router_compare_to_my_exit_policy(&edge_conn->base_.addr, @@ -2654,14 +2977,36 @@ connection_exit_connect(edge_connection_t *edge_conn) return; } - addr = &conn->addr; - port = conn->port; +#ifdef HAVE_SYS_UN_H + if (conn->socket_family != AF_UNIX) { +#else + { +#endif /* defined(HAVE_SYS_UN_H) */ + addr = &conn->addr; + port = conn->port; + + if (tor_addr_family(addr) == AF_INET6) + conn->socket_family = AF_INET6; + + log_debug(LD_EXIT, "about to try connecting"); + result = connection_connect(conn, conn->address, + addr, port, &socket_error); +#ifdef HAVE_SYS_UN_H + } else { + /* + * In the AF_UNIX case, we expect to have already had conn->port = 1, + * tor_addr_make_unspec(conn->addr) (cf. the way we mark in the incoming + * case in connection_handle_listener_read()), and conn->address should + * have the socket path to connect to. + */ + tor_assert(conn->address && strlen(conn->address) > 0); - if (tor_addr_family(addr) == AF_INET6) - conn->socket_family = AF_INET6; + log_debug(LD_EXIT, "about to try connecting"); + result = connection_connect_unix(conn, conn->address, &socket_error); +#endif /* defined(HAVE_SYS_UN_H) */ + } - log_debug(LD_EXIT,"about to try connecting"); - switch (connection_connect(conn, conn->address, addr, port, &socket_error)) { + switch (result) { case -1: { int reason = errno_to_stream_end_reason(socket_error); connection_edge_end(edge_conn, reason); @@ -2691,7 +3036,6 @@ connection_exit_connect(edge_connection_t *edge_conn) /* also, deliver a 'connected' cell back through the circuit. */ if (connection_edge_is_rendezvous_stream(edge_conn)) { - /* rendezvous stream */ /* don't send an address back! */ connection_edge_send_command(edge_conn, RELAY_COMMAND_CONNECTED, @@ -2830,10 +3174,10 @@ connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit) addr_policy_result_t r; if (0 == tor_addr_parse(&addr, conn->socks_request->address)) { addrp = &addr; - } else if (!conn->ipv4_traffic_ok && conn->ipv6_traffic_ok) { + } else if (!conn->entry_cfg.ipv4_traffic && conn->entry_cfg.ipv6_traffic) { tor_addr_make_null(&addr, AF_INET6); addrp = &addr; - } else if (conn->ipv4_traffic_ok && !conn->ipv6_traffic_ok) { + } else if (conn->entry_cfg.ipv4_traffic && !conn->entry_cfg.ipv6_traffic) { tor_addr_make_null(&addr, AF_INET); addrp = &addr; } @@ -2939,7 +3283,7 @@ int connection_edge_compatible_with_circuit(const entry_connection_t *conn, const origin_circuit_t *circ) { - const uint8_t iso = conn->isolation_flags; + const uint8_t iso = conn->entry_cfg.isolation_flags; const socks_request_t *sr = conn->socks_request; /* If circ has never been used for an isolated connection, we can @@ -2988,7 +3332,8 @@ connection_edge_compatible_with_circuit(const entry_connection_t *conn, if ((iso & ISO_CLIENTADDR) && !tor_addr_eq(&ENTRY_TO_CONN(conn)->addr, &circ->client_addr)) return 0; - if ((iso & ISO_SESSIONGRP) && conn->session_group != circ->session_group) + if ((iso & ISO_SESSIONGRP) && + conn->entry_cfg.session_group != circ->session_group) return 0; if ((iso & ISO_NYM_EPOCH) && conn->nym_epoch != circ->nym_epoch) return 0; @@ -3027,7 +3372,7 @@ connection_edge_update_circuit_isolation(const entry_connection_t *conn, circ->client_proto_type = conn->socks_request->listener_type; circ->client_proto_socksver = conn->socks_request->socks_version; tor_addr_copy(&circ->client_addr, &ENTRY_TO_CONN(conn)->addr); - circ->session_group = conn->session_group; + circ->session_group = conn->entry_cfg.session_group; circ->nym_epoch = conn->nym_epoch; circ->socks_username = sr->username ? tor_memdup(sr->username, sr->usernamelen) : NULL; @@ -3054,7 +3399,7 @@ connection_edge_update_circuit_isolation(const entry_connection_t *conn, mixed |= ISO_CLIENTPROTO; if (!tor_addr_eq(&ENTRY_TO_CONN(conn)->addr, &circ->client_addr)) mixed |= ISO_CLIENTADDR; - if (conn->session_group != circ->session_group) + if (conn->entry_cfg.session_group != circ->session_group) mixed |= ISO_SESSIONGRP; if (conn->nym_epoch != circ->nym_epoch) mixed |= ISO_NYM_EPOCH; @@ -3062,7 +3407,7 @@ connection_edge_update_circuit_isolation(const entry_connection_t *conn, if (dry_run) return mixed; - if ((mixed & conn->isolation_flags) != 0) { + if ((mixed & conn->entry_cfg.isolation_flags) != 0) { log_warn(LD_BUG, "Updating a circuit with seemingly incompatible " "isolation flags."); } diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index ea284cbcfd..7c0b9c0767 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,11 +12,14 @@ #ifndef TOR_CONNECTION_EDGE_H #define TOR_CONNECTION_EDGE_H +#include "testsupport.h" + #define connection_mark_unattached_ap(conn, endreason) \ connection_mark_unattached_ap_((conn), (endreason), __LINE__, SHORT_FILE__) -void connection_mark_unattached_ap_(entry_connection_t *conn, int endreason, - int line, const char *file); +MOCK_DECL(void,connection_mark_unattached_ap_, + (entry_connection_t *conn, int endreason, + int line, const char *file)); int connection_edge_reached_eof(edge_connection_t *conn); int connection_edge_process_inbuf(edge_connection_t *conn, int package_partial); @@ -42,12 +45,17 @@ entry_connection_t *connection_ap_make_link(connection_t *partner, void connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, size_t replylen, int endreason); -void connection_ap_handshake_socks_resolved(entry_connection_t *conn, - int answer_type, - size_t answer_len, - const uint8_t *answer, - int ttl, - time_t expires); +MOCK_DECL(void,connection_ap_handshake_socks_resolved, + (entry_connection_t *conn, + int answer_type, + size_t answer_len, + const uint8_t *answer, + int ttl, + time_t expires)); +void connection_ap_handshake_socks_resolved_addr(entry_connection_t *conn, + const tor_addr_t *answer, + int ttl, + time_t expires); int connection_exit_begin_conn(cell_t *cell, circuit_t *circ); int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ); @@ -130,11 +138,35 @@ typedef struct begin_cell_t { unsigned is_begindir : 1; } begin_cell_t; -int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, +STATIC int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, uint8_t *end_reason_out); -int connected_cell_format_payload(uint8_t *payload_out, +STATIC int connected_cell_format_payload(uint8_t *payload_out, const tor_addr_t *addr, uint32_t ttl); + +typedef struct { + /** Original address, after we lowercased it but before we started + * mapping it. + */ + char orig_address[MAX_SOCKS_ADDR_LEN]; + /** True iff the address has been automatically remapped to a local + * address in VirtualAddrNetwork. (Only set true when we do a resolve + * and get a virtual address; not when we connect to the address.) */ + int automap; + /** If this connection has a .exit address, who put it there? */ + addressmap_entry_source_t exit_source; + /** If we've rewritten the address, when does this map expire? */ + time_t map_expires; + /** If we should close the connection, this is the end_reason to pass + * to connection_mark_unattached_ap */ + int end_reason; + /** True iff we should close the connection, either because of error or + * because of successful early RESOLVED reply. */ + int should_close; +} rewrite_result_t; + +STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn, + rewrite_result_t *out); #endif #endif diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 8e7cd9ea51..e0dff1c915 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -37,6 +37,8 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" +#include "ext_orport.h" +#include "scheduler.h" #ifdef USE_BUFFEREVENTS #include <event2/bufferevent_ssl.h> @@ -75,6 +77,10 @@ static void connection_or_handle_event_cb(struct bufferevent *bufev, * they form a linked list, with next_with_same_id as the next pointer. */ static digestmap_t *orconn_identity_map = NULL; +/** Global map between Extended ORPort identifiers and OR + * connections. */ +static digestmap_t *orconn_ext_or_id_map = NULL; + /** If conn is listed in orconn_identity_map, remove it, and clear * conn->identity_digest. Otherwise do nothing. */ void @@ -174,6 +180,71 @@ connection_or_set_identity_digest(or_connection_t *conn, const char *digest) #endif } +/** Remove the Extended ORPort identifier of <b>conn</b> from the + * global identifier list. Also, clear the identifier from the + * connection itself. */ +void +connection_or_remove_from_ext_or_id_map(or_connection_t *conn) +{ + or_connection_t *tmp; + if (!orconn_ext_or_id_map) + return; + if (!conn->ext_or_conn_id) + return; + + tmp = digestmap_remove(orconn_ext_or_id_map, conn->ext_or_conn_id); + if (!tor_digest_is_zero(conn->ext_or_conn_id)) + tor_assert(tmp == conn); + + memset(conn->ext_or_conn_id, 0, EXT_OR_CONN_ID_LEN); +} + +/** Return the connection whose ext_or_id is <b>id</b>. Return NULL if no such + * connection is found. */ +or_connection_t * +connection_or_get_by_ext_or_id(const char *id) +{ + if (!orconn_ext_or_id_map) + return NULL; + return digestmap_get(orconn_ext_or_id_map, id); +} + +/** Deallocate the global Extended ORPort identifier list */ +void +connection_or_clear_ext_or_id_map(void) +{ + digestmap_free(orconn_ext_or_id_map, NULL); + orconn_ext_or_id_map = NULL; +} + +/** Creates an Extended ORPort identifier for <b>conn</b> and deposits + * it into the global list of identifiers. */ +void +connection_or_set_ext_or_identifier(or_connection_t *conn) +{ + char random_id[EXT_OR_CONN_ID_LEN]; + or_connection_t *tmp; + + if (!orconn_ext_or_id_map) + orconn_ext_or_id_map = digestmap_new(); + + /* Remove any previous identifiers: */ + if (conn->ext_or_conn_id && !tor_digest_is_zero(conn->ext_or_conn_id)) + connection_or_remove_from_ext_or_id_map(conn); + + do { + crypto_rand(random_id, sizeof(random_id)); + } while (digestmap_get(orconn_ext_or_id_map, random_id)); + + if (!conn->ext_or_conn_id) + conn->ext_or_conn_id = tor_malloc_zero(EXT_OR_CONN_ID_LEN); + + memcpy(conn->ext_or_conn_id, random_id, EXT_OR_CONN_ID_LEN); + + tmp = digestmap_set(orconn_ext_or_id_map, random_id, conn); + tor_assert(!tmp); +} + /**************************************************************/ /** Map from a string describing what a non-open OR connection was doing when @@ -228,7 +299,7 @@ connection_or_get_state_description(or_connection_t *orconn, const char *conn_state; char tls_state[256]; - tor_assert(conn->type == CONN_TYPE_OR); + tor_assert(conn->type == CONN_TYPE_OR || conn->type == CONN_TYPE_EXT_OR); conn_state = conn_state_to_string(conn->type, conn->state); tor_tls_get_state_description(orconn->tls, tls_state, sizeof(tls_state)); @@ -505,48 +576,51 @@ connection_or_process_inbuf(or_connection_t *conn) return ret; } -/** When adding cells to an OR connection's outbuf, keep adding until the - * outbuf is at least this long, or we run out of cells. */ -#define OR_CONN_HIGHWATER (32*1024) - -/** Add cells to an OR connection's outbuf whenever the outbuf's data length - * drops below this size. */ -#define OR_CONN_LOWWATER (16*1024) - /** Called whenever we have flushed some data on an or_conn: add more data * from active circuits. */ int connection_or_flushed_some(or_connection_t *conn) { - size_t datalen, temp; - ssize_t n, flushed; - size_t cell_network_size = get_cell_network_size(conn->wide_circ_ids); + size_t datalen; + + /* The channel will want to update its estimated queue size */ + channel_update_xmit_queue_size(TLS_CHAN_TO_BASE(conn->chan)); /* If we're under the low water mark, add cells until we're just over the * high water mark. */ datalen = connection_get_outbuf_len(TO_CONN(conn)); if (datalen < OR_CONN_LOWWATER) { - while ((conn->chan) && channel_tls_more_to_flush(conn->chan)) { - /* Compute how many more cells we want at most */ - n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, cell_network_size); - /* Bail out if we don't want any more */ - if (n <= 0) break; - /* We're still here; try to flush some more cells */ - flushed = channel_tls_flush_some_cells(conn->chan, n); - /* Bail out if it says it didn't flush anything */ - if (flushed <= 0) break; - /* How much in the outbuf now? */ - temp = connection_get_outbuf_len(TO_CONN(conn)); - /* Bail out if we didn't actually increase the outbuf size */ - if (temp <= datalen) break; - /* Update datalen for the next iteration */ - datalen = temp; - } + /* Let the scheduler know */ + scheduler_channel_wants_writes(TLS_CHAN_TO_BASE(conn->chan)); } return 0; } +/** This is for channeltls.c to ask how many cells we could accept if + * they were available. */ +ssize_t +connection_or_num_cells_writeable(or_connection_t *conn) +{ + size_t datalen, cell_network_size; + ssize_t n = 0; + + tor_assert(conn); + + /* + * If we're under the high water mark, we're potentially + * writeable; note this is different from the calculation above + * used to trigger when to start writing after we've stopped. + */ + datalen = connection_get_outbuf_len(TO_CONN(conn)); + if (datalen < OR_CONN_HIGHWATER) { + cell_network_size = get_cell_network_size(conn->wide_circ_ids); + n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, cell_network_size); + } + + return n; +} + /** Connection <b>conn</b> has finished writing and has no bytes left on * its outbuf. * @@ -645,7 +719,8 @@ connection_or_about_to_close(or_connection_t *or_conn) reason); if (!authdir_mode_tests_reachability(options)) control_event_bootstrap_problem( - orconn_end_reason_to_control_string(reason), reason); + orconn_end_reason_to_control_string(reason), + reason, or_conn); } } } else if (conn->hold_open_until_flushed) { @@ -756,6 +831,45 @@ connection_or_update_token_buckets(smartlist_t *conns, }); } +/** How long do we wait before killing non-canonical OR connections with no + * circuits? In Tor versions up to 0.2.1.25 and 0.2.2.12-alpha, we waited 15 + * minutes before cancelling these connections, which caused fast relays to + * accrue many many idle connections. Hopefully 3-4.5 minutes is low enough + * that it kills most idle connections, without being so low that we cause + * clients to bounce on and off. + * + * For canonical connections, the limit is higher, at 15-22.5 minutes. + * + * For each OR connection, we randomly add up to 50% extra to its idle_timeout + * field, to avoid exposing when exactly the last circuit closed. Since we're + * storing idle_timeout in a uint16_t, don't let these values get higher than + * 12 hours or so without revising connection_or_set_canonical and/or expanding + * idle_timeout. + */ +#define IDLE_OR_CONN_TIMEOUT_NONCANONICAL 180 +#define IDLE_OR_CONN_TIMEOUT_CANONICAL 900 + +/* Mark <b>or_conn</b> as canonical if <b>is_canonical</b> is set, and + * non-canonical otherwise. Adjust idle_timeout accordingly. + */ +void +connection_or_set_canonical(or_connection_t *or_conn, + int is_canonical) +{ + const unsigned int timeout_base = is_canonical ? + IDLE_OR_CONN_TIMEOUT_CANONICAL : IDLE_OR_CONN_TIMEOUT_NONCANONICAL; + + if (bool_eq(is_canonical, or_conn->is_canonical) && + or_conn->idle_timeout != 0) { + /* Don't recalculate an existing idle_timeout unless the canonical + * status changed. */ + return; + } + + or_conn->is_canonical = !! is_canonical; /* force to a 1-bit boolean */ + or_conn->idle_timeout = timeout_base + crypto_rand_int(timeout_base / 2); +} + /** If we don't necessarily know the router we're connecting to, but we * have an addr/port/id_digest, then fill in as much as we can. Start * by checking to see if this describes a router we know. @@ -780,7 +894,7 @@ connection_or_init_conn_from_address(or_connection_t *conn, /* XXXX proposal 186 is making this more complex. For now, a conn is canonical when it uses the _preferred_ address. */ if (tor_addr_eq(&conn->base_.addr, &node_ap.addr)) - conn->is_canonical = 1; + connection_or_set_canonical(conn, 1); if (!started_here) { /* Override the addr/port, so our log messages will make sense. * This is dangerous, since if we ever try looking up a conn by @@ -799,21 +913,23 @@ connection_or_init_conn_from_address(or_connection_t *conn, tor_free(conn->base_.address); conn->base_.address = tor_dup_addr(&node_ap.addr); } else { - const char *n; - /* If we're an authoritative directory server, we may know a - * nickname for this router. */ - n = dirserv_get_nickname_by_digest(id_digest); - if (n) { - conn->nickname = tor_strdup(n); - } else { - conn->nickname = tor_malloc(HEX_DIGEST_LEN+2); - conn->nickname[0] = '$'; - base16_encode(conn->nickname+1, HEX_DIGEST_LEN+1, - conn->identity_digest, DIGEST_LEN); - } + conn->nickname = tor_malloc(HEX_DIGEST_LEN+2); + conn->nickname[0] = '$'; + base16_encode(conn->nickname+1, HEX_DIGEST_LEN+1, + conn->identity_digest, DIGEST_LEN); + tor_free(conn->base_.address); conn->base_.address = tor_dup_addr(addr); } + + /* + * We have to tell channeltls.c to update the channel marks (local, in + * particular), since we may have changed the address. + */ + + if (conn->chan) { + channel_tls_update_marks(conn); + } } /** These just pass all the is_bad_for_new_circs manipulation on to @@ -1008,7 +1124,7 @@ connection_or_connect_failed(or_connection_t *conn, { control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, reason); if (!authdir_mode_tests_reachability(get_options())) - control_event_bootstrap_problem(msg, reason); + control_event_bootstrap_problem(msg, reason, conn); } /** <b>conn</b> got an error in connection_handle_read_impl() or @@ -1033,9 +1149,7 @@ connection_or_notify_error(or_connection_t *conn, if (conn->chan) { chan = TLS_CHAN_TO_BASE(conn->chan); /* Don't transition if we're already in closing, closed or error */ - if (!(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR)) { + if (!CHANNEL_CONDEMNED(chan)) { channel_close_for_error(chan); } } @@ -1058,10 +1172,10 @@ connection_or_notify_error(or_connection_t *conn, * * Return the launched conn, or NULL if it failed. */ -or_connection_t * -connection_or_connect(const tor_addr_t *_addr, uint16_t port, - const char *id_digest, - channel_tls_t *chan) + +MOCK_IMPL(or_connection_t *, +connection_or_connect, (const tor_addr_t *_addr, uint16_t port, + const char *id_digest, channel_tls_t *chan)) { or_connection_t *conn; const or_options_t *options = get_options(); @@ -1082,7 +1196,7 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, return NULL; } - conn = or_connection_new(tor_addr_family(&addr)); + conn = or_connection_new(CONN_TYPE_OR, tor_addr_family(&addr)); /* * Set up conn so it's got all the data we need to remember for channels @@ -1125,6 +1239,12 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, "your pluggable transport proxy stopped running.", fmt_addrport(&TO_CONN(conn)->addr, TO_CONN(conn)->port), transport_name, transport_name); + + control_event_bootstrap_problem( + "Can't connect to bridge", + END_OR_CONN_REASON_PT_MISSING, + conn); + } else { log_warn(LD_GENERAL, "Tried to connect to '%s' through a proxy, but " "the proxy address could not be found.", @@ -1188,9 +1308,7 @@ connection_or_close_normally(or_connection_t *orconn, int flush) if (orconn->chan) { chan = TLS_CHAN_TO_BASE(orconn->chan); /* Don't transition if we're already in closing, closed or error */ - if (!(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR)) { + if (!CHANNEL_CONDEMNED(chan)) { channel_close_from_lower_layer(chan); } } @@ -1211,9 +1329,7 @@ connection_or_close_for_error(or_connection_t *orconn, int flush) if (orconn->chan) { chan = TLS_CHAN_TO_BASE(orconn->chan); /* Don't transition if we're already in closing, closed or error */ - if (!(chan->state == CHANNEL_STATE_CLOSING || - chan->state == CHANNEL_STATE_CLOSED || - chan->state == CHANNEL_STATE_ERROR)) { + if (!CHANNEL_CONDEMNED(chan)) { channel_close_for_error(chan); } } @@ -1227,8 +1343,8 @@ connection_or_close_for_error(or_connection_t *orconn, int flush) * * Return -1 if <b>conn</b> is broken, else return 0. */ -int -connection_tls_start_handshake(or_connection_t *conn, int receiving) +MOCK_IMPL(int, +connection_tls_start_handshake,(or_connection_t *conn, int receiving)) { channel_listener_t *chan_listener; channel_t *chan; @@ -1485,7 +1601,8 @@ connection_or_handle_event_cb(struct bufferevent *bufev, short event, int connection_or_nonopen_was_started_here(or_connection_t *conn) { - tor_assert(conn->base_.type == CONN_TYPE_OR); + tor_assert(conn->base_.type == CONN_TYPE_OR || + conn->base_.type == CONN_TYPE_EXT_OR); if (!conn->tls) return 1; /* it's still in proxy states or something */ if (conn->handshake_state) @@ -1638,7 +1755,8 @@ connection_or_client_learned_peer_id(or_connection_t *conn, if (!authdir_mode_tests_reachability(options)) control_event_bootstrap_problem( "Unexpected identity in router certificate", - END_OR_CONN_REASON_OR_IDENTITY); + END_OR_CONN_REASON_OR_IDENTITY, + conn); return -1; } if (authdir_mode_tests_reachability(options)) { @@ -1688,13 +1806,11 @@ connection_tls_finish_handshake(or_connection_t *conn) safe_str_client(conn->base_.address), tor_tls_get_ciphersuite_name(conn->tls)); - directory_set_dirty(); - if (connection_or_check_valid_tls_handshake(conn, started_here, digest_rcvd) < 0) return -1; - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); if (tor_tls_used_v1_handshake(conn->tls)) { conn->link_proto = 1; @@ -1703,6 +1819,7 @@ connection_tls_finish_handshake(or_connection_t *conn) conn->base_.port, digest_rcvd, 0); } tor_tls_block_renegotiation(conn->tls); + rep_hist_note_negotiated_link_proto(1, started_here); return connection_or_set_state_open(conn); } else { connection_or_change_state(conn, OR_CONN_STATE_OR_HANDSHAKING_V2); @@ -1728,7 +1845,7 @@ connection_or_launch_v3_or_handshake(or_connection_t *conn) tor_assert(connection_or_nonopen_was_started_here(conn)); tor_assert(tor_tls_received_v3_certificate(conn->tls)); - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); connection_or_change_state(conn, OR_CONN_STATE_OR_HANDSHAKING_V3); if (connection_init_or_handshake_state(conn, 1) < 0) @@ -1890,9 +2007,6 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn) if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_cell(conn, conn->handshake_state, cell, 0); - - if (cell->command != CELL_PADDING) - conn->timestamp_last_added_nonpadding = approx_time(); } /** Pack a variable-length <b>cell</b> into wire-format, and write it onto @@ -1913,8 +2027,6 @@ connection_or_write_var_cell_to_buf(const var_cell_t *cell, cell->payload_len, TO_CONN(conn)); if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_var_cell(conn, conn->handshake_state, cell, 0); - if (cell->command != CELL_PADDING) - conn->timestamp_last_added_nonpadding = approx_time(); /* Touch the channel's active timestamp if there is one */ if (conn->chan) @@ -1961,7 +2073,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) if (conn->chan) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); channel_tls_handle_var_cell(var_cell, conn); var_cell_free(var_cell); } else { @@ -1977,7 +2089,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) if (conn->chan) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); - circuit_build_times_network_is_live(&circ_times); + circuit_build_times_network_is_live(get_circuit_build_times_mutable()); connection_fetch_from_buf(buf, cell_network_size, TO_CONN(conn)); /* retrieve cell info from buf (create the host-order struct from the diff --git a/src/or/connection_or.h b/src/or/connection_or.h index 85e68f1a33..fc261c6bac 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -24,6 +24,7 @@ void connection_or_set_bad_connections(const char *digest, int force); void connection_or_block_renegotiation(or_connection_t *conn); int connection_or_reached_eof(or_connection_t *conn); int connection_or_process_inbuf(or_connection_t *conn); +ssize_t connection_or_num_cells_writeable(or_connection_t *conn); int connection_or_flushed_some(or_connection_t *conn); int connection_or_finished_flushing(or_connection_t *conn); int connection_or_finished_connecting(or_connection_t *conn); @@ -36,17 +37,21 @@ void connection_or_connect_failed(or_connection_t *conn, int reason, const char *msg); void connection_or_notify_error(or_connection_t *conn, int reason, const char *msg); -or_connection_t *connection_or_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest, - channel_tls_t *chan); +MOCK_DECL(or_connection_t *, + connection_or_connect, + (const tor_addr_t *addr, uint16_t port, + const char *id_digest, channel_tls_t *chan)); void connection_or_close_normally(or_connection_t *orconn, int flush); void connection_or_close_for_error(or_connection_t *orconn, int flush); void connection_or_report_broken_states(int severity, int domain); -int connection_tls_start_handshake(or_connection_t *conn, int receiving); +MOCK_DECL(int,connection_tls_start_handshake,(or_connection_t *conn, + int receiving)); int connection_tls_continue_handshake(or_connection_t *conn); +void connection_or_set_canonical(or_connection_t *or_conn, + int is_canonical); int connection_init_or_handshake_state(or_connection_t *conn, int started_here); diff --git a/src/or/control.c b/src/or/control.c index ae9dd69d21..e25c3b2954 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,6 +19,7 @@ #include "circuitlist.h" #include "circuitstats.h" #include "circuituse.h" +#include "command.h" #include "config.h" #include "confparse.h" #include "connection.h" @@ -46,52 +47,20 @@ #include <sys/resource.h> #endif +#include "crypto_s2k.h" #include "procmon.h" /** 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) -/* Recognized asynchronous event types. It's okay to expand this list - * because it is used both as a list of v0 event types, and as indices - * into the bitfield to determine which controllers want which events. - */ -#define EVENT_MIN_ 0x0001 -#define EVENT_CIRCUIT_STATUS 0x0001 -#define EVENT_STREAM_STATUS 0x0002 -#define EVENT_OR_CONN_STATUS 0x0003 -#define EVENT_BANDWIDTH_USED 0x0004 -#define EVENT_CIRCUIT_STATUS_MINOR 0x0005 -#define EVENT_NEW_DESC 0x0006 -#define EVENT_DEBUG_MSG 0x0007 -#define EVENT_INFO_MSG 0x0008 -#define EVENT_NOTICE_MSG 0x0009 -#define EVENT_WARN_MSG 0x000A -#define EVENT_ERR_MSG 0x000B -#define EVENT_ADDRMAP 0x000C -// #define EVENT_AUTHDIR_NEWDESCS 0x000D -#define EVENT_DESCCHANGED 0x000E -// #define EVENT_NS 0x000F -#define EVENT_STATUS_CLIENT 0x0010 -#define EVENT_STATUS_SERVER 0x0011 -#define EVENT_STATUS_GENERAL 0x0012 -#define EVENT_GUARD 0x0013 -#define EVENT_STREAM_BANDWIDTH_USED 0x0014 -#define EVENT_CLIENTS_SEEN 0x0015 -#define EVENT_NEWCONSENSUS 0x0016 -#define EVENT_BUILDTIMEOUT_SET 0x0017 -#define EVENT_SIGNAL 0x0018 -#define EVENT_CONF_CHANGED 0x0019 -#define EVENT_MAX_ 0x0019 -/* If EVENT_MAX_ ever hits 0x0020, we need to make the mask wider. */ - /** Bitfield: The bit 1<<e is set if <b>any</b> open control * connection is interested in events of type <b>e</b>. We use this * so that we can decide to skip generating event messages that nobody * has interest in without having to walk over the global connection * list to find out. **/ -typedef uint32_t event_mask_t; +typedef uint64_t event_mask_t; /** An event mask of all the events that any controller is interested in * receiving. */ @@ -103,7 +72,7 @@ static int disable_log_messages = 0; /** Macro: true if any control connection is interested in events of type * <b>e</b>. */ #define EVENT_IS_INTERESTING(e) \ - (global_event_mask & (1<<(e))) + (!! (global_event_mask & (((uint64_t)1)<<(e)))) /** If we're using cookie-type authentication, how long should our cookies be? */ @@ -115,7 +84,7 @@ static int authentication_cookie_is_set = 0; /** If authentication_cookie_is_set, a secret cookie that we've stored to disk * and which we're using to authenticate controllers. (If the controller can * read it off disk, it has permission to connect.) */ -static char authentication_cookie[AUTHENTICATION_COOKIE_LEN]; +static uint8_t *authentication_cookie = NULL; #define SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT \ "Tor safe cookie authentication server-to-controller hash" @@ -130,15 +99,6 @@ static char authentication_cookie[AUTHENTICATION_COOKIE_LEN]; * of this so we can respond to getinfo status/bootstrap-phase queries. */ static char last_sent_bootstrap_message[BOOTSTRAP_MSG_LEN]; -/** Flag for event_format_t. Indicates that we should use the one standard - format. - */ -#define ALL_FORMATS 1 - -/** Bit field of flags to select how to format a controller event. Recognized - * flag is ALL_FORMATS. */ -typedef int event_format_t; - static void connection_printf_to_buf(control_connection_t *conn, const char *format, ...) CHECK_PRINTF(2,3); @@ -201,7 +161,6 @@ static int write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len); static void orconn_target_get_name(char *buf, size_t len, or_connection_t *conn); -static char *get_cookie_file(void); /** Given a control event code for a message event, return the corresponding * log severity. */ @@ -232,6 +191,20 @@ log_severity_to_event(int severity) } } +/** Helper: clear bandwidth counters of all origin circuits. */ +static void +clear_circ_bw_fields(void) +{ + origin_circuit_t *ocirc; + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!CIRCUIT_IS_ORIGIN(circ)) + continue; + ocirc = TO_ORIGIN_CIRCUIT(circ); + ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; + } + SMARTLIST_FOREACH_END(circ); +} + /** Set <b>global_event_mask*</b> to the bitwise OR of each live control * connection's event_mask field. */ void @@ -257,8 +230,8 @@ control_update_global_event_mask(void) * we want to hear...*/ control_adjust_event_log_severity(); - /* ...then, if we've started logging stream bw, clear the appropriate - * fields. */ + /* ...then, if we've started logging stream or circ bw, clear the + * appropriate fields. */ if (! (old_mask & EVENT_STREAM_BANDWIDTH_USED) && (new_mask & EVENT_STREAM_BANDWIDTH_USED)) { SMARTLIST_FOREACH(conns, connection_t *, conn, @@ -269,6 +242,10 @@ control_update_global_event_mask(void) } }); } + if (! (old_mask & EVENT_CIRC_BANDWIDTH_USED) && + (new_mask & EVENT_CIRC_BANDWIDTH_USED)) { + clear_circ_bw_fields(); + } } /** Adjust the log severities that result in control_event_logmsg being called @@ -334,7 +311,7 @@ connection_write_str_to_buf(const char *s, control_connection_t *conn) * the end. Replace all LF characters sequences with CRLF. Return the number * of bytes in *<b>out</b>. */ -/* static */ size_t +STATIC size_t write_escaped_data(const char *data, size_t len, char **out) { size_t sz_out = len+8; @@ -382,7 +359,7 @@ write_escaped_data(const char *data, size_t len, char **out) * that appears at the start of a line, and replacing all CRLF sequences * with LF. Return the number of * bytes in *<b>out</b>. */ -/* static */ size_t +STATIC size_t read_escaped_data(const char *data, size_t len, char **out) { char *outp; @@ -592,9 +569,9 @@ send_control_done(control_connection_t *conn) * * The EXTENDED_FORMAT and NONEXTENDED_FORMAT flags behave similarly with * respect to the EXTENDED_EVENTS feature. */ -static void -send_control_event_string(uint16_t event, event_format_t which, - const char *msg) +MOCK_IMPL(STATIC void, +send_control_event_string,(uint16_t event, event_format_t which, + const char *msg)) { smartlist_t *conns = get_connection_array(); (void)which; @@ -606,7 +583,7 @@ send_control_event_string(uint16_t event, event_format_t which, conn->state == CONTROL_CONN_STATE_OPEN) { control_connection_t *control_conn = TO_CONTROL_CONN(conn); - if (control_conn->event_mask & (1<<event)) { + if (control_conn->event_mask & (((event_mask_t)1)<<event)) { int is_err = 0; connection_write_to_buf(msg, strlen(msg), TO_CONN(control_conn)); if (event == EVENT_ERR_MSG) @@ -958,6 +935,12 @@ static const struct control_event_t control_event_table[] = { { EVENT_BUILDTIMEOUT_SET, "BUILDTIMEOUT_SET" }, { EVENT_SIGNAL, "SIGNAL" }, { EVENT_CONF_CHANGED, "CONF_CHANGED"}, + { EVENT_CONN_BW, "CONN_BW" }, + { EVENT_CELL_STATS, "CELL_STATS" }, + { EVENT_TB_EMPTY, "TB_EMPTY" }, + { EVENT_CIRC_BANDWIDTH_USED, "CIRC_BW" }, + { EVENT_TRANSPORT_LAUNCHED, "TRANSPORT_LAUNCHED" }, + { EVENT_HS_DESC, "HS_DESC" }, { 0, NULL }, }; @@ -967,8 +950,8 @@ static int handle_control_setevents(control_connection_t *conn, uint32_t len, const char *body) { - int event_code = -1; - uint32_t event_mask = 0; + int event_code; + event_mask_t event_mask = 0; smartlist_t *events = smartlist_new(); (void) len; @@ -981,6 +964,8 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, continue; } else { int i; + event_code = -1; + for (i = 0; control_event_table[i].event_name != NULL; ++i) { if (!strcasecmp(ev, control_event_table[i].event_name)) { event_code = control_event_table[i].event_code; @@ -996,7 +981,7 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, return 0; } } - event_mask |= (1 << event_code); + event_mask |= (((event_mask_t)1) << event_code); } SMARTLIST_FOREACH_END(ev); SMARTLIST_FOREACH(events, char *, e, tor_free(e)); @@ -1011,7 +996,8 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, /** Decode the hashed, base64'd passwords stored in <b>passwords</b>. * Return a smartlist of acceptable passwords (unterminated strings of - * length S2K_SPECIFIER_LEN+DIGEST_LEN) on success, or NULL on failure. + * length S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) on success, or NULL on + * failure. */ smartlist_t * decode_hashed_passwords(config_line_t *passwords) @@ -1027,16 +1013,17 @@ decode_hashed_passwords(config_line_t *passwords) if (!strcmpstart(hashed, "16:")) { if (base16_decode(decoded, sizeof(decoded), hashed+3, strlen(hashed+3))<0 - || strlen(hashed+3) != (S2K_SPECIFIER_LEN+DIGEST_LEN)*2) { + || strlen(hashed+3) != (S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)*2) { goto err; } } else { if (base64_decode(decoded, sizeof(decoded), hashed, strlen(hashed)) - != S2K_SPECIFIER_LEN+DIGEST_LEN) { + != S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) { goto err; } } - smartlist_add(sl, tor_memdup(decoded, S2K_SPECIFIER_LEN+DIGEST_LEN)); + smartlist_add(sl, + tor_memdup(decoded, S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)); } return sl; @@ -1057,7 +1044,7 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len, { int used_quoted_string = 0; const or_options_t *options = get_options(); - const char *errstr = NULL; + const char *errstr = "Unknown error"; char *password; size_t password_len; const char *cp; @@ -1178,22 +1165,27 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len, } if (bad) { if (!also_cookie) { - log_warn(LD_CONTROL, + log_warn(LD_BUG, "Couldn't decode HashedControlPassword: invalid base16"); errstr="Couldn't decode HashedControlPassword value in configuration."; + goto err; } bad_password = 1; SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); smartlist_free(sl); + sl = NULL; } else { SMARTLIST_FOREACH(sl, char *, expected, { - secret_to_key(received,DIGEST_LEN,password,password_len,expected); - if (tor_memeq(expected+S2K_SPECIFIER_LEN, received, DIGEST_LEN)) + secret_to_key_rfc2440(received,DIGEST_LEN, + password,password_len,expected); + if (tor_memeq(expected + S2K_RFC2440_SPECIFIER_LEN, + received, DIGEST_LEN)) goto ok; }); SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); smartlist_free(sl); + sl = NULL; if (used_quoted_string) errstr = "Password did not match HashedControlPassword value from " @@ -1216,9 +1208,12 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len, err: tor_free(password); - connection_printf_to_buf(conn, "515 Authentication failed: %s\r\n", - errstr ? errstr : "Unknown reason."); + connection_printf_to_buf(conn, "515 Authentication failed: %s\r\n", errstr); connection_mark_for_close(TO_CONN(conn)); + if (sl) { /* clean up */ + SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); + smartlist_free(sl); + } return 0; ok: log_info(LD_CONTROL, "Authenticated control connection ("TOR_SOCKET_T_FORMAT @@ -1268,6 +1263,7 @@ static const struct signal_t signal_table[] = { { SIGTERM, "INT" }, { SIGNEWNYM, "NEWNYM" }, { SIGCLEARDNSCACHE, "CLEARDNSCACHE"}, + { SIGHEARTBEAT, "HEARTBEAT"}, { 0, NULL }, }; @@ -1442,12 +1438,18 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, (void) conn; if (!strcmp(question, "version")) { *answer = tor_strdup(get_version()); + } else if (!strcmp(question, "bw-event-cache")) { + *answer = get_bw_samples(); } else if (!strcmp(question, "config-file")) { - *answer = tor_strdup(get_torrc_fname(0)); + const char *a = get_torrc_fname(0); + if (a) + *answer = tor_strdup(a); } else if (!strcmp(question, "config-defaults-file")) { - *answer = tor_strdup(get_torrc_fname(1)); + const char *a = get_torrc_fname(1); + if (a) + *answer = tor_strdup(a); } else if (!strcmp(question, "config-text")) { - *answer = options_dump(get_options(), 1); + *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL); } else if (!strcmp(question, "info/names")) { *answer = list_getinfo_options(); } else if (!strcmp(question, "dormant")) { @@ -1509,7 +1511,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, *answer = tor_strdup(""); #else int myUid = geteuid(); - struct passwd *myPwEntry = getpwuid(myUid); + const struct passwd *myPwEntry = tor_getpwuid(myUid); if (myPwEntry) { *answer = tor_strdup(myPwEntry->pw_name); @@ -1521,6 +1523,9 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, int max_fds=-1; set_max_file_descriptors(0, &max_fds); tor_asprintf(answer, "%d", max_fds); + } else if (!strcmp(question, "limits/max-mem-in-queues")) { + tor_asprintf(answer, U64_FORMAT, + U64_PRINTF_ARG(get_options()->MaxMemInQueues)); } else if (!strcmp(question, "dir-usage")) { *answer = directory_dump_request_log(); } else if (!strcmp(question, "fingerprint")) { @@ -1567,12 +1572,13 @@ munge_extrainfo_into_routerinfo(const char *ri_body, outp += router_sig-ri_body; for (i=0; i < 2; ++i) { - const char *kwd = i?"\nwrite-history ":"\nread-history "; + const char *kwd = i ? "\nwrite-history " : "\nread-history "; const char *cp, *eol; if (!(cp = tor_memstr(ei_body, ei_len, kwd))) continue; ++cp; - eol = memchr(cp, '\n', ei_len - (cp-ei_body)); + if (!(eol = memchr(cp, '\n', ei_len - (cp-ei_body)))) + continue; memcpy(outp, cp, eol-cp+1); outp += eol-cp+1; } @@ -1764,39 +1770,7 @@ getinfo_helper_dir(control_connection_t *control_conn, tor_free(url); smartlist_free(descs); } else if (!strcmpstart(question, "dir/status/")) { - if (directory_permits_controller_requests(get_options())) { - size_t len=0; - char *cp; - smartlist_t *status_list = smartlist_new(); - dirserv_get_networkstatus_v2(status_list, - question+strlen("dir/status/")); - SMARTLIST_FOREACH(status_list, cached_dir_t *, d, len += d->dir_len); - cp = *answer = tor_malloc(len+1); - SMARTLIST_FOREACH(status_list, cached_dir_t *, d, { - memcpy(cp, d->dir, d->dir_len); - cp += d->dir_len; - }); - *cp = '\0'; - smartlist_free(status_list); - } else { - smartlist_t *fp_list = smartlist_new(); - smartlist_t *status_list = smartlist_new(); - dirserv_get_networkstatus_v2_fingerprints( - fp_list, question+strlen("dir/status/")); - SMARTLIST_FOREACH(fp_list, const char *, fp, { - char *s; - char *fname = networkstatus_get_cache_filename(fp); - s = read_file_to_str(fname, 0, NULL); - if (s) - smartlist_add(status_list, s); - tor_free(fname); - }); - SMARTLIST_FOREACH(fp_list, char *, fp, tor_free(fp)); - smartlist_free(fp_list); - *answer = smartlist_join_strings(status_list, "", 0, NULL); - SMARTLIST_FOREACH(status_list, char *, s, tor_free(s)); - smartlist_free(status_list); - } + *answer = tor_strdup(""); } else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */ if (directory_caches_dir_info(get_options())) { const cached_dir_t *consensus = dirserv_get_consensus("ns"); @@ -1908,6 +1882,22 @@ circuit_describe_status_for_controller(origin_circuit_t *circ) smartlist_add_asprintf(descparts, "TIME_CREATED=%s", tbuf); } + // Show username and/or password if available. + if (circ->socks_username_len > 0) { + char* socks_username_escaped = esc_for_log_len(circ->socks_username, + (size_t) circ->socks_username_len); + smartlist_add_asprintf(descparts, "SOCKS_USERNAME=%s", + socks_username_escaped); + tor_free(socks_username_escaped); + } + if (circ->socks_password_len > 0) { + char* socks_password_escaped = esc_for_log_len(circ->socks_password, + (size_t) circ->socks_password_len); + smartlist_add_asprintf(descparts, "SOCKS_PASSWORD=%s", + socks_password_escaped); + tor_free(socks_password_escaped); + } + rv = smartlist_join_strings(descparts, " ", 0, NULL); SMARTLIST_FOREACH(descparts, char *, cp, tor_free(cp)); @@ -1925,9 +1915,8 @@ getinfo_helper_events(control_connection_t *control_conn, { (void) control_conn; if (!strcmp(question, "circuit-status")) { - circuit_t *circ_; smartlist_t *status = smartlist_new(); - for (circ_ = circuit_get_global_list_(); circ_; circ_ = circ_->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ_) { origin_circuit_t *circ; char *circdesc; const char *state; @@ -1949,6 +1938,7 @@ getinfo_helper_events(control_connection_t *control_conn, state, *circdesc ? " " : "", circdesc); tor_free(circdesc); } + SMARTLIST_FOREACH_END(circ_); *answer = smartlist_join_strings(status, "\r\n", 0, NULL); SMARTLIST_FOREACH(status, char *, cp, tor_free(cp)); smartlist_free(status); @@ -2048,7 +2038,7 @@ getinfo_helper_events(control_connection_t *control_conn, /* Note that status/ is not a catch-all for events; there's only supposed * to be a status GETINFO if there's a corresponding STATUS event. */ if (!strcmp(question, "status/circuit-established")) { - *answer = tor_strdup(can_complete_circuit ? "1" : "0"); + *answer = tor_strdup(have_completed_a_circuit() ? "1" : "0"); } else if (!strcmp(question, "status/enough-dir-info")) { *answer = tor_strdup(router_have_minimum_dir_info() ? "1" : "0"); } else if (!strcmp(question, "status/good-server-descriptor") || @@ -2145,6 +2135,7 @@ typedef struct getinfo_item_t { * to answer them. */ static const getinfo_item_t getinfo_items[] = { ITEM("version", misc, "The current version of Tor."), + ITEM("bw-event-cache", misc, "Cached BW events for a short interval."), ITEM("config-file", misc, "Current location of the \"torrc\" file."), ITEM("config-defaults-file", misc, "Current location of the defaults file."), ITEM("config-text", misc, @@ -2194,6 +2185,8 @@ static const getinfo_item_t getinfo_items[] = { "Brief summary of router status by nickname (v2 directory format)."), PREFIX("ns/purpose/", networkstatus, "Brief summary of router status by purpose (v2 directory format)."), + PREFIX("consensus/", networkstatus, + "Information about and from the ns consensus."), ITEM("network-status", dir, "Brief summary of router status (v1 directory format)"), ITEM("circuit-status", events, "List of current circuits originating here."), @@ -2232,6 +2225,7 @@ static const getinfo_item_t getinfo_items[] = { ITEM("process/user", misc, "Username under which the tor process is running."), ITEM("process/descriptor-limit", misc, "File descriptor limit."), + ITEM("limits/max-mem-in-queues", misc, "Actual limit on memory in queues"), 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."), @@ -2241,6 +2235,9 @@ static const getinfo_item_t getinfo_items[] = { "v3 Networkstatus consensus as retrieved from a DirPort."), ITEM("exit-policy/default", policies, "The default value appended to the configured exit policy."), + ITEM("exit-policy/full", policies, "The entire exit policy of onion router"), + ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"), + ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"), PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"), { NULL, NULL, NULL, 0 } }; @@ -2493,6 +2490,14 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, goto done; } + if (smartlist_len(args) < 2) { + connection_printf_to_buf(conn, + "512 syntax error: not enough arguments.\r\n"); + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + goto done; + } + smartlist_split_string(router_nicknames, smartlist_get(args,1), ",", 0, 0); SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); @@ -2681,7 +2686,7 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, /* Is this a single hop circuit? */ if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { const node_t *node = NULL; - char *exit_digest; + char *exit_digest = NULL; if (circ->build_state && circ->build_state->chosen_exit && !tor_digest_is_zero(circ->build_state->chosen_exit->identity_digest)) { @@ -2696,6 +2701,7 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, "551 Can't attach stream to this one-hop circuit.\r\n", conn); return 0; } + tor_assert(exit_digest); ap_conn->chosen_exit_name = tor_strdup(hex_str(exit_digest, DIGEST_LEN)); } @@ -2921,7 +2927,7 @@ handle_control_resolve(control_connection_t *conn, uint32_t len, int is_reverse = 0; (void) len; /* body is nul-terminated; it's safe to ignore the length */ - if (!(conn->event_mask & ((uint32_t)1L<<EVENT_ADDRMAP))) { + if (!(conn->event_mask & (((event_mask_t)1)<<EVENT_ADDRMAP))) { log_warn(LD_CONTROL, "Controller asked us to resolve an address, but " "isn't listening for ADDRMAP events. It probably won't see " "the answer."); @@ -2985,7 +2991,7 @@ handle_control_protocolinfo(control_connection_t *conn, uint32_t len, } else { const or_options_t *options = get_options(); int cookies = options->CookieAuthentication; - char *cfile = get_cookie_file(); + char *cfile = get_controller_cookie_file_name(); char *abs_cfile; char *esc_cfile; char *methods; @@ -3181,6 +3187,30 @@ handle_control_usefeature(control_connection_t *conn, return 0; } +/** Implementation for the DROPGUARDS command. */ +static int +handle_control_dropguards(control_connection_t *conn, + uint32_t len, + const char *body) +{ + smartlist_t *args; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + args = smartlist_new(); + smartlist_split_string(args, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + + if (smartlist_len(args)) { + connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n"); + } else { + remove_all_entry_guards(); + send_control_done(conn); + } + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + return 0; +} + /** Called when <b>conn</b> has no more bytes left on its outbuf. */ int connection_control_finished_flushing(control_connection_t *conn) @@ -3200,27 +3230,22 @@ connection_control_reached_eof(control_connection_t *conn) return 0; } +static void lost_owning_controller(const char *owner_type, + const char *loss_manner) + ATTR_NORETURN; + /** Shut down this Tor instance in the same way that SIGINT would, but * with a log message appropriate for the loss of an owning controller. */ static void lost_owning_controller(const char *owner_type, const char *loss_manner) { - int shutdown_slowly = server_mode(get_options()); - - log_notice(LD_CONTROL, "Owning controller %s has %s -- %s.", - owner_type, loss_manner, - shutdown_slowly ? "shutting down" : "exiting now"); + log_notice(LD_CONTROL, "Owning controller %s has %s -- exiting now.", + owner_type, loss_manner); /* XXXX Perhaps this chunk of code should be a separate function, * called here and by process_signal(SIGINT). */ - - if (!shutdown_slowly) { - tor_cleanup(); - exit(0); - } - /* XXXX This will close all listening sockets except control-port - * listeners. Perhaps we should close those too. */ - hibernate_begin_shutdown(); + tor_cleanup(); + exit(0); } /** Called when <b>conn</b> is being freed. */ @@ -3480,6 +3505,9 @@ connection_control_process_inbuf(control_connection_t *conn) } else if (!strcasecmp(conn->incoming_cmd, "AUTHCHALLENGE")) { if (handle_control_authchallenge(conn, cmd_data_len, args)) return -1; + } else if (!strcasecmp(conn->incoming_cmd, "DROPGUARDS")) { + if (handle_control_dropguards(conn, cmd_data_len, args)) + return -1; } else { connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", conn->incoming_cmd); @@ -3847,17 +3875,17 @@ control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, } ncircs += connection_or_get_num_circuits(conn); if (ncircs && (tp == OR_CONN_EVENT_FAILED || tp == OR_CONN_EVENT_CLOSED)) { - tor_snprintf(ncircs_buf, sizeof(ncircs_buf), "%sNCIRCS=%d", - reason ? " " : "", ncircs); + tor_snprintf(ncircs_buf, sizeof(ncircs_buf), " NCIRCS=%d", ncircs); } orconn_target_get_name(name, sizeof(name), conn); send_control_event(EVENT_OR_CONN_STATUS, ALL_FORMATS, - "650 ORCONN %s %s %s%s%s\r\n", + "650 ORCONN %s %s%s%s%s ID="U64_FORMAT"\r\n", name, status, - reason ? "REASON=" : "", + reason ? " REASON=" : "", orconn_end_reason_to_control_string(reason), - ncircs_buf); + ncircs_buf, + U64_PRINTF_ARG(conn->base_.global_identifier)); return 0; } @@ -3868,6 +3896,8 @@ control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, int control_event_stream_bandwidth(edge_connection_t *edge_conn) { + circuit_t *circ; + origin_circuit_t *ocirc; if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { if (!edge_conn->n_read && !edge_conn->n_written) return 0; @@ -3878,6 +3908,12 @@ control_event_stream_bandwidth(edge_connection_t *edge_conn) (unsigned long)edge_conn->n_read, (unsigned long)edge_conn->n_written); + circ = circuit_get_by_edge_conn(edge_conn); + if (circ && CIRCUIT_IS_ORIGIN(circ)) { + ocirc = TO_ORIGIN_CIRCUIT(circ); + ocirc->n_read_circ_bw += edge_conn->n_read; + ocirc->n_written_circ_bw += edge_conn->n_written; + } edge_conn->n_written = edge_conn->n_read = 0; } @@ -3915,11 +3951,258 @@ control_event_stream_bandwidth_used(void) return 0; } +/** A second or more has elapsed: tell any interested control connections + * how much bandwidth origin circuits have used. */ +int +control_event_circ_bandwidth_used(void) +{ + origin_circuit_t *ocirc; + if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) + return 0; + + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!CIRCUIT_IS_ORIGIN(circ)) + continue; + ocirc = TO_ORIGIN_CIRCUIT(circ); + if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) + continue; + send_control_event(EVENT_CIRC_BANDWIDTH_USED, ALL_FORMATS, + "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu\r\n", + ocirc->global_identifier, + (unsigned long)ocirc->n_read_circ_bw, + (unsigned long)ocirc->n_written_circ_bw); + ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; + } + SMARTLIST_FOREACH_END(circ); + + return 0; +} + +/** Print out CONN_BW event for a single OR/DIR/EXIT <b>conn</b> and reset + * bandwidth counters. */ +int +control_event_conn_bandwidth(connection_t *conn) +{ + const char *conn_type_str; + if (!get_options()->TestingEnableConnBwEvent || + !EVENT_IS_INTERESTING(EVENT_CONN_BW)) + return 0; + if (!conn->n_read_conn_bw && !conn->n_written_conn_bw) + return 0; + switch (conn->type) { + case CONN_TYPE_OR: + conn_type_str = "OR"; + break; + case CONN_TYPE_DIR: + conn_type_str = "DIR"; + break; + case CONN_TYPE_EXIT: + conn_type_str = "EXIT"; + break; + default: + return 0; + } + send_control_event(EVENT_CONN_BW, ALL_FORMATS, + "650 CONN_BW ID="U64_FORMAT" TYPE=%s " + "READ=%lu WRITTEN=%lu\r\n", + U64_PRINTF_ARG(conn->global_identifier), + conn_type_str, + (unsigned long)conn->n_read_conn_bw, + (unsigned long)conn->n_written_conn_bw); + conn->n_written_conn_bw = conn->n_read_conn_bw = 0; + return 0; +} + +/** A second or more has elapsed: tell any interested control + * connections how much bandwidth connections have used. */ +int +control_event_conn_bandwidth_used(void) +{ + if (get_options()->TestingEnableConnBwEvent && + EVENT_IS_INTERESTING(EVENT_CONN_BW)) { + SMARTLIST_FOREACH(get_connection_array(), connection_t *, conn, + control_event_conn_bandwidth(conn)); + } + return 0; +} + +/** Helper: iterate over cell statistics of <b>circ</b> and sum up added + * cells, removed cells, and waiting times by cell command and direction. + * Store results in <b>cell_stats</b>. Free cell statistics of the + * circuit afterwards. */ +void +sum_up_cell_stats_by_command(circuit_t *circ, cell_stats_t *cell_stats) +{ + memset(cell_stats, 0, sizeof(cell_stats_t)); + SMARTLIST_FOREACH_BEGIN(circ->testing_cell_stats, + testing_cell_stats_entry_t *, ent) { + tor_assert(ent->command <= CELL_COMMAND_MAX_); + if (!ent->removed && !ent->exitward) { + cell_stats->added_cells_appward[ent->command] += 1; + } else if (!ent->removed && ent->exitward) { + cell_stats->added_cells_exitward[ent->command] += 1; + } else if (!ent->exitward) { + cell_stats->removed_cells_appward[ent->command] += 1; + cell_stats->total_time_appward[ent->command] += ent->waiting_time * 10; + } else { + cell_stats->removed_cells_exitward[ent->command] += 1; + cell_stats->total_time_exitward[ent->command] += ent->waiting_time * 10; + } + tor_free(ent); + } SMARTLIST_FOREACH_END(ent); + smartlist_free(circ->testing_cell_stats); + circ->testing_cell_stats = NULL; +} + +/** Helper: append a cell statistics string to <code>event_parts</code>, + * prefixed with <code>key</code>=. Statistics consist of comma-separated + * key:value pairs with lower-case command strings as keys and cell + * numbers or total waiting times as values. A key:value pair is included + * if the entry in <code>include_if_non_zero</code> is not zero, but with + * the (possibly zero) entry from <code>number_to_include</code>. Both + * arrays are expected to have a length of CELL_COMMAND_MAX_ + 1. If no + * entry in <code>include_if_non_zero</code> is positive, no string will + * be added to <code>event_parts</code>. */ +void +append_cell_stats_by_command(smartlist_t *event_parts, const char *key, + const uint64_t *include_if_non_zero, + const uint64_t *number_to_include) +{ + smartlist_t *key_value_strings = smartlist_new(); + int i; + for (i = 0; i <= CELL_COMMAND_MAX_; i++) { + if (include_if_non_zero[i] > 0) { + smartlist_add_asprintf(key_value_strings, "%s:"U64_FORMAT, + cell_command_to_string(i), + U64_PRINTF_ARG(number_to_include[i])); + } + } + if (smartlist_len(key_value_strings) > 0) { + char *joined = smartlist_join_strings(key_value_strings, ",", 0, NULL); + smartlist_add_asprintf(event_parts, "%s=%s", key, joined); + SMARTLIST_FOREACH(key_value_strings, char *, cp, tor_free(cp)); + tor_free(joined); + } + smartlist_free(key_value_strings); +} + +/** Helper: format <b>cell_stats</b> for <b>circ</b> for inclusion in a + * CELL_STATS event and write result string to <b>event_string</b>. */ +void +format_cell_stats(char **event_string, circuit_t *circ, + cell_stats_t *cell_stats) +{ + smartlist_t *event_parts = smartlist_new(); + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + smartlist_add_asprintf(event_parts, "ID=%lu", + (unsigned long)ocirc->global_identifier); + } else if (TO_OR_CIRCUIT(circ)->p_chan) { + or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); + smartlist_add_asprintf(event_parts, "InboundQueue=%lu", + (unsigned long)or_circ->p_circ_id); + smartlist_add_asprintf(event_parts, "InboundConn="U64_FORMAT, + U64_PRINTF_ARG(or_circ->p_chan->global_identifier)); + append_cell_stats_by_command(event_parts, "InboundAdded", + cell_stats->added_cells_appward, + cell_stats->added_cells_appward); + append_cell_stats_by_command(event_parts, "InboundRemoved", + cell_stats->removed_cells_appward, + cell_stats->removed_cells_appward); + append_cell_stats_by_command(event_parts, "InboundTime", + cell_stats->removed_cells_appward, + cell_stats->total_time_appward); + } + if (circ->n_chan) { + smartlist_add_asprintf(event_parts, "OutboundQueue=%lu", + (unsigned long)circ->n_circ_id); + smartlist_add_asprintf(event_parts, "OutboundConn="U64_FORMAT, + U64_PRINTF_ARG(circ->n_chan->global_identifier)); + append_cell_stats_by_command(event_parts, "OutboundAdded", + cell_stats->added_cells_exitward, + cell_stats->added_cells_exitward); + append_cell_stats_by_command(event_parts, "OutboundRemoved", + cell_stats->removed_cells_exitward, + cell_stats->removed_cells_exitward); + append_cell_stats_by_command(event_parts, "OutboundTime", + cell_stats->removed_cells_exitward, + cell_stats->total_time_exitward); + } + *event_string = smartlist_join_strings(event_parts, " ", 0, NULL); + SMARTLIST_FOREACH(event_parts, char *, cp, tor_free(cp)); + smartlist_free(event_parts); +} + +/** A second or more has elapsed: tell any interested control connection + * how many cells have been processed for a given circuit. */ +int +control_event_circuit_cell_stats(void) +{ + cell_stats_t *cell_stats; + char *event_string; + if (!get_options()->TestingEnableCellStatsEvent || + !EVENT_IS_INTERESTING(EVENT_CELL_STATS)) + return 0; + cell_stats = tor_malloc(sizeof(cell_stats_t));; + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!circ->testing_cell_stats) + continue; + sum_up_cell_stats_by_command(circ, cell_stats); + format_cell_stats(&event_string, circ, cell_stats); + send_control_event(EVENT_CELL_STATS, ALL_FORMATS, + "650 CELL_STATS %s\r\n", event_string); + tor_free(event_string); + } + SMARTLIST_FOREACH_END(circ); + tor_free(cell_stats); + return 0; +} + +/** Tokens in <b>bucket</b> have been refilled: the read bucket was empty + * for <b>read_empty_time</b> millis, the write bucket was empty for + * <b>write_empty_time</b> millis, and buckets were last refilled + * <b>milliseconds_elapsed</b> millis ago. Only emit TB_EMPTY event if + * either read or write bucket have been empty before. */ +int +control_event_tb_empty(const char *bucket, uint32_t read_empty_time, + uint32_t write_empty_time, + int milliseconds_elapsed) +{ + if (get_options()->TestingEnableTbEmptyEvent && + EVENT_IS_INTERESTING(EVENT_TB_EMPTY) && + (read_empty_time > 0 || write_empty_time > 0)) { + send_control_event(EVENT_TB_EMPTY, ALL_FORMATS, + "650 TB_EMPTY %s READ=%d WRITTEN=%d " + "LAST=%d\r\n", + bucket, read_empty_time, write_empty_time, + milliseconds_elapsed); + } + return 0; +} + +/* about 5 minutes worth. */ +#define N_BW_EVENTS_TO_CACHE 300 +/* Index into cached_bw_events to next write. */ +static int next_measurement_idx = 0; +/* number of entries set in n_measurements */ +static int n_measurements = 0; +static struct cached_bw_event_s { + uint32_t n_read; + uint32_t n_written; +} cached_bw_events[N_BW_EVENTS_TO_CACHE]; + /** A second or more has elapsed: tell any interested control * connections how much bandwidth we used. */ int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written) { + cached_bw_events[next_measurement_idx].n_read = n_read; + cached_bw_events[next_measurement_idx].n_written = n_written; + if (++next_measurement_idx == N_BW_EVENTS_TO_CACHE) + next_measurement_idx = 0; + if (n_measurements < N_BW_EVENTS_TO_CACHE) + ++n_measurements; + if (EVENT_IS_INTERESTING(EVENT_BANDWIDTH_USED)) { send_control_event(EVENT_BANDWIDTH_USED, ALL_FORMATS, "650 BW %lu %lu\r\n", @@ -3930,6 +4213,35 @@ control_event_bandwidth_used(uint32_t n_read, uint32_t n_written) return 0; } +STATIC char * +get_bw_samples(void) +{ + int i; + int idx = (next_measurement_idx + N_BW_EVENTS_TO_CACHE - n_measurements) + % N_BW_EVENTS_TO_CACHE; + tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE); + + smartlist_t *elements = smartlist_new(); + + for (i = 0; i < n_measurements; ++i) { + tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE); + const struct cached_bw_event_s *bwe = &cached_bw_events[idx]; + + smartlist_add_asprintf(elements, "%u,%u", + (unsigned)bwe->n_read, + (unsigned)bwe->n_written); + + idx = (idx + 1) % N_BW_EVENTS_TO_CACHE; + } + + char *result = smartlist_join_strings(elements, " ", 0, NULL); + + SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); + smartlist_free(elements); + + return result; +} + /** Called when we are sending a log message to the controllers: suspend * sending further log messages to the controllers until we're done. Used by * CONN_LOG_PROTECT. */ @@ -4162,32 +4474,26 @@ control_event_newconsensus(const networkstatus_t *consensus) /** Called when we compute a new circuitbuildtimeout */ int -control_event_buildtimeout_set(const circuit_build_times_t *cbt, - buildtimeout_set_event_t type) +control_event_buildtimeout_set(buildtimeout_set_event_t type, + const char *args) { const char *type_string = NULL; - double qnt; if (!control_event_is_interesting(EVENT_BUILDTIMEOUT_SET)) return 0; - qnt = circuit_build_times_quantile_cutoff(); - switch (type) { case BUILDTIMEOUT_SET_EVENT_COMPUTED: type_string = "COMPUTED"; break; case BUILDTIMEOUT_SET_EVENT_RESET: type_string = "RESET"; - qnt = 1.0; break; case BUILDTIMEOUT_SET_EVENT_SUSPENDED: type_string = "SUSPENDED"; - qnt = 1.0; break; case BUILDTIMEOUT_SET_EVENT_DISCARD: type_string = "DISCARD"; - qnt = 1.0; break; case BUILDTIMEOUT_SET_EVENT_RESUME: type_string = "RESUME"; @@ -4198,15 +4504,8 @@ control_event_buildtimeout_set(const circuit_build_times_t *cbt, } send_control_event(EVENT_BUILDTIMEOUT_SET, ALL_FORMATS, - "650 BUILDTIMEOUT_SET %s TOTAL_TIMES=%lu " - "TIMEOUT_MS=%lu XM=%lu ALPHA=%f CUTOFF_QUANTILE=%f " - "TIMEOUT_RATE=%f CLOSE_MS=%lu CLOSE_RATE=%f\r\n", - type_string, (unsigned long)cbt->total_build_times, - (unsigned long)cbt->timeout_ms, - (unsigned long)cbt->Xm, cbt->alpha, qnt, - circuit_build_times_timeout_rate(cbt), - (unsigned long)cbt->close_ms, - circuit_build_times_close_rate(cbt)); + "650 BUILDTIMEOUT_SET %s %s\r\n", + type_string, args); return 0; } @@ -4236,6 +4535,9 @@ control_event_signal(uintptr_t signal) case SIGCLEARDNSCACHE: signal_string = "CLEARDNSCACHE"; break; + case SIGHEARTBEAT: + signal_string = "HEARTBEAT"; + break; default: log_warn(LD_BUG, "Unrecognized signal %lu in control_event_signal", (unsigned long)signal); @@ -4434,8 +4736,8 @@ control_event_conf_changed(const smartlist_t *elements) /** Helper: Return a newly allocated string containing a path to the * file where we store our authentication cookie. */ -static char * -get_cookie_file(void) +char * +get_controller_cookie_file_name(void) { const or_options_t *options = get_options(); if (options->CookieAuthFile && strlen(options->CookieAuthFile)) { @@ -4445,44 +4747,28 @@ get_cookie_file(void) } } -/** Choose a random authentication cookie and write it to disk. - * Anybody who can read the cookie from disk will be considered - * authorized to use the control connection. Return -1 if we can't - * write the file, or 0 on success. */ +/* Initialize the cookie-based authentication system of the + * ControlPort. If <b>enabled</b> is 0, then disable the cookie + * authentication system. */ int -init_cookie_authentication(int enabled) +init_control_cookie_authentication(int enabled) { - char *fname; + char *fname = NULL; + int retval; + if (!enabled) { authentication_cookie_is_set = 0; return 0; } - /* We don't want to generate a new cookie every time we call - * options_act(). One should be enough. */ - if (authentication_cookie_is_set) - return 0; /* all set */ - - fname = get_cookie_file(); - crypto_rand(authentication_cookie, AUTHENTICATION_COOKIE_LEN); - authentication_cookie_is_set = 1; - if (write_bytes_to_file(fname, authentication_cookie, - AUTHENTICATION_COOKIE_LEN, 1)) { - log_warn(LD_FS,"Error writing authentication cookie to %s.", - escaped(fname)); - tor_free(fname); - return -1; - } -#ifndef _WIN32 - if (get_options()->CookieAuthFileGroupReadable) { - if (chmod(fname, 0640)) { - log_warn(LD_FS,"Unable to make %s group-readable.", escaped(fname)); - } - } -#endif - + fname = get_controller_cookie_file_name(); + retval = init_cookie_authentication(fname, "", /* no header */ + AUTHENTICATION_COOKIE_LEN, + get_options()->CookieAuthFileGroupReadable, + &authentication_cookie, + &authentication_cookie_is_set); tor_free(fname); - return 0; + return retval; } /** A copy of the process specifier of Tor's owning controller, or @@ -4493,6 +4779,8 @@ static char *owning_controller_process_spec = NULL; * if this Tor instance is not currently owned by a process. */ static tor_process_monitor_t *owning_controller_process_monitor = NULL; +static void owning_controller_procmon_cb(void *unused) ATTR_NORETURN; + /** Process-termination monitor callback for Tor's owning controller * process. */ static void @@ -4599,23 +4887,43 @@ bootstrap_status_to_string(bootstrap_status_t s, const char **tag, break; case BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS: *tag = "requesting_descriptors"; - *summary = "Asking for relay descriptors"; + /* XXXX this appears to incorrectly report internal on most loads */ + *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? + "Asking for relay descriptors for internal paths" : + "Asking for relay descriptors"; break; + /* If we're sure there are no exits in the consensus, + * inform the controller by adding "internal" + * to the status summaries. + * (We only check this while loading descriptors, + * so we may not know in the earlier stages.) + * But if there are exits, we can't be sure whether + * we're creating internal or exit paths/circuits. + * XXXX Or should be use different tags or statuses + * for internal and exit/all? */ case BOOTSTRAP_STATUS_LOADING_DESCRIPTORS: *tag = "loading_descriptors"; - *summary = "Loading relay descriptors"; + *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? + "Loading relay descriptors for internal paths" : + "Loading relay descriptors"; break; case BOOTSTRAP_STATUS_CONN_OR: *tag = "conn_or"; - *summary = "Connecting to the Tor network"; + *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? + "Connecting to the Tor network internally" : + "Connecting to the Tor network"; break; case BOOTSTRAP_STATUS_HANDSHAKE_OR: *tag = "handshake_or"; - *summary = "Finishing handshake with first hop"; + *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? + "Finishing handshake with first hop of internal circuit" : + "Finishing handshake with first hop"; break; case BOOTSTRAP_STATUS_CIRCUIT_CREATE: *tag = "circuit_create"; - *summary = "Establishing a Tor circuit"; + *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? + "Establishing an internal Tor circuit" : + "Establishing a Tor circuit"; break; case BOOTSTRAP_STATUS_DONE: *tag = "done"; @@ -4636,36 +4944,51 @@ bootstrap_status_to_string(bootstrap_status_t s, const char **tag, * Tor initializes. */ static int bootstrap_percent = BOOTSTRAP_STATUS_UNDEF; +/** As bootstrap_percent, but holds the bootstrapping level at which we last + * logged a NOTICE-level message. We use this, plus BOOTSTRAP_PCT_INCREMENT, + * to avoid flooding the log with a new message every time we get a few more + * microdescriptors */ +static int notice_bootstrap_percent = 0; + /** How many problems have we had getting to the next bootstrapping phase? * These include failure to establish a connection to a Tor relay, * failures to finish the TLS handshake, failures to validate the * consensus document, etc. */ static int bootstrap_problems = 0; -/* We only tell the controller once we've hit a threshold of problems +/** We only tell the controller once we've hit a threshold of problems * for the current phase. */ #define BOOTSTRAP_PROBLEM_THRESHOLD 10 +/** When our bootstrapping progress level changes, but our bootstrapping + * status has not advanced, we only log at NOTICE when we have made at least + * this much progress. + */ +#define BOOTSTRAP_PCT_INCREMENT 5 + /** Called when Tor has made progress at bootstrapping its directory * information and initial circuits. * * <b>status</b> is the new status, that is, what task we will be doing * next. <b>progress</b> is zero if we just started this task, else it - * represents progress on the task. */ -void + * represents progress on the task. + * + * Return true if we logged a message at level NOTICE, and false otherwise. + */ +int control_event_bootstrap(bootstrap_status_t status, int progress) { const char *tag, *summary; char buf[BOOTSTRAP_MSG_LEN]; if (bootstrap_percent == BOOTSTRAP_STATUS_DONE) - return; /* already bootstrapped; nothing to be done here. */ + return 0; /* already bootstrapped; nothing to be done here. */ /* special case for handshaking status, since our TLS handshaking code * can't distinguish what the connection is going to be for. */ if (status == BOOTSTRAP_STATUS_HANDSHAKE) { if (bootstrap_percent < BOOTSTRAP_STATUS_CONN_OR) { - status = BOOTSTRAP_STATUS_HANDSHAKE_DIR; + status = BOOTSTRAP_STATUS_HANDSHAKE_DIR; } else { status = BOOTSTRAP_STATUS_HANDSHAKE_OR; } @@ -4673,9 +4996,19 @@ control_event_bootstrap(bootstrap_status_t status, int progress) if (status > bootstrap_percent || (progress && progress > bootstrap_percent)) { + int loglevel = LOG_NOTICE; bootstrap_status_to_string(status, &tag, &summary); - tor_log(status ? LOG_NOTICE : LOG_INFO, LD_CONTROL, - "Bootstrapped %d%%: %s.", progress ? progress : status, summary); + + if (status <= bootstrap_percent && + (progress < notice_bootstrap_percent + BOOTSTRAP_PCT_INCREMENT)) { + /* We log the message at info if the status hasn't advanced, and if less + * than BOOTSTRAP_PCT_INCREMENT progress has been made. + */ + loglevel = LOG_INFO; + } + + tor_log(loglevel, LD_CONTROL, + "Bootstrapped %d%%: %s", progress ? progress : status, summary); tor_snprintf(buf, sizeof(buf), "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\"", progress ? progress : status, tag, summary); @@ -4691,18 +5024,28 @@ control_event_bootstrap(bootstrap_status_t status, int progress) bootstrap_percent = progress; bootstrap_problems = 0; /* Progress! Reset our problem counter. */ } + if (loglevel == LOG_NOTICE && + bootstrap_percent > notice_bootstrap_percent) { + /* Remember that we gave a notice at this level. */ + notice_bootstrap_percent = bootstrap_percent; + } + return loglevel == LOG_NOTICE; } + + return 0; } /** Called when Tor has failed to make bootstrapping progress in a way * that indicates a problem. <b>warn</b> gives a hint as to why, and - * <b>reason</b> provides an "or_conn_end_reason" tag. + * <b>reason</b> provides an "or_conn_end_reason" tag. <b>or_conn</b> + * is the connection that caused this problem. */ -void -control_event_bootstrap_problem(const char *warn, int reason) +MOCK_IMPL(void, + control_event_bootstrap_problem, (const char *warn, int reason, + or_connection_t *or_conn)) { int status = bootstrap_percent; - const char *tag, *summary; + const char *tag = "", *summary = ""; char buf[BOOTSTRAP_MSG_LEN]; const char *recommendation = "ignore"; int severity; @@ -4710,6 +5053,11 @@ control_event_bootstrap_problem(const char *warn, int reason) /* bootstrap_percent must not be in "undefined" state here. */ tor_assert(status >= 0); + if (or_conn->have_noted_bootstrap_problem) + return; + + or_conn->have_noted_bootstrap_problem = 1; + if (bootstrap_percent == 100) return; /* already bootstrapped; nothing to be done here. */ @@ -4721,9 +5069,10 @@ control_event_bootstrap_problem(const char *warn, int reason) if (reason == END_OR_CONN_REASON_NO_ROUTE) recommendation = "warn"; - if (get_options()->UseBridges && - !any_bridge_descriptors_known() && - !any_pending_bridge_descriptor_fetches()) + /* If we are using bridges and all our OR connections are now + closed, it means that we totally failed to connect to our + bridges. Throw a warning. */ + if (get_options()->UseBridges && !any_other_active_or_conns(or_conn)) recommendation = "warn"; if (we_are_hibernating()) @@ -4737,19 +5086,26 @@ control_event_bootstrap_problem(const char *warn, int reason) log_fn(severity, LD_CONTROL, "Problem bootstrapping. Stuck at %d%%: %s. (%s; %s; " - "count %d; recommendation %s)", + "count %d; recommendation %s; host %s at %s:%d)", status, summary, warn, orconn_end_reason_to_control_string(reason), - bootstrap_problems, recommendation); + bootstrap_problems, recommendation, + hex_str(or_conn->identity_digest, DIGEST_LEN), + or_conn->base_.address, + or_conn->base_.port); connection_or_report_broken_states(severity, LD_HANDSHAKE); tor_snprintf(buf, sizeof(buf), "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\" WARNING=\"%s\" REASON=%s " - "COUNT=%d RECOMMENDATION=%s", + "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s:%d\"", bootstrap_percent, tag, summary, warn, orconn_end_reason_to_control_string(reason), bootstrap_problems, - recommendation); + recommendation, + hex_str(or_conn->identity_digest, DIGEST_LEN), + or_conn->base_.address, + (int)or_conn->base_.port); + tor_snprintf(last_sent_bootstrap_message, sizeof(last_sent_bootstrap_message), "WARN %s", buf); @@ -4766,3 +5122,173 @@ control_event_clients_seen(const char *controller_str) "650 CLIENTS_SEEN %s\r\n", controller_str); } +/** A new pluggable transport called <b>transport_name</b> was + * launched on <b>addr</b>:<b>port</b>. <b>mode</b> is either + * "server" or "client" depending on the mode of the pluggable + * transport. + * "650" SP "TRANSPORT_LAUNCHED" SP Mode SP Name SP Address SP Port + */ +void +control_event_transport_launched(const char *mode, const char *transport_name, + tor_addr_t *addr, uint16_t port) +{ + send_control_event(EVENT_TRANSPORT_LAUNCHED, ALL_FORMATS, + "650 TRANSPORT_LAUNCHED %s %s %s %u\r\n", + mode, transport_name, fmt_addr(addr), port); +} + +/** Convert rendezvous auth type to string for HS_DESC control events + */ +const char * +rend_auth_type_to_string(rend_auth_type_t auth_type) +{ + const char *str; + + switch (auth_type) { + case REND_NO_AUTH: + str = "NO_AUTH"; + break; + case REND_BASIC_AUTH: + str = "BASIC_AUTH"; + break; + case REND_STEALTH_AUTH: + str = "STEALTH_AUTH"; + break; + default: + str = "UNKNOWN"; + } + + return str; +} + +/** Return a longname the node whose identity is <b>id_digest</b>. If + * node_get_by_id() returns NULL, base 16 encoding of <b>id_digest</b> is + * returned instead. + * + * This function is not thread-safe. Each call to this function invalidates + * previous values returned by this function. + */ +MOCK_IMPL(const char *, +node_describe_longname_by_id,(const char *id_digest)) +{ + static char longname[MAX_VERBOSE_NICKNAME_LEN+1]; + node_get_verbose_nickname_by_id(id_digest, longname); + return longname; +} + +/** send HS_DESC requested event. + * + * <b>rend_query</b> is used to fetch requested onion address and auth type. + * <b>hs_dir</b> is the description of contacting hs directory. + * <b>desc_id_base32</b> is the ID of requested hs descriptor. + */ +void +control_event_hs_descriptor_requested(const rend_data_t *rend_query, + const char *id_digest, + const char *desc_id_base32) +{ + if (!id_digest || !rend_query || !desc_id_base32) { + log_warn(LD_BUG, "Called with rend_query==%p, " + "id_digest==%p, desc_id_base32==%p", + rend_query, id_digest, desc_id_base32); + return; + } + + send_control_event(EVENT_HS_DESC, ALL_FORMATS, + "650 HS_DESC REQUESTED %s %s %s %s\r\n", + rend_query->onion_address, + rend_auth_type_to_string(rend_query->auth_type), + node_describe_longname_by_id(id_digest), + desc_id_base32); +} + +/** send HS_DESC event after got response from hs directory. + * + * NOTE: this is an internal function used by following functions: + * control_event_hs_descriptor_received + * control_event_hs_descriptor_failed + * + * So do not call this function directly. + */ +void +control_event_hs_descriptor_receive_end(const char *action, + const rend_data_t *rend_query, + const char *id_digest, + const char *reason) +{ + char *reason_field = NULL; + + if (!action || !rend_query || !id_digest) { + log_warn(LD_BUG, "Called with action==%p, rend_query==%p, " + "id_digest==%p", action, rend_query, id_digest); + return; + } + + if (reason) { + tor_asprintf(&reason_field, " REASON=%s", reason); + } + + send_control_event(EVENT_HS_DESC, ALL_FORMATS, + "650 HS_DESC %s %s %s %s%s\r\n", + action, + rend_query->onion_address, + rend_auth_type_to_string(rend_query->auth_type), + node_describe_longname_by_id(id_digest), + reason_field ? reason_field : ""); + + tor_free(reason_field); +} + +/** send HS_DESC RECEIVED event + * + * called when a we successfully received a hidden service descriptor. + */ +void +control_event_hs_descriptor_received(const rend_data_t *rend_query, + const char *id_digest) +{ + if (!rend_query || !id_digest) { + log_warn(LD_BUG, "Called with rend_query==%p, id_digest==%p", + rend_query, id_digest); + return; + } + control_event_hs_descriptor_receive_end("RECEIVED", rend_query, + id_digest, NULL); +} + +/** Send HS_DESC event to inform controller that query <b>rend_query</b> + * failed to retrieve hidden service descriptor identified by + * <b>id_digest</b>. If <b>reason</b> is not NULL, add it to REASON= + * field. + */ +void +control_event_hs_descriptor_failed(const rend_data_t *rend_query, + const char *id_digest, + const char *reason) +{ + if (!rend_query || !id_digest) { + log_warn(LD_BUG, "Called with rend_query==%p, id_digest==%p", + rend_query, id_digest); + return; + } + control_event_hs_descriptor_receive_end("FAILED", rend_query, + id_digest, reason); +} + +/** Free any leftover allocated memory of the control.c subsystem. */ +void +control_free_all(void) +{ + if (authentication_cookie) /* Free the auth cookie */ + tor_free(authentication_cookie); +} + +#ifdef TOR_UNIT_TESTS +/* For testing: change the value of global_event_mask */ +void +control_testing_set_global_event_mask(uint64_t mask) +{ + global_event_mask = mask; +} +#endif + diff --git a/src/or/control.h b/src/or/control.h index 61062da2c4..47a601817a 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -50,6 +50,13 @@ int control_event_or_conn_status(or_connection_t *conn, int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written); int control_event_stream_bandwidth(edge_connection_t *edge_conn); int control_event_stream_bandwidth_used(void); +int control_event_circ_bandwidth_used(void); +int control_event_conn_bandwidth(connection_t *conn); +int control_event_conn_bandwidth_used(void); +int control_event_circuit_cell_stats(void); +int control_event_tb_empty(const char *bucket, uint32_t read_empty_time, + uint32_t write_empty_time, + int milliseconds_elapsed); void control_event_logmsg(int severity, uint32_t domain, const char *msg); int control_event_descriptors_changed(smartlist_t *routers); int control_event_address_mapped(const char *from, const char *to, @@ -73,26 +80,130 @@ int control_event_server_status(int severity, const char *format, ...) int control_event_guard(const char *nickname, const char *digest, const char *status); int control_event_conf_changed(const smartlist_t *elements); -int control_event_buildtimeout_set(const circuit_build_times_t *cbt, - buildtimeout_set_event_t type); +int control_event_buildtimeout_set(buildtimeout_set_event_t type, + const char *args); int control_event_signal(uintptr_t signal); -int init_cookie_authentication(int enabled); +int init_control_cookie_authentication(int enabled); +char *get_controller_cookie_file_name(void); smartlist_t *decode_hashed_passwords(config_line_t *passwords); void disable_control_logging(void); void enable_control_logging(void); void monitor_owning_controller_process(const char *process_spec); -void control_event_bootstrap(bootstrap_status_t status, int progress); -void control_event_bootstrap_problem(const char *warn, int reason); +int control_event_bootstrap(bootstrap_status_t status, int progress); +MOCK_DECL(void, control_event_bootstrap_problem,(const char *warn, + int reason, + or_connection_t *or_conn)); void control_event_clients_seen(const char *controller_str); +void control_event_transport_launched(const char *mode, + const char *transport_name, + tor_addr_t *addr, uint16_t port); +const char *rend_auth_type_to_string(rend_auth_type_t auth_type); +MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest)); +void control_event_hs_descriptor_requested(const rend_data_t *rend_query, + const char *desc_id_base32, + const char *hs_dir); +void control_event_hs_descriptor_receive_end(const char *action, + const rend_data_t *rend_query, + const char *hs_dir, + const char *reason); +void control_event_hs_descriptor_received(const rend_data_t *rend_query, + const char *hs_dir); +void control_event_hs_descriptor_failed(const rend_data_t *rend_query, + const char *hs_dir, + const char *reason); + +void control_free_all(void); #ifdef CONTROL_PRIVATE +/* Recognized asynchronous event types. It's okay to expand this list + * because it is used both as a list of v0 event types, and as indices + * into the bitfield to determine which controllers want which events. + */ +#define EVENT_MIN_ 0x0001 +#define EVENT_CIRCUIT_STATUS 0x0001 +#define EVENT_STREAM_STATUS 0x0002 +#define EVENT_OR_CONN_STATUS 0x0003 +#define EVENT_BANDWIDTH_USED 0x0004 +#define EVENT_CIRCUIT_STATUS_MINOR 0x0005 +#define EVENT_NEW_DESC 0x0006 +#define EVENT_DEBUG_MSG 0x0007 +#define EVENT_INFO_MSG 0x0008 +#define EVENT_NOTICE_MSG 0x0009 +#define EVENT_WARN_MSG 0x000A +#define EVENT_ERR_MSG 0x000B +#define EVENT_ADDRMAP 0x000C +/* Exposed above */ +// #define EVENT_AUTHDIR_NEWDESCS 0x000D +#define EVENT_DESCCHANGED 0x000E +/* Exposed above */ +// #define EVENT_NS 0x000F +#define EVENT_STATUS_CLIENT 0x0010 +#define EVENT_STATUS_SERVER 0x0011 +#define EVENT_STATUS_GENERAL 0x0012 +#define EVENT_GUARD 0x0013 +#define EVENT_STREAM_BANDWIDTH_USED 0x0014 +#define EVENT_CLIENTS_SEEN 0x0015 +#define EVENT_NEWCONSENSUS 0x0016 +#define EVENT_BUILDTIMEOUT_SET 0x0017 +#define EVENT_SIGNAL 0x0018 +#define EVENT_CONF_CHANGED 0x0019 +#define EVENT_CONN_BW 0x001A +#define EVENT_CELL_STATS 0x001B +#define EVENT_TB_EMPTY 0x001C +#define EVENT_CIRC_BANDWIDTH_USED 0x001D +#define EVENT_TRANSPORT_LAUNCHED 0x0020 +#define EVENT_HS_DESC 0x0021 +#define EVENT_MAX_ 0x0021 +/* If EVENT_MAX_ ever hits 0x003F, we need to make the mask into a + * different structure, as it can only handle a maximum left shift of 1<<63. */ + /* Used only by control.c and test.c */ -size_t write_escaped_data(const char *data, size_t len, char **out); -size_t read_escaped_data(const char *data, size_t len, char **out); +STATIC size_t write_escaped_data(const char *data, size_t len, char **out); +STATIC size_t read_escaped_data(const char *data, size_t len, char **out); +/** Flag for event_format_t. Indicates that we should use the one standard + format. (Other formats previous existed, and are now deprecated) + */ +#define ALL_FORMATS 1 +/** Bit field of flags to select how to format a controller event. Recognized + * flag is ALL_FORMATS. */ +typedef int event_format_t; + +#ifdef TOR_UNIT_TESTS +MOCK_DECL(STATIC void, +send_control_event_string,(uint16_t event, event_format_t which, + const char *msg)); + +void control_testing_set_global_event_mask(uint64_t mask); +#endif + +/** Helper structure: temporarily stores cell statistics for a circuit. */ +typedef struct cell_stats_t { + /** Number of cells added in app-ward direction by command. */ + uint64_t added_cells_appward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells added in exit-ward direction by command. */ + uint64_t added_cells_exitward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells removed in app-ward direction by command. */ + uint64_t removed_cells_appward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells removed in exit-ward direction by command. */ + uint64_t removed_cells_exitward[CELL_COMMAND_MAX_ + 1]; + /** Total waiting time of cells in app-ward direction by command. */ + uint64_t total_time_appward[CELL_COMMAND_MAX_ + 1]; + /** Total waiting time of cells in exit-ward direction by command. */ + uint64_t total_time_exitward[CELL_COMMAND_MAX_ + 1]; +} cell_stats_t; +void sum_up_cell_stats_by_command(circuit_t *circ, + cell_stats_t *cell_stats); +void append_cell_stats_by_command(smartlist_t *event_parts, + const char *key, + const uint64_t *include_if_non_zero, + const uint64_t *number_to_include); +void format_cell_stats(char **event_string, circuit_t *circ, + cell_stats_t *cell_stats); +STATIC char *get_bw_samples(void); #endif #endif diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index ecf0d2035d..d511ecf84c 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -1,88 +1,103 @@ /* Copyright (c) 2003-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file cpuworker.c - * \brief Implements a farm of 'CPU worker' processes to perform - * CPU-intensive tasks in another thread or process, to not - * interrupt the main thread. + * \brief Uses the workqueue/threadpool code to farm CPU-intensive activities + * out to subprocesses. * * Right now, we only use this for processing onionskins. **/ #include "or.h" -#include "buffers.h" #include "channel.h" -#include "channeltls.h" #include "circuitbuild.h" #include "circuitlist.h" -#include "config.h" -#include "connection.h" #include "connection_or.h" +#include "config.h" #include "cpuworker.h" #include "main.h" #include "onion.h" #include "rephist.h" #include "router.h" +#include "workqueue.h" -/** The maximum number of cpuworker processes we will keep around. */ -#define MAX_CPUWORKERS 16 -/** The minimum number of cpuworker processes we will keep around. */ -#define MIN_CPUWORKERS 1 - -/** The tag specifies which circuit this onionskin was from. */ -#define TAG_LEN 12 - -/** How many cpuworkers we have running right now. */ -static int num_cpuworkers=0; -/** How many of the running cpuworkers have an assigned task right now. */ -static int num_cpuworkers_busy=0; -/** We need to spawn new cpuworkers whenever we rotate the onion keys - * on platforms where execution contexts==processes. This variable stores - * the last time we got a key rotation event. */ -static time_t last_rotation_time=0; - -static void cpuworker_main(void *data) ATTR_NORETURN; -static int spawn_cpuworker(void); -static void spawn_enough_cpuworkers(void); -static void process_pending_task(connection_t *cpuworker); - -/** Initialize the cpuworker subsystem. - */ -void -cpu_init(void) +#ifdef HAVE_EVENT2_EVENT_H +#include <event2/event.h> +#else +#include <event.h> +#endif + +static void queue_pending_tasks(void); + +typedef struct worker_state_s { + int generation; + server_onion_keys_t *onion_keys; +} worker_state_t; + +static void * +worker_state_new(void *arg) { - cpuworkers_rotate(); + worker_state_t *ws; + (void)arg; + ws = tor_malloc_zero(sizeof(worker_state_t)); + ws->onion_keys = server_onion_keys_new(); + return ws; } - -/** Called when we're done sending a request to a cpuworker. */ -int -connection_cpu_finished_flushing(connection_t *conn) +static void +worker_state_free(void *arg) { - tor_assert(conn); - tor_assert(conn->type == CONN_TYPE_CPUWORKER); - return 0; + worker_state_t *ws = arg; + server_onion_keys_free(ws->onion_keys); + tor_free(ws); } -/** Pack global_id and circ_id; set *tag to the result. (See note on - * cpuworker_main for wire format.) */ +static replyqueue_t *replyqueue = NULL; +static threadpool_t *threadpool = NULL; +static struct event *reply_event = NULL; + +static tor_weak_rng_t request_sample_rng = TOR_WEAK_RNG_INIT; + +static int total_pending_tasks = 0; +static int max_pending_tasks = 128; + static void -tag_pack(uint8_t *tag, uint64_t chan_id, circid_t circ_id) +replyqueue_process_cb(evutil_socket_t sock, short events, void *arg) { - /*XXXX RETHINK THIS WHOLE MESS !!!! !NM NM NM NM*/ - /*XXXX DOUBLEPLUSTHIS!!!! AS AS AS AS*/ - set_uint64(tag, chan_id); - set_uint32(tag+8, circ_id); + replyqueue_t *rq = arg; + (void) sock; + (void) events; + replyqueue_process(rq); } -/** Unpack <b>tag</b> into addr, port, and circ_id. +/** Initialize the cpuworker subsystem. It is OK to call this more than once + * during Tor's lifetime. */ -static void -tag_unpack(const uint8_t *tag, uint64_t *chan_id, circid_t *circ_id) +void +cpu_init(void) { - *chan_id = get_uint64(tag); - *circ_id = get_uint32(tag+8); + if (!replyqueue) { + replyqueue = replyqueue_new(0); + } + if (!reply_event) { + reply_event = tor_event_new(tor_libevent_get_base(), + replyqueue_get_socket(replyqueue), + EV_READ|EV_PERSIST, + replyqueue_process_cb, + replyqueue); + event_add(reply_event, NULL); + } + if (!threadpool) { + threadpool = threadpool_new(get_num_cpus(get_options()), + replyqueue, + worker_state_new, + worker_state_free, + NULL); + } + /* Total voodoo. Can we make this more sensible? */ + max_pending_tasks = get_num_cpus(get_options()) * 64; + crypto_seed_weak_rng(&request_sample_rng); } /** Magic numbers to make sure our cpuworker_requests don't grow any @@ -94,10 +109,6 @@ tag_unpack(const uint8_t *tag, uint64_t *chan_id, circid_t *circ_id) typedef struct cpuworker_request_t { /** Magic number; must be CPUWORKER_REQUEST_MAGIC. */ uint32_t magic; - /** Opaque tag to identify the job */ - uint8_t tag[TAG_LEN]; - /** Task code. Must be one of CPUWORKER_TASK_* */ - uint8_t task; /** Flag: Are we timing this request? */ unsigned timed : 1; @@ -114,8 +125,7 @@ typedef struct cpuworker_request_t { typedef struct cpuworker_reply_t { /** Magic number; must be CPUWORKER_REPLY_MAGIC. */ uint32_t magic; - /** Opaque tag to identify the job; matches the request's tag.*/ - uint8_t tag[TAG_LEN]; + /** True iff we got a successful request. */ uint8_t success; @@ -142,42 +152,45 @@ typedef struct cpuworker_reply_t { uint8_t rend_auth_material[DIGEST_LEN]; } cpuworker_reply_t; -/** Called when the onion key has changed and we need to spawn new - * cpuworkers. Close all currently idle cpuworkers, and mark the last - * rotation time as now. - */ -void -cpuworkers_rotate(void) +typedef struct cpuworker_job_u { + or_circuit_t *circ; + union { + cpuworker_request_t request; + cpuworker_reply_t reply; + } u; +} cpuworker_job_t; + +static int +update_state_threadfn(void *state_, void *work_) { - connection_t *cpuworker; - while ((cpuworker = connection_get_by_type_state(CONN_TYPE_CPUWORKER, - CPUWORKER_STATE_IDLE))) { - connection_mark_for_close(cpuworker); - --num_cpuworkers; - } - last_rotation_time = time(NULL); - if (server_mode(get_options())) - spawn_enough_cpuworkers(); + worker_state_t *state = state_; + worker_state_t *update = work_; + server_onion_keys_free(state->onion_keys); + state->onion_keys = update->onion_keys; + update->onion_keys = NULL; + ++state->generation; + return WQ_RPL_REPLY; } -/** If the cpuworker closes the connection, - * mark it as closed and spawn a new one as needed. */ -int -connection_cpu_reached_eof(connection_t *conn) +/** Called when the onion key has changed so update all CPU worker(s) with + * new function pointers with which a new state will be generated. + */ +void +cpuworkers_rotate_keyinfo(void) { - log_warn(LD_GENERAL,"Read eof. CPU worker died unexpectedly."); - if (conn->state != CPUWORKER_STATE_IDLE) { - /* the circ associated with this cpuworker will have to wait until - * it gets culled in run_connection_housekeeping(), since we have - * no way to find out which circ it was. */ - log_warn(LD_GENERAL,"...and it left a circuit queued; abandoning circ."); - num_cpuworkers_busy--; + if (!threadpool) { + /* If we're a client, then we won't have cpuworkers, and we won't need + * to tell them to rotate their state. + */ + return; + } + if (threadpool_queue_update(threadpool, + worker_state_new, + update_state_threadfn, + worker_state_free, + NULL)) { + log_warn(LD_OR, "Failed to queue key update for worker threads."); } - num_cpuworkers--; - spawn_enough_cpuworkers(); /* try to regrow. hope we don't end up - spinning. */ - connection_mark_for_close(conn); - return 0; } /** Indexed by handshake type: how many onionskins have we processed and @@ -197,8 +210,6 @@ static uint64_t onionskins_usec_roundtrip[MAX_ONION_HANDSHAKE_TYPE+1]; * time. (microseconds) */ #define MAX_BELIEVABLE_ONIONSKIN_DELAY (2*1000*1000) -static tor_weak_rng_t request_sample_rng = TOR_WEAK_RNG_INIT; - /** Return true iff we'd like to measure a handshake of type * <b>onionskin_type</b>. Call only from the main thread. */ static int @@ -286,433 +297,275 @@ cpuworker_log_onionskin_overhead(int severity, int onionskin_type, onionskin_type_name, (unsigned)overhead, relative_overhead*100); } -/** Called when we get data from a cpuworker. If the answer is not complete, - * wait for a complete answer. If the answer is complete, - * process it as appropriate. - */ -int -connection_cpu_process_inbuf(connection_t *conn) -{ - uint64_t chan_id; - circid_t circ_id; - channel_t *p_chan = NULL; - circuit_t *circ; - - tor_assert(conn); - tor_assert(conn->type == CONN_TYPE_CPUWORKER); - - if (!connection_get_inbuf_len(conn)) - return 0; - - if (conn->state == CPUWORKER_STATE_BUSY_ONION) { - cpuworker_reply_t rpl; - if (connection_get_inbuf_len(conn) < sizeof(cpuworker_reply_t)) - return 0; /* not yet */ - tor_assert(connection_get_inbuf_len(conn) == sizeof(cpuworker_reply_t)); - - connection_fetch_from_buf((void*)&rpl,sizeof(cpuworker_reply_t),conn); - - tor_assert(rpl.magic == CPUWORKER_REPLY_MAGIC); - - if (rpl.timed && rpl.success && - rpl.handshake_type <= MAX_ONION_HANDSHAKE_TYPE) { - /* Time how long this request took. The handshake_type check should be - needless, but let's leave it in to be safe. */ - struct timeval tv_end, tv_diff; - int64_t usec_roundtrip; - tor_gettimeofday(&tv_end); - timersub(&tv_end, &rpl.started_at, &tv_diff); - usec_roundtrip = ((int64_t)tv_diff.tv_sec)*1000000 + tv_diff.tv_usec; - if (usec_roundtrip >= 0 && - usec_roundtrip < MAX_BELIEVABLE_ONIONSKIN_DELAY) { - ++onionskins_n_processed[rpl.handshake_type]; - onionskins_usec_internal[rpl.handshake_type] += rpl.n_usec; - onionskins_usec_roundtrip[rpl.handshake_type] += usec_roundtrip; - if (onionskins_n_processed[rpl.handshake_type] >= 500000) { - /* Scale down every 500000 handshakes. On a busy server, that's - * less impressive than it sounds. */ - onionskins_n_processed[rpl.handshake_type] /= 2; - onionskins_usec_internal[rpl.handshake_type] /= 2; - onionskins_usec_roundtrip[rpl.handshake_type] /= 2; - } - } - } - /* parse out the circ it was talking about */ - tag_unpack(rpl.tag, &chan_id, &circ_id); - circ = NULL; - log_debug(LD_OR, - "Unpacking cpuworker reply, chan_id is " U64_FORMAT - ", circ_id is %u", - U64_PRINTF_ARG(chan_id), (unsigned)circ_id); - p_chan = channel_find_by_global_id(chan_id); - - if (p_chan) - circ = circuit_get_by_circid_channel(circ_id, p_chan); - - if (rpl.success == 0) { - log_debug(LD_OR, - "decoding onionskin failed. " - "(Old key or bad software.) Closing."); - if (circ) - circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); - goto done_processing; - } - if (!circ) { - /* This happens because somebody sends us a destroy cell and the - * circuit goes away, while the cpuworker is working. This is also - * why our tag doesn't include a pointer to the circ, because we'd - * never know if it's still valid. - */ - log_debug(LD_OR,"processed onion for a circ that's gone. Dropping."); - goto done_processing; - } - tor_assert(! CIRCUIT_IS_ORIGIN(circ)); - if (onionskin_answer(TO_OR_CIRCUIT(circ), - &rpl.created_cell, - (const char*)rpl.keys, - rpl.rend_auth_material) < 0) { - log_warn(LD_OR,"onionskin_answer failed. Closing."); - circuit_mark_for_close(circ, END_CIRC_REASON_INTERNAL); - goto done_processing; - } - log_debug(LD_OR,"onionskin_answer succeeded. Yay."); - } else { - tor_assert(0); /* don't ask me to do handshakes yet */ - } - - done_processing: - conn->state = CPUWORKER_STATE_IDLE; - num_cpuworkers_busy--; - if (conn->timestamp_created < last_rotation_time) { - connection_mark_for_close(conn); - num_cpuworkers--; - spawn_enough_cpuworkers(); - } else { - process_pending_task(conn); - } - return 0; -} - -/** Implement a cpuworker. 'data' is an fdarray as returned by socketpair. - * Read and writes from fdarray[1]. Reads requests, writes answers. - * - * Request format: - * cpuworker_request_t. - * Response format: - * cpuworker_reply_t - */ +/** Handle a reply from the worker threads. */ static void -cpuworker_main(void *data) +cpuworker_onion_handshake_replyfn(void *work_) { - /* For talking to the parent thread/process */ - tor_socket_t *fdarray = data; - tor_socket_t fd; - - /* variables for onion processing */ - server_onion_keys_t onion_keys; - cpuworker_request_t req; + cpuworker_job_t *job = work_; cpuworker_reply_t rpl; - - fd = fdarray[1]; /* this side is ours */ -#ifndef TOR_IS_MULTITHREADED - tor_close_socket(fdarray[0]); /* this is the side of the socketpair the - * parent uses */ - tor_free_all(1); /* so the child doesn't hold the parent's fd's open */ - handle_signals(0); /* ignore interrupts from the keyboard, etc */ -#endif - tor_free(data); - - setup_server_onion_keys(&onion_keys); - - for (;;) { - if (read_all(fd, (void *)&req, sizeof(req), 1) != sizeof(req)) { - log_info(LD_OR, "read request failed. Exiting."); - goto end; - } - tor_assert(req.magic == CPUWORKER_REQUEST_MAGIC); - - memset(&rpl, 0, sizeof(rpl)); - - if (req.task == CPUWORKER_TASK_ONION) { - const create_cell_t *cc = &req.create_cell; - created_cell_t *cell_out = &rpl.created_cell; - struct timeval tv_start, tv_end; - int n; - rpl.timed = req.timed; - rpl.started_at = req.started_at; - rpl.handshake_type = cc->handshake_type; - if (req.timed) - tor_gettimeofday(&tv_start); - n = onion_skin_server_handshake(cc->handshake_type, - cc->onionskin, cc->handshake_len, - &onion_keys, - cell_out->reply, - rpl.keys, CPATH_KEY_MATERIAL_LEN, - rpl.rend_auth_material); - if (n < 0) { - /* failure */ - log_debug(LD_OR,"onion_skin_server_handshake failed."); - memset(&rpl, 0, sizeof(rpl)); - memcpy(rpl.tag, req.tag, TAG_LEN); - rpl.success = 0; - } else { - /* success */ - log_debug(LD_OR,"onion_skin_server_handshake succeeded."); - memcpy(rpl.tag, req.tag, TAG_LEN); - cell_out->handshake_len = n; - switch (cc->cell_type) { - case CELL_CREATE: - cell_out->cell_type = CELL_CREATED; break; - case CELL_CREATE2: - cell_out->cell_type = CELL_CREATED2; break; - case CELL_CREATE_FAST: - cell_out->cell_type = CELL_CREATED_FAST; break; - default: - tor_assert(0); - goto end; - } - rpl.success = 1; - } - rpl.magic = CPUWORKER_REPLY_MAGIC; - if (req.timed) { - struct timeval tv_diff; - int64_t usec; - tor_gettimeofday(&tv_end); - timersub(&tv_end, &tv_start, &tv_diff); - usec = ((int64_t)tv_diff.tv_sec)*1000000 + tv_diff.tv_usec; - if (usec < 0 || usec > MAX_BELIEVABLE_ONIONSKIN_DELAY) - rpl.n_usec = MAX_BELIEVABLE_ONIONSKIN_DELAY; - else - rpl.n_usec = (uint32_t) usec; + or_circuit_t *circ = NULL; + + tor_assert(total_pending_tasks > 0); + --total_pending_tasks; + + /* Could avoid this, but doesn't matter. */ + memcpy(&rpl, &job->u.reply, sizeof(rpl)); + + tor_assert(rpl.magic == CPUWORKER_REPLY_MAGIC); + + if (rpl.timed && rpl.success && + rpl.handshake_type <= MAX_ONION_HANDSHAKE_TYPE) { + /* Time how long this request took. The handshake_type check should be + needless, but let's leave it in to be safe. */ + struct timeval tv_end, tv_diff; + int64_t usec_roundtrip; + tor_gettimeofday(&tv_end); + timersub(&tv_end, &rpl.started_at, &tv_diff); + usec_roundtrip = ((int64_t)tv_diff.tv_sec)*1000000 + tv_diff.tv_usec; + if (usec_roundtrip >= 0 && + usec_roundtrip < MAX_BELIEVABLE_ONIONSKIN_DELAY) { + ++onionskins_n_processed[rpl.handshake_type]; + onionskins_usec_internal[rpl.handshake_type] += rpl.n_usec; + onionskins_usec_roundtrip[rpl.handshake_type] += usec_roundtrip; + if (onionskins_n_processed[rpl.handshake_type] >= 500000) { + /* Scale down every 500000 handshakes. On a busy server, that's + * less impressive than it sounds. */ + onionskins_n_processed[rpl.handshake_type] /= 2; + onionskins_usec_internal[rpl.handshake_type] /= 2; + onionskins_usec_roundtrip[rpl.handshake_type] /= 2; } - if (write_all(fd, (void*)&rpl, sizeof(rpl), 1) != sizeof(rpl)) { - log_err(LD_BUG,"writing response buf failed. Exiting."); - goto end; - } - log_debug(LD_OR,"finished writing response."); - } else if (req.task == CPUWORKER_TASK_SHUTDOWN) { - log_info(LD_OR,"Clean shutdown: exiting"); - goto end; } - memwipe(&req, 0, sizeof(req)); - memwipe(&rpl, 0, sizeof(req)); } - end: - memwipe(&req, 0, sizeof(req)); - memwipe(&rpl, 0, sizeof(req)); - release_server_onion_keys(&onion_keys); - tor_close_socket(fd); - crypto_thread_cleanup(); - spawn_exit(); -} -/** Launch a new cpuworker. Return 0 if we're happy, -1 if we failed. - */ -static int -spawn_cpuworker(void) -{ - tor_socket_t *fdarray; - tor_socket_t fd; - connection_t *conn; - int err; - - fdarray = tor_malloc(sizeof(tor_socket_t)*2); - if ((err = tor_socketpair(AF_UNIX, SOCK_STREAM, 0, fdarray)) < 0) { - log_warn(LD_NET, "Couldn't construct socketpair for cpuworker: %s", - tor_socket_strerror(-err)); - tor_free(fdarray); - return -1; - } - - tor_assert(SOCKET_OK(fdarray[0])); - tor_assert(SOCKET_OK(fdarray[1])); + circ = job->circ; - fd = fdarray[0]; - spawn_func(cpuworker_main, (void*)fdarray); - log_debug(LD_OR,"just spawned a cpu worker."); -#ifndef TOR_IS_MULTITHREADED - tor_close_socket(fdarray[1]); /* don't need the worker's side of the pipe */ - tor_free(fdarray); -#endif + log_debug(LD_OR, + "Unpacking cpuworker reply %p, circ=%p, success=%d", + job, circ, rpl.success); - conn = connection_new(CONN_TYPE_CPUWORKER, AF_UNIX); + if (circ->base_.magic == DEAD_CIRCUIT_MAGIC) { + /* The circuit was supposed to get freed while the reply was + * pending. Instead, it got left for us to free so that we wouldn't freak + * out when the job->circ field wound up pointing to nothing. */ + log_debug(LD_OR, "Circuit died while reply was pending. Freeing memory."); + circ->base_.magic = 0; + tor_free(circ); + goto done_processing; + } - /* set up conn so it's got all the data we need to remember */ - conn->s = fd; - conn->address = tor_strdup("localhost"); - tor_addr_make_unspec(&conn->addr); + circ->workqueue_entry = NULL; - if (set_socket_nonblocking(fd) == -1) { - connection_free(conn); /* this closes fd */ - return -1; + if (TO_CIRCUIT(circ)->marked_for_close) { + /* We already marked this circuit; we can't call it open. */ + log_debug(LD_OR,"circuit is already marked."); + goto done_processing; } - if (connection_add(conn) < 0) { /* no space, forget it */ - log_warn(LD_NET,"connection_add for cpuworker failed. Giving up."); - connection_free(conn); /* this closes fd */ - return -1; + if (rpl.success == 0) { + log_debug(LD_OR, + "decoding onionskin failed. " + "(Old key or bad software.) Closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto done_processing; } - conn->state = CPUWORKER_STATE_IDLE; - connection_start_reading(conn); + if (onionskin_answer(circ, + &rpl.created_cell, + (const char*)rpl.keys, + rpl.rend_auth_material) < 0) { + log_warn(LD_OR,"onionskin_answer failed. Closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto done_processing; + } + log_debug(LD_OR,"onionskin_answer succeeded. Yay."); - return 0; /* success */ + done_processing: + memwipe(&rpl, 0, sizeof(rpl)); + memwipe(job, 0, sizeof(*job)); + tor_free(job); + queue_pending_tasks(); } -/** If we have too few or too many active cpuworkers, try to spawn new ones - * or kill idle ones. - */ -static void -spawn_enough_cpuworkers(void) +/** Implementation function for onion handshake requests. */ +static int +cpuworker_onion_handshake_threadfn(void *state_, void *work_) { - int num_cpuworkers_needed = get_num_cpus(get_options()); - int reseed = 0; + worker_state_t *state = state_; + cpuworker_job_t *job = work_; - if (num_cpuworkers_needed < MIN_CPUWORKERS) - num_cpuworkers_needed = MIN_CPUWORKERS; - if (num_cpuworkers_needed > MAX_CPUWORKERS) - num_cpuworkers_needed = MAX_CPUWORKERS; + /* variables for onion processing */ + server_onion_keys_t *onion_keys = state->onion_keys; + cpuworker_request_t req; + cpuworker_reply_t rpl; - while (num_cpuworkers < num_cpuworkers_needed) { - if (spawn_cpuworker() < 0) { - log_warn(LD_GENERAL,"Cpuworker spawn failed. Will try again later."); - return; + memcpy(&req, &job->u.request, sizeof(req)); + + tor_assert(req.magic == CPUWORKER_REQUEST_MAGIC); + memset(&rpl, 0, sizeof(rpl)); + + const create_cell_t *cc = &req.create_cell; + created_cell_t *cell_out = &rpl.created_cell; + struct timeval tv_start = {0,0}, tv_end; + int n; + rpl.timed = req.timed; + rpl.started_at = req.started_at; + rpl.handshake_type = cc->handshake_type; + if (req.timed) + tor_gettimeofday(&tv_start); + n = onion_skin_server_handshake(cc->handshake_type, + cc->onionskin, cc->handshake_len, + onion_keys, + cell_out->reply, + rpl.keys, CPATH_KEY_MATERIAL_LEN, + rpl.rend_auth_material); + if (n < 0) { + /* failure */ + log_debug(LD_OR,"onion_skin_server_handshake failed."); + memset(&rpl, 0, sizeof(rpl)); + rpl.success = 0; + } else { + /* success */ + log_debug(LD_OR,"onion_skin_server_handshake succeeded."); + cell_out->handshake_len = n; + switch (cc->cell_type) { + case CELL_CREATE: + cell_out->cell_type = CELL_CREATED; break; + case CELL_CREATE2: + cell_out->cell_type = CELL_CREATED2; break; + case CELL_CREATE_FAST: + cell_out->cell_type = CELL_CREATED_FAST; break; + default: + tor_assert(0); + return WQ_RPL_SHUTDOWN; } - num_cpuworkers++; - reseed++; + rpl.success = 1; + } + rpl.magic = CPUWORKER_REPLY_MAGIC; + if (req.timed) { + struct timeval tv_diff; + int64_t usec; + tor_gettimeofday(&tv_end); + timersub(&tv_end, &tv_start, &tv_diff); + usec = ((int64_t)tv_diff.tv_sec)*1000000 + tv_diff.tv_usec; + if (usec < 0 || usec > MAX_BELIEVABLE_ONIONSKIN_DELAY) + rpl.n_usec = MAX_BELIEVABLE_ONIONSKIN_DELAY; + else + rpl.n_usec = (uint32_t) usec; } - if (reseed) - crypto_seed_weak_rng(&request_sample_rng); + memcpy(&job->u.reply, &rpl, sizeof(rpl)); + + memwipe(&req, 0, sizeof(req)); + memwipe(&rpl, 0, sizeof(req)); + return WQ_RPL_REPLY; } -/** Take a pending task from the queue and assign it to 'cpuworker'. */ +/** Take pending tasks from the queue and assign them to cpuworkers. */ static void -process_pending_task(connection_t *cpuworker) +queue_pending_tasks(void) { or_circuit_t *circ; create_cell_t *onionskin = NULL; - tor_assert(cpuworker); - - /* for now only process onion tasks */ - - circ = onion_next_task(&onionskin); - if (!circ) - return; - if (assign_onionskin_to_cpuworker(cpuworker, circ, onionskin)) - log_warn(LD_OR,"assign_to_cpuworker failed. Ignoring."); -} + while (total_pending_tasks < max_pending_tasks) { + circ = onion_next_task(&onionskin); -/** How long should we let a cpuworker stay busy before we give - * up on it and decide that we have a bug or infinite loop? - * This value is high because some servers with low memory/cpu - * sometimes spend an hour or more swapping, and Tor starves. */ -#define CPUWORKER_BUSY_TIMEOUT (60*60*12) + if (!circ) + return; -/** We have a bug that I can't find. Sometimes, very rarely, cpuworkers get - * stuck in the 'busy' state, even though the cpuworker process thinks of - * itself as idle. I don't know why. But here's a workaround to kill any - * cpuworker that's been busy for more than CPUWORKER_BUSY_TIMEOUT. - */ -static void -cull_wedged_cpuworkers(void) -{ - time_t now = time(NULL); - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - if (!conn->marked_for_close && - conn->type == CONN_TYPE_CPUWORKER && - conn->state == CPUWORKER_STATE_BUSY_ONION && - conn->timestamp_lastwritten + CPUWORKER_BUSY_TIMEOUT < now) { - log_notice(LD_BUG, - "closing wedged cpuworker. Can somebody find the bug?"); - num_cpuworkers_busy--; - num_cpuworkers--; - connection_mark_for_close(conn); - } - } SMARTLIST_FOREACH_END(conn); + if (assign_onionskin_to_cpuworker(circ, onionskin)) + log_warn(LD_OR,"assign_to_cpuworker failed. Ignoring."); + } } /** Try to tell a cpuworker to perform the public key operations necessary to * respond to <b>onionskin</b> for the circuit <b>circ</b>. * - * If <b>cpuworker</b> is defined, assert that he's idle, and use him. Else, - * look for an idle cpuworker and use him. If none idle, queue task onto the - * pending onion list and return. Return 0 if we successfully assign the - * task, or -1 on failure. + * Return 0 if we successfully assign the task, or -1 on failure. */ int -assign_onionskin_to_cpuworker(connection_t *cpuworker, - or_circuit_t *circ, +assign_onionskin_to_cpuworker(or_circuit_t *circ, create_cell_t *onionskin) { + workqueue_entry_t *queue_entry; + cpuworker_job_t *job; cpuworker_request_t req; - time_t now = approx_time(); - static time_t last_culled_cpuworkers = 0; int should_time; - /* Checking for wedged cpuworkers requires a linear search over all - * connections, so let's do it only once a minute. - */ -#define CULL_CPUWORKERS_INTERVAL 60 + tor_assert(threadpool); - if (last_culled_cpuworkers + CULL_CPUWORKERS_INTERVAL <= now) { - cull_wedged_cpuworkers(); - spawn_enough_cpuworkers(); - last_culled_cpuworkers = now; + if (!circ->p_chan) { + log_info(LD_OR,"circ->p_chan gone. Failing circ."); + tor_free(onionskin); + return -1; } - if (1) { - if (num_cpuworkers_busy == num_cpuworkers) { - log_debug(LD_OR,"No idle cpuworkers. Queuing."); - if (onion_pending_add(circ, onionskin) < 0) { - tor_free(onionskin); - return -1; - } - return 0; - } - - if (!cpuworker) - cpuworker = connection_get_by_type_state(CONN_TYPE_CPUWORKER, - CPUWORKER_STATE_IDLE); - - tor_assert(cpuworker); - - if (!circ->p_chan) { - log_info(LD_OR,"circ->p_chan gone. Failing circ."); + if (total_pending_tasks >= max_pending_tasks) { + log_debug(LD_OR,"No idle cpuworkers. Queuing."); + if (onion_pending_add(circ, onionskin) < 0) { tor_free(onionskin); return -1; } + return 0; + } - if (connection_or_digest_is_known_relay(circ->p_chan->identity_digest)) - rep_hist_note_circuit_handshake_completed(onionskin->handshake_type); + if (connection_or_digest_is_known_relay(circ->p_chan->identity_digest)) + rep_hist_note_circuit_handshake_assigned(onionskin->handshake_type); - should_time = should_time_request(onionskin->handshake_type); - memset(&req, 0, sizeof(req)); - req.magic = CPUWORKER_REQUEST_MAGIC; - tag_pack(req.tag, circ->p_chan->global_identifier, - circ->p_circ_id); - req.timed = should_time; + should_time = should_time_request(onionskin->handshake_type); + memset(&req, 0, sizeof(req)); + req.magic = CPUWORKER_REQUEST_MAGIC; + req.timed = should_time; - cpuworker->state = CPUWORKER_STATE_BUSY_ONION; - /* touch the lastwritten timestamp, since that's how we check to - * see how long it's been since we asked the question, and sometimes - * we check before the first call to connection_handle_write(). */ - cpuworker->timestamp_lastwritten = now; - num_cpuworkers_busy++; + memcpy(&req.create_cell, onionskin, sizeof(create_cell_t)); - req.task = CPUWORKER_TASK_ONION; - memcpy(&req.create_cell, onionskin, sizeof(create_cell_t)); + tor_free(onionskin); - tor_free(onionskin); + if (should_time) + tor_gettimeofday(&req.started_at); - if (should_time) - tor_gettimeofday(&req.started_at); + job = tor_malloc_zero(sizeof(cpuworker_job_t)); + job->circ = circ; + memcpy(&job->u.request, &req, sizeof(req)); + memwipe(&req, 0, sizeof(req)); - connection_write_to_buf((void*)&req, sizeof(req), cpuworker); - memwipe(&req, 0, sizeof(req)); + ++total_pending_tasks; + queue_entry = threadpool_queue_work(threadpool, + cpuworker_onion_handshake_threadfn, + cpuworker_onion_handshake_replyfn, + job); + if (!queue_entry) { + log_warn(LD_BUG, "Couldn't queue work on threadpool"); + tor_free(job); + return -1; } + + log_debug(LD_OR, "Queued task %p (qe=%p, circ=%p)", + job, queue_entry, job->circ); + + circ->workqueue_entry = queue_entry; + return 0; } +/** If <b>circ</b> has a pending handshake that hasn't been processed yet, + * remove it from the worker queue. */ +void +cpuworker_cancel_circ_handshake(or_circuit_t *circ) +{ + cpuworker_job_t *job; + if (circ->workqueue_entry == NULL) + return; + + job = workqueue_entry_cancel(circ->workqueue_entry); + if (job) { + /* It successfully cancelled. */ + memwipe(job, 0xe0, sizeof(*job)); + tor_free(job); + tor_assert(total_pending_tasks > 0); + --total_pending_tasks; + /* if (!job), this is done in cpuworker_onion_handshake_replyfn. */ + circ->workqueue_entry = NULL; + } +} + diff --git a/src/or/cpuworker.h b/src/or/cpuworker.h index 317cef43ba..70a595e472 100644 --- a/src/or/cpuworker.h +++ b/src/or/cpuworker.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,19 +13,17 @@ #define TOR_CPUWORKER_H void cpu_init(void); -void cpuworkers_rotate(void); -int connection_cpu_finished_flushing(connection_t *conn); -int connection_cpu_reached_eof(connection_t *conn); -int connection_cpu_process_inbuf(connection_t *conn); +void cpuworkers_rotate_keyinfo(void); + struct create_cell_t; -int assign_onionskin_to_cpuworker(connection_t *cpuworker, - or_circuit_t *circ, +int assign_onionskin_to_cpuworker(or_circuit_t *circ, struct create_cell_t *onionskin); uint64_t estimated_usec_for_onionskins(uint32_t n_requests, uint16_t onionskin_type); void cpuworker_log_onionskin_overhead(int severity, int onionskin_type, const char *onionskin_type_name); +void cpuworker_cancel_circ_handshake(or_circuit_t *circ); #endif diff --git a/src/or/directory.c b/src/or/directory.c index 3752367c44..d2b6b86f6d 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" @@ -20,6 +20,7 @@ #include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "relay.h" #include "rendclient.h" #include "rendcommon.h" #include "rephist.h" @@ -63,19 +64,13 @@ static void directory_send_command(dir_connection_t *conn, time_t if_modified_since); static int directory_handle_command(dir_connection_t *conn); static int body_is_plausible(const char *body, size_t body_len, int purpose); -static int purpose_needs_anonymity(uint8_t dir_purpose, - uint8_t router_purpose); static char *http_get_header(const char *headers, const char *which); static void http_set_address_origin(const char *headers, connection_t *conn); -static void connection_dir_download_v2_networkstatus_failed( - dir_connection_t *conn, int status_code); static void connection_dir_download_routerdesc_failed(dir_connection_t *conn); static void connection_dir_bridge_routerdesc_failed(dir_connection_t *conn); static void connection_dir_download_cert_failed( dir_connection_t *conn, int status_code); static void connection_dir_retry_bridges(smartlist_t *descs); -static void dir_networkstatus_download_failed(smartlist_t *failed, - int status_code); static void dir_routerdesc_download_failed(smartlist_t *failed, int status_code, int router_purpose, @@ -86,8 +81,7 @@ static void dir_microdesc_download_failed(smartlist_t *failed, static void note_client_request(int purpose, int compressed, size_t bytes); static int client_likes_consensus(networkstatus_t *v, const char *want_url); -static void directory_initiate_command_rend(const char *address, - const tor_addr_t *addr, +static void directory_initiate_command_rend(const tor_addr_t *addr, uint16_t or_port, uint16_t dir_port, const char *digest, @@ -124,7 +118,7 @@ static void directory_initiate_command_rend(const char *address, /** Return true iff the directory purpose <b>dir_purpose</b> (and if it's * fetching descriptors, it's fetching them for <b>router_purpose</b>) * must use an anonymous connection to a directory. */ -static int +STATIC int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose) { if (get_options()->AllDirActionsPrivate) @@ -135,7 +129,6 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose) if (dir_purpose == DIR_PURPOSE_UPLOAD_DIR || dir_purpose == DIR_PURPOSE_UPLOAD_VOTE || dir_purpose == DIR_PURPOSE_UPLOAD_SIGNATURES || - dir_purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS || dir_purpose == DIR_PURPOSE_FETCH_STATUS_VOTE || dir_purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES || dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS || @@ -154,16 +147,10 @@ authdir_type_to_string(dirinfo_type_t auth) { char *result; smartlist_t *lst = smartlist_new(); - if (auth & V1_DIRINFO) - smartlist_add(lst, (void*)"V1"); - if (auth & V2_DIRINFO) - smartlist_add(lst, (void*)"V2"); if (auth & V3_DIRINFO) smartlist_add(lst, (void*)"V3"); if (auth & BRIDGE_DIRINFO) smartlist_add(lst, (void*)"Bridge"); - if (auth & HIDSERV_DIRINFO) - smartlist_add(lst, (void*)"Hidden service"); if (smartlist_len(lst)) { result = smartlist_join_strings(lst, ", ", 0, NULL); } else { @@ -179,18 +166,12 @@ dir_conn_purpose_to_string(int purpose) { switch (purpose) { - case DIR_PURPOSE_FETCH_RENDDESC: - return "hidden-service descriptor fetch"; case DIR_PURPOSE_UPLOAD_DIR: return "server descriptor upload"; - case DIR_PURPOSE_UPLOAD_RENDDESC: - return "hidden-service descriptor upload"; case DIR_PURPOSE_UPLOAD_VOTE: return "server vote upload"; case DIR_PURPOSE_UPLOAD_SIGNATURES: return "consensus signature upload"; - case DIR_PURPOSE_FETCH_V2_NETWORKSTATUS: - return "network-status fetch"; case DIR_PURPOSE_FETCH_SERVERDESC: return "server descriptor fetch"; case DIR_PURPOSE_FETCH_EXTRAINFO: @@ -215,9 +196,49 @@ dir_conn_purpose_to_string(int purpose) return "(unknown)"; } -/** Return true iff <b>identity_digest</b> is the digest of a router we - * believe to support extrainfo downloads. (If <b>is_authority</b> we do - * additional checking that's only valid for authorities.) */ +/** Return the requisite directory information types. */ +STATIC dirinfo_type_t +dir_fetch_type(int dir_purpose, int router_purpose, const char *resource) +{ + dirinfo_type_t type; + switch (dir_purpose) { + case DIR_PURPOSE_FETCH_EXTRAINFO: + type = EXTRAINFO_DIRINFO; + if (router_purpose == ROUTER_PURPOSE_BRIDGE) + type |= BRIDGE_DIRINFO; + else + type |= V3_DIRINFO; + break; + case DIR_PURPOSE_FETCH_SERVERDESC: + if (router_purpose == ROUTER_PURPOSE_BRIDGE) + type = BRIDGE_DIRINFO; + else + type = V3_DIRINFO; + break; + case DIR_PURPOSE_FETCH_STATUS_VOTE: + case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES: + case DIR_PURPOSE_FETCH_CERTIFICATE: + type = V3_DIRINFO; + break; + case DIR_PURPOSE_FETCH_CONSENSUS: + type = V3_DIRINFO; + if (resource && !strcmp(resource, "microdesc")) + type |= MICRODESC_DIRINFO; + break; + case DIR_PURPOSE_FETCH_MICRODESC: + type = MICRODESC_DIRINFO; + break; + default: + log_warn(LD_BUG, "Unexpected purpose %d", (int)dir_purpose); + type = NO_DIRINFO; + break; + } + return type; +} + +/** Return true iff <b>identity_digest</b> is the digest of a router which + * says that it caches extrainfos. (If <b>is_authority</b> we always + * believe that to be true.) */ int router_supports_extrainfo(const char *identity_digest, int is_authority) { @@ -258,13 +279,13 @@ directories_have_accepted_server_descriptor(void) /** Start a connection to every suitable directory authority, using * connection purpose <b>dir_purpose</b> and uploading <b>payload</b> * (of length <b>payload_len</b>). The dir_purpose should be one of - * 'DIR_PURPOSE_UPLOAD_DIR' or 'DIR_PURPOSE_UPLOAD_RENDDESC'. + * 'DIR_PURPOSE_UPLOAD_{DIR|VOTE|SIGNATURES}'. * * <b>router_purpose</b> describes the type of descriptor we're * publishing, if we're publishing a descriptor -- e.g. general or bridge. * - * <b>type</b> specifies what sort of dir authorities (V1, V2, - * HIDSERV, BRIDGE) we should upload to. + * <b>type</b> specifies what sort of dir authorities (V3, + * BRIDGE, etc) we should upload to. * * If <b>extrainfo_len</b> is nonzero, the first <b>payload_len</b> bytes of * <b>payload</b> hold a router descriptor, and the next <b>extrainfo_len</b> @@ -279,7 +300,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, size_t payload_len, size_t extrainfo_len) { const or_options_t *options = get_options(); - int post_via_tor; + dir_indirection_t indirection; const smartlist_t *dirservers = router_get_trusted_dir_servers(); int found = 0; const int exclude_self = (dir_purpose == DIR_PURPOSE_UPLOAD_VOTE || @@ -296,8 +317,12 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, if ((type & ds->type) == 0) continue; - if (exclude_self && router_digest_is_me(ds->digest)) + if (exclude_self && router_digest_is_me(ds->digest)) { + /* we don't upload to ourselves, but at least there's now at least + * one authority of this type that has what we wanted to upload. */ + found = 1; continue; + } if (options->StrictNodes && routerset_contains_routerstatus(options->ExcludeNodes, rs, -1)) { @@ -319,11 +344,19 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, (int) extrainfo_len); } tor_addr_from_ipv4h(&ds_addr, ds->addr); - post_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose) || - !fascist_firewall_allows_address_dir(&ds_addr, ds->dir_port); + if (purpose_needs_anonymity(dir_purpose, router_purpose)) { + indirection = DIRIND_ANONYMOUS; + } else if (!fascist_firewall_allows_address_dir(&ds_addr,ds->dir_port)) { + if (fascist_firewall_allows_address_or(&ds_addr,ds->or_port)) + indirection = DIRIND_ONEHOP; + else + indirection = DIRIND_ANONYMOUS; + } else { + indirection = DIRIND_DIRECT_CONN; + } directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, - post_via_tor, + indirection, NULL, payload, upload_len, 0); } SMARTLIST_FOREACH_END(ds); if (!found) { @@ -350,15 +383,12 @@ should_use_directory_guards(const or_options_t *options) /* If we're configured to fetch directory info aggressively or of a * nonstandard type, don't use directory guards. */ if (options->DownloadExtraInfo || options->FetchDirInfoEarly || - options->FetchDirInfoExtraEarly || options->FetchUselessDescriptors || - options->FetchV2Networkstatus) - return 0; - if (! options->PreferTunneledDirConns) + options->FetchDirInfoExtraEarly || options->FetchUselessDescriptors) return 0; return 1; } -/** Pick an unconsetrained directory server from among our guards, the latest +/** Pick an unconstrained directory server from among our guards, the latest * networkstatus, or the fallback dirservers, for use in downloading * information of type <b>type</b>, and return its routerstatus. */ static const routerstatus_t * @@ -394,55 +424,21 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, * Use <b>pds_flags</b> as arguments to router_pick_directory_server() * or router_pick_trusteddirserver(). */ -void -directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, - const char *resource, int pds_flags) +MOCK_IMPL(void, directory_get_from_dirserver, (uint8_t dir_purpose, + uint8_t router_purpose, + const char *resource, + int pds_flags)) { const routerstatus_t *rs = NULL; const or_options_t *options = get_options(); int prefer_authority = directory_fetches_from_authorities(options); int require_authority = 0; int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose); - dirinfo_type_t type; + dirinfo_type_t type = dir_fetch_type(dir_purpose, router_purpose, resource); time_t if_modified_since = 0; - /* FFFF we could break this switch into its own function, and call - * it elsewhere in directory.c. -RD */ - switch (dir_purpose) { - case DIR_PURPOSE_FETCH_EXTRAINFO: - type = EXTRAINFO_DIRINFO | - (router_purpose == ROUTER_PURPOSE_BRIDGE ? BRIDGE_DIRINFO : - V3_DIRINFO); - break; - case DIR_PURPOSE_FETCH_V2_NETWORKSTATUS: - type = V2_DIRINFO; - prefer_authority = 1; /* Only v2 authorities have these anyway. */ - require_authority = 1; /* Don't fallback to asking a non-authority */ - break; - case DIR_PURPOSE_FETCH_SERVERDESC: - type = (router_purpose == ROUTER_PURPOSE_BRIDGE ? BRIDGE_DIRINFO : - V3_DIRINFO); - break; - case DIR_PURPOSE_FETCH_RENDDESC: - type = HIDSERV_DIRINFO; - break; - case DIR_PURPOSE_FETCH_STATUS_VOTE: - case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES: - case DIR_PURPOSE_FETCH_CERTIFICATE: - type = V3_DIRINFO; - break; - case DIR_PURPOSE_FETCH_CONSENSUS: - type = V3_DIRINFO; - if (resource && !strcmp(resource,"microdesc")) - type |= MICRODESC_DIRINFO; - break; - case DIR_PURPOSE_FETCH_MICRODESC: - type = MICRODESC_DIRINFO; - break; - default: - log_warn(LD_BUG, "Unexpected purpose %d", (int)dir_purpose); - return; - } + if (type == NO_DIRINFO) + return; if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS) { int flav = FLAV_NS; @@ -450,26 +446,41 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, if (resource) flav = networkstatus_parse_flavor_name(resource); + /* DEFAULT_IF_MODIFIED_SINCE_DELAY is 1/20 of the default consensus + * period of 1 hour. + */ +#define DEFAULT_IF_MODIFIED_SINCE_DELAY (180) if (flav != -1) { /* IF we have a parsed consensus of this type, we can do an * if-modified-time based on it. */ v = networkstatus_get_latest_consensus_by_flavor(flav); - if (v) - if_modified_since = v->valid_after + 180; + if (v) { + /* In networks with particularly short V3AuthVotingIntervals, + * ask for the consensus if it's been modified since half the + * V3AuthVotingInterval of the most recent consensus. */ + time_t ims_delay = DEFAULT_IF_MODIFIED_SINCE_DELAY; + if (v->fresh_until > v->valid_after + && ims_delay > (v->fresh_until - v->valid_after)/2) { + ims_delay = (v->fresh_until - v->valid_after)/2; + } + if_modified_since = v->valid_after + ims_delay; + } } else { /* Otherwise it might be a consensus we don't parse, but which we * do cache. Look at the cached copy, perhaps. */ cached_dir_t *cd = dirserv_get_consensus(resource); + /* We have no method of determining the voting interval from an + * unparsed consensus, so we use the default. */ if (cd) - if_modified_since = cd->published + 180; + if_modified_since = cd->published + DEFAULT_IF_MODIFIED_SINCE_DELAY; } } - if (!options->FetchServerDescriptors && type != HIDSERV_DIRINFO) + if (!options->FetchServerDescriptors) return; if (!get_via_tor) { - if (options->UseBridges && type != BRIDGE_DIRINFO) { + if (options->UseBridges && !(type & BRIDGE_DIRINFO)) { /* We want to ask a running bridge for which we have a descriptor. * * When we ask choose_random_entry() for a bridge, we specify what @@ -484,7 +495,7 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, tor_addr_t addr; routerinfo_t *ri = node->ri; node_get_addr(node, &addr); - directory_initiate_command(ri->address, &addr, + directory_initiate_command(&addr, ri->or_port, 0/*no dirport*/, ri->cache_info.identity_digest, dir_purpose, @@ -496,7 +507,7 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, "nodes are available yet."); return; } else { - if (prefer_authority || type == BRIDGE_DIRINFO) { + if (prefer_authority || (type & BRIDGE_DIRINFO)) { /* 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| @@ -523,33 +534,25 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, return; } } - if (!rs && type != BRIDGE_DIRINFO) { + if (!rs && !(type & BRIDGE_DIRINFO)) { /* */ rs = directory_pick_generic_dirserver(type, pds_flags, dir_purpose); - if (!rs) { - /*XXXX024 I'm pretty sure this can never do any good, since - * rs isn't set. */ + if (!rs) get_via_tor = 1; /* last resort: try routing it via Tor */ - } } } - } else { /* get_via_tor */ + } + + if (get_via_tor) { /* Never use fascistfirewall; we're going via Tor. */ - if (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC) { - /* only ask hidserv authorities, any of them will do */ - pds_flags |= PDS_IGNORE_FASCISTFIREWALL|PDS_ALLOW_SELF; - rs = router_pick_trusteddirserver(HIDSERV_DIRINFO, pds_flags); - } else { - /* anybody with a non-zero dirport will do. Disregard firewalls. */ - pds_flags |= PDS_IGNORE_FASCISTFIREWALL; - rs = router_pick_directory_server(type, pds_flags); - /* If we have any hope of building an indirect conn, we know some router - * descriptors. If (rs==NULL), we can't build circuits anyway, so - * there's no point in falling back to the authorities in this case. */ - } + pds_flags |= PDS_IGNORE_FASCISTFIREWALL; + rs = router_pick_directory_server(type, pds_flags); } + /* If we have any hope of building an indirect conn, we know some router + * descriptors. If (rs==NULL), we can't build circuits anyway, so + * there's no point in falling back to the authorities in this case. */ if (rs) { const dir_indirection_t indirection = get_via_tor ? DIRIND_ANONYMOUS : DIRIND_ONEHOP; @@ -617,9 +620,6 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, { const or_options_t *options = get_options(); const node_t *node; - char address_buf[INET_NTOA_BUF_LEN+1]; - struct in_addr in; - const char *address; tor_addr_t addr; const int anonymized_connection = dirind_is_anon(indirection); node = node_get_by_id(status->identity_digest); @@ -629,13 +629,6 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, "don't have its router descriptor.", routerstatus_describe(status)); return; - } 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)); - address = address_buf; } tor_addr_from_ipv4h(&addr, status->addr); @@ -649,7 +642,7 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, return; } - directory_initiate_command_rend(address, &addr, + directory_initiate_command_rend(&addr, status->or_port, status->dir_port, status->identity_digest, dir_purpose, router_purpose, @@ -662,7 +655,7 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, * upload or download a server or rendezvous * descriptor. <b>dir_purpose</b> determines what * kind of directory connection we're launching, and must be one of - * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC|RENDDESC_V2}. <b>router_purpose</b> + * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC_V2}. <b>router_purpose</b> * specifies the descriptor purposes we have in mind (currently only * used for FETCH_DIR). * @@ -719,11 +712,7 @@ connection_dir_request_failed(dir_connection_t *conn) } if (!entry_list_is_constrained(get_options())) router_set_status(conn->identity_digest, 0); /* don't try him again */ - if (conn->base_.purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS) { - log_info(LD_DIR, "Giving up on directory server at '%s'; retrying", - conn->base_.address); - connection_dir_download_v2_networkstatus_failed(conn, -1); - } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || + if (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { log_info(LD_DIR, "Giving up on serverdesc/extrainfo fetch from " "directory server at '%s'; retrying", @@ -747,48 +736,11 @@ connection_dir_request_failed(dir_connection_t *conn) conn->base_.address); } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC) { log_info(LD_DIR, "Giving up on downloading microdescriptors from " - " directory server at '%s'; will retry", conn->base_.address); + "directory server at '%s'; will retry", conn->base_.address); connection_dir_download_routerdesc_failed(conn); } } -/** Called when an attempt to download one or more network status - * documents on connection <b>conn</b> failed. Decide whether to - * retry the fetch now, later, or never. - */ -static void -connection_dir_download_v2_networkstatus_failed(dir_connection_t *conn, - int status_code) -{ - if (!conn->requested_resource) { - /* We never reached directory_send_command, which means that we never - * opened a network connection. Either we're out of sockets, or the - * network is down. Either way, retrying would be pointless. */ - return; - } - if (!strcmpstart(conn->requested_resource, "all")) { - /* We're a non-authoritative directory cache; try again. Ignore status - * code, since we don't want to keep trying forever in a tight loop - * if all the authorities are shutting us out. */ - const smartlist_t *trusted_dirs = router_get_trusted_dir_servers(); - SMARTLIST_FOREACH(trusted_dirs, dir_server_t *, ds, - download_status_failed(&ds->v2_ns_dl_status, 0)); - directory_get_from_dirserver(conn->base_.purpose, conn->router_purpose, - "all.z", 0 /* don't retry_if_no_servers */); - } else if (!strcmpstart(conn->requested_resource, "fp/")) { - /* We were trying to download by fingerprint; mark them all as having - * failed, and possibly retry them later.*/ - smartlist_t *failed = smartlist_new(); - dir_split_resource_into_fingerprints(conn->requested_resource+3, - failed, NULL, 0); - if (smartlist_len(failed)) { - dir_networkstatus_download_failed(failed, status_code); - SMARTLIST_FOREACH(failed, char *, cp, tor_free(cp)); - } - smartlist_free(failed); - } -} - /** Helper: Attempt to fetch directly the descriptors of each bridge * listed in <b>failed</b>. */ @@ -912,6 +864,7 @@ directory_command_should_use_begindir(const or_options_t *options, int or_port, uint8_t router_purpose, dir_indirection_t indirection) { + (void) router_purpose; if (!or_port) return 0; /* We don't know an ORPort -- no chance. */ if (indirection == DIRIND_DIRECT_CONN || indirection == DIRIND_ANON_DIRPORT) @@ -920,9 +873,6 @@ directory_command_should_use_begindir(const or_options_t *options, if (!fascist_firewall_allows_address_or(addr, or_port) || directory_fetches_from_authorities(options)) return 0; /* We're firewalled or are acting like a relay -- also no. */ - if (!options->TunnelDirConns && - router_purpose != ROUTER_PURPOSE_BRIDGE) - return 0; /* We prefer to avoid using begindir conns. Fine. */ return 1; } @@ -932,7 +882,7 @@ directory_command_should_use_begindir(const or_options_t *options, * <b>supports_begindir</b>, and whose identity key digest is * <b>digest</b>. */ void -directory_initiate_command(const char *address, const tor_addr_t *_addr, +directory_initiate_command(const tor_addr_t *_addr, uint16_t or_port, uint16_t dir_port, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, @@ -940,7 +890,7 @@ directory_initiate_command(const char *address, const tor_addr_t *_addr, const char *payload, size_t payload_len, time_t if_modified_since) { - directory_initiate_command_rend(address, _addr, or_port, dir_port, + directory_initiate_command_rend(_addr, or_port, dir_port, digest, dir_purpose, router_purpose, indirection, resource, payload, payload_len, @@ -954,9 +904,7 @@ directory_initiate_command(const char *address, const tor_addr_t *_addr, static int is_sensitive_dir_purpose(uint8_t dir_purpose) { - return ((dir_purpose == DIR_PURPOSE_FETCH_RENDDESC) || - (dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC) || - (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC) || + return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) || (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) || (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2)); } @@ -964,7 +912,7 @@ is_sensitive_dir_purpose(uint8_t dir_purpose) /** Same as directory_initiate_command(), but accepts rendezvous data to * fetch a hidden service descriptor. */ static void -directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, +directory_initiate_command_rend(const tor_addr_t *_addr, uint16_t or_port, uint16_t dir_port, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, @@ -982,7 +930,6 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, const int anonymized_connection = dirind_is_anon(indirection); tor_addr_t addr; - tor_assert(address); tor_assert(_addr); tor_assert(or_port || dir_port); tor_assert(digest); @@ -1015,7 +962,7 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, /* set up conn so it's got all the data we need to remember */ tor_addr_copy(&conn->base_.addr, &addr); conn->base_.port = use_begindir ? or_port : dir_port; - conn->base_.address = tor_strdup(address); + conn->base_.address = tor_dup_addr(&addr); memcpy(conn->identity_digest, digest, DIGEST_LEN); conn->base_.purpose = dir_purpose; @@ -1250,11 +1197,6 @@ directory_send_command(dir_connection_t *conn, } switch (purpose) { - case DIR_PURPOSE_FETCH_V2_NETWORKSTATUS: - tor_assert(resource); - httpcommand = "GET"; - tor_asprintf(&url, "/tor/status/%s", resource); - break; case DIR_PURPOSE_FETCH_CONSENSUS: /* resource is optional. If present, it's a flavor name */ tor_assert(!payload); @@ -1326,12 +1268,6 @@ directory_send_command(dir_connection_t *conn, httpcommand = "GET"; tor_asprintf(&url, "/tor/rendezvous2/%s", resource); break; - case DIR_PURPOSE_UPLOAD_RENDDESC: - tor_assert(!resource); - tor_assert(payload); - httpcommand = "POST"; - url = tor_strdup("/tor/rendezvous/publish"); - break; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: tor_assert(!resource); tor_assert(payload); @@ -1343,7 +1279,8 @@ directory_send_command(dir_connection_t *conn, return; } - if (strlen(proxystring) + strlen(url) >= 4096) { + /* warn in the non-tunneled case */ + if (direct && (strlen(proxystring) + strlen(url) >= 4096)) { log_warn(LD_BUG, "Squid does not like URLs longer than 4095 bytes, and this " "one is %d bytes long: %s%s", @@ -1387,7 +1324,7 @@ directory_send_command(dir_connection_t *conn, * so it does. Return 0. * Otherwise, return -1. */ -static int +STATIC int parse_http_url(const char *headers, char **url) { char *s, *start, *tmp; @@ -1416,6 +1353,19 @@ parse_http_url(const char *headers, char **url) } } + /* Check if the header is well formed (next sequence + * should be HTTP/1.X\r\n). Assumes we're supporting 1.0? */ + { + unsigned minor_ver; + char ch; + char *e = (char *)eat_whitespace_no_nl(s); + if (2 != tor_sscanf(e, "HTTP/1.%u%c", &minor_ver, &ch)) { + return -1; + } + if (ch != '\r') + return -1; + } + if (s-start < 5 || strcmpstart(start,"/tor/")) { /* need to rewrite it */ *url = tor_malloc(s - start + 5); strlcpy(*url,"/tor", s-start+5); @@ -1462,13 +1412,14 @@ http_set_address_origin(const char *headers, connection_t *conn) if (!fwd) fwd = http_get_header(headers, "X-Forwarded-For: "); if (fwd) { - struct in_addr in; - if (!tor_inet_aton(fwd, &in) || is_internal_IP(ntohl(in.s_addr), 0)) { - log_debug(LD_DIR, "Ignoring unrecognized or internal IP %s", - escaped(fwd)); + tor_addr_t toraddr; + if (tor_addr_parse(&toraddr,fwd) == -1 || + tor_addr_is_internal(&toraddr,0)) { + log_debug(LD_DIR, "Ignoring local/internal IP %s", escaped(fwd)); tor_free(fwd); return; } + tor_free(conn->address); conn->address = tor_strdup(fwd); tor_free(fwd); @@ -1565,8 +1516,8 @@ parse_http_response(const char *headers, int *code, time_t *date, } /** Return true iff <b>body</b> doesn't start with a plausible router or - * running-list or directory opening. This is a sign of possible compression. - **/ + * network-status or microdescriptor opening. This is a sign of possible + * compression. */ static int body_is_plausible(const char *body, size_t len, int purpose) { @@ -1578,20 +1529,16 @@ body_is_plausible(const char *body, size_t len, int purpose) if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { return (!strcmpstart(body,"onion-key")); } - if (purpose != DIR_PURPOSE_FETCH_RENDDESC) { + if (1) { if (!strcmpstart(body,"router") || - !strcmpstart(body,"signed-directory") || - !strcmpstart(body,"network-status") || - !strcmpstart(body,"running-routers")) - return 1; + !strcmpstart(body,"network-status")) + return 1; for (i=0;i<32;++i) { if (!TOR_ISPRINT(body[i]) && !TOR_ISSPACE(body[i])) return 0; } - return 1; - } else { - return 1; } + return 1; } /** Called when we've just fetched a bunch of router descriptors in @@ -1626,8 +1573,9 @@ load_downloaded_routers(const char *body, smartlist_t *which, added = router_load_routers_from_string(body, NULL, SAVED_NOWHERE, which, descriptor_digests, buf); - control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, - count_loading_descriptors_progress()); + if (general) + control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, + count_loading_descriptors_progress()); return added; } @@ -1646,17 +1594,17 @@ connection_dir_client_reached_eof(dir_connection_t *conn) char *body; char *headers; char *reason = NULL; - size_t body_len=0, orig_len=0; + size_t body_len = 0, orig_len = 0; int status_code; - time_t date_header=0; + time_t date_header = 0; long delta; compress_method_t compression; int plausible; - int skewed=0; + 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_MICRODESC); - int was_compressed=0; + int was_compressed = 0; time_t now = time(NULL); int src_code; @@ -1810,77 +1758,6 @@ connection_dir_client_reached_eof(dir_connection_t *conn) } } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS) { - smartlist_t *which = NULL; - v2_networkstatus_source_t source; - char *cp; - log_info(LD_DIR,"Received networkstatus objects (size %d) from server " - "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); - if (status_code != 200) { - static ratelim_t warning_limit = RATELIM_INIT(3600); - char *m; - if ((m = rate_limit_log(&warning_limit, now))) { - log_warn(LD_DIR, - "Received http status code %d (%s) from server " - "'%s:%d' while fetching \"/tor/status/%s\". " - "I'll try again soon.%s", - status_code, escaped(reason), conn->base_.address, - conn->base_.port, conn->requested_resource, m); - tor_free(m); - } - tor_free(body); tor_free(headers); tor_free(reason); - connection_dir_download_v2_networkstatus_failed(conn, status_code); - return -1; - } - if (conn->requested_resource && - !strcmpstart(conn->requested_resource,"fp/")) { - source = NS_FROM_DIR_BY_FP; - which = smartlist_new(); - dir_split_resource_into_fingerprints(conn->requested_resource+3, - which, NULL, 0); - } else if (conn->requested_resource && - !strcmpstart(conn->requested_resource, "all")) { - source = NS_FROM_DIR_ALL; - which = smartlist_new(); - SMARTLIST_FOREACH(router_get_trusted_dir_servers(), - dir_server_t *, ds, - { - char *hex = tor_malloc(HEX_DIGEST_LEN+1); - base16_encode(hex, HEX_DIGEST_LEN+1, ds->digest, DIGEST_LEN); - smartlist_add(which, hex); - }); - } else { - /* XXXX Can we even end up here? -- weasel*/ - source = NS_FROM_DIR_BY_FP; - log_warn(LD_BUG, "We received a networkstatus but we didn't ask " - "for it by fp, nor did we ask for all."); - } - cp = body; - while (*cp) { - char *next = strstr(cp, "\nnetwork-status-version"); - if (next) - next[1] = '\0'; - /* learn from it, and then remove it from 'which' */ - if (router_set_networkstatus_v2(cp, now, source, which)<0) - break; - if (next) { - next[1] = 'n'; - cp = next+1; - } else - break; - } - /* launches router downloads as needed */ - routers_update_all_from_networkstatus(now, 2); - directory_info_has_arrived(now, 0); - if (which) { - if (smartlist_len(which)) { - dir_networkstatus_download_failed(which, status_code); - } - SMARTLIST_FOREACH(which, char *, s, tor_free(s)); - smartlist_free(which); - } - } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { int r; const char *flavname = conn->requested_resource; @@ -2220,72 +2097,35 @@ connection_dir_client_reached_eof(dir_connection_t *conn) * dirservers down just because they don't like us. */ } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC) { - tor_assert(conn->rend_data); - log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d " - "(%s))", - (int)body_len, status_code, escaped(reason)); - switch (status_code) { - case 200: - if (rend_cache_store(body, body_len, 0, - conn->rend_data->onion_address) < -1) { - log_warn(LD_REND,"Failed to parse rendezvous descriptor."); - /* Any pending rendezvous attempts will notice when - * connection_about_to_close_connection() - * cleans this dir conn up. */ - /* We could retry. But since v0 descriptors are going out of - * style, it isn't worth the hassle. We'll do better in v2. */ - } else { - /* Success, or at least there's a v2 descriptor already - * present. Notify pending connections about this. */ - conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; - rend_client_desc_trynow(conn->rend_data->onion_address); - } - break; - case 404: - /* Not there. Pending connections will be notified when - * connection_about_to_close_connection() cleans this conn up. */ - break; - case 400: - log_warn(LD_REND, - "http status 400 (%s). Dirserver didn't like our " - "rendezvous query?", escaped(reason)); - break; - default: - log_warn(LD_REND,"http status %d (%s) response unexpected while " - "fetching hidden service descriptor (server '%s:%d').", - status_code, escaped(reason), conn->base_.address, - conn->base_.port); - break; - } - } - if (conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) { + #define SEND_HS_DESC_FAILED_EVENT(reason) ( \ + control_event_hs_descriptor_failed(conn->rend_data, \ + conn->identity_digest, \ + reason) ) tor_assert(conn->rend_data); log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d " "(%s))", (int)body_len, status_code, escaped(reason)); switch (status_code) { case 200: - switch (rend_cache_store_v2_desc_as_client(body, conn->rend_data)) { - case -2: + switch (rend_cache_store_v2_desc_as_client(body, + conn->requested_resource, conn->rend_data)) { + case RCS_BADDESC: + case RCS_NOTDIR: /* Impossible */ log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. " "Retrying at another directory."); /* We'll retry when connection_about_to_close_connection() * cleans this dir conn up. */ + SEND_HS_DESC_FAILED_EVENT("BAD_DESC"); break; - case -1: - /* We already have a v0 descriptor here. Ignoring this one - * and _not_ performing another request. */ - log_info(LD_REND, "Successfully fetched v2 rendezvous " - "descriptor, but we already have a v0 descriptor."); - conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; - break; + case RCS_OKAY: default: /* success. notify pending connections about this. */ log_info(LD_REND, "Successfully fetched v2 rendezvous " "descriptor."); - conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; + control_event_hs_descriptor_received(conn->rend_data, + conn->identity_digest); + conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2; rend_client_desc_trynow(conn->rend_data->onion_address); break; } @@ -2295,12 +2135,14 @@ connection_dir_client_reached_eof(dir_connection_t *conn) * connection_about_to_close_connection() cleans this conn up. */ log_info(LD_REND,"Fetching v2 rendezvous descriptor failed: " "Retrying at another directory."); + SEND_HS_DESC_FAILED_EVENT("NOT_FOUND"); break; case 400: log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: " "http status 400 (%s). Dirserver didn't like our " "v2 rendezvous query? Retrying at another directory.", escaped(reason)); + SEND_HS_DESC_FAILED_EVENT("QUERY_REJECTED"); break; default: log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: " @@ -2309,12 +2151,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "Retrying at another directory.", status_code, escaped(reason), conn->base_.address, conn->base_.port); + SEND_HS_DESC_FAILED_EVENT("UNEXPECTED"); break; } } - if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC || - conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) { + if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) { log_info(LD_REND,"Uploaded rendezvous descriptor (status %d " "(%s))", status_code, escaped(reason)); @@ -2368,12 +2210,15 @@ connection_dir_reached_eof(dir_connection_t *conn) */ #define MAX_DIRECTORY_OBJECT_SIZE (10*(1<<20)) +#define MAX_VOTE_DL_SIZE (MAX_DIRECTORY_OBJECT_SIZE * 5) + /** Read handler for directory connections. (That's connections <em>to</em> * directory servers and connections <em>at</em> directory servers.) */ int connection_dir_process_inbuf(dir_connection_t *conn) { + size_t max_size; tor_assert(conn); tor_assert(conn->base_.type == CONN_TYPE_DIR); @@ -2392,9 +2237,15 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } - 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?"); + max_size = + (TO_CONN(conn)->purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) ? + MAX_VOTE_DL_SIZE : MAX_DIRECTORY_OBJECT_SIZE; + + if (connection_get_inbuf_len(TO_CONN(conn)) > max_size) { + log_warn(LD_HTTP, + "Too much data received from directory connection (%s): " + "denial of service attempt, or you need to upgrade?", + conn->base_.address); connection_mark_for_close(TO_CONN(conn)); return -1; } @@ -2418,7 +2269,7 @@ connection_dir_about_to_close(dir_connection_t *dir_conn) } /* If we were trying to fetch a v2 rend desc and did not succeed, * retry as needed. (If a fetch is successful, the connection state - * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC to mark that + * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that * refetching is unnecessary.) */ if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && dir_conn->rend_data && @@ -2439,6 +2290,7 @@ write_http_status_line(dir_connection_t *conn, int status, log_warn(LD_BUG,"status line too long."); return; } + log_debug(LD_DIRSERV,"Wrote status 'HTTP/1.0 %d %s'", status, reason_phrase); connection_write_to_buf(buf, strlen(buf), TO_CONN(conn)); } @@ -2492,7 +2344,7 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, } if (cache_lifetime > 0) { char expbuf[RFC1123_TIME_LEN+1]; - format_rfc1123_time(expbuf, now + cache_lifetime); + format_rfc1123_time(expbuf, (time_t)(now + cache_lifetime)); /* We could say 'Cache-control: max-age=%d' here if we start doing * http/1.1 */ tor_snprintf(cp, sizeof(tmp)-(cp-tmp), @@ -2549,7 +2401,6 @@ note_client_request(int purpose, int compressed, size_t bytes) char *key; const char *kind = NULL; switch (purpose) { - case DIR_PURPOSE_FETCH_V2_NETWORKSTATUS: kind = "dl/status"; break; case DIR_PURPOSE_FETCH_CONSENSUS: kind = "dl/consensus"; break; case DIR_PURPOSE_FETCH_CERTIFICATE: kind = "dl/cert"; break; case DIR_PURPOSE_FETCH_STATUS_VOTE: kind = "dl/vote"; break; @@ -2560,9 +2411,7 @@ note_client_request(int purpose, int compressed, size_t bytes) case DIR_PURPOSE_UPLOAD_DIR: kind = "dl/ul-dir"; break; case DIR_PURPOSE_UPLOAD_VOTE: kind = "dl/ul-vote"; break; case DIR_PURPOSE_UPLOAD_SIGNATURES: kind = "dl/ul-sig"; break; - case DIR_PURPOSE_FETCH_RENDDESC: kind = "dl/rend"; break; case DIR_PURPOSE_FETCH_RENDDESC_V2: kind = "dl/rend2"; break; - case DIR_PURPOSE_UPLOAD_RENDDESC: kind = "dl/ul-rend"; break; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: kind = "dl/ul-rend2"; break; } if (kind) { @@ -2685,7 +2534,7 @@ client_likes_consensus(networkstatus_t *v, const char *want_url) if (base16_decode(want_digest, DIGEST_LEN, d, want_len*2) < 0) { log_fn(LOG_PROTOCOL_WARN, LD_DIR, - "Failed to decode requested authority digest %s.", d); + "Failed to decode requested authority digest %s.", escaped(d)); continue; }; @@ -2707,6 +2556,24 @@ client_likes_consensus(networkstatus_t *v, const char *want_url) return (have >= need_at_least); } +/** Return the compression level we should use for sending a compressed + * response of size <b>n_bytes</b>. */ +static zlib_compression_level_t +choose_compression_level(ssize_t n_bytes) +{ + if (! have_been_under_memory_pressure()) { + return HIGH_COMPRESSION; /* we have plenty of RAM. */ + } else if (n_bytes < 0) { + return HIGH_COMPRESSION; /* unknown; might be big. */ + } else if (n_bytes < 1024) { + return LOW_COMPRESSION; + } else if (n_bytes < 2048) { + return MEDIUM_COMPRESSION; + } else { + return HIGH_COMPRESSION; + } +} + /** Helper function: called when a dirserver gets a complete HTTP GET * request. Look for a request for a directory or for a rendezvous * service descriptor. On finding one, write a response into @@ -2738,14 +2605,17 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, if ((header = http_get_header(headers, "If-Modified-Since: "))) { struct tm tm; if (parse_http_time(header, &tm) == 0) { - if (tor_timegm(&tm, &if_modified_since)<0) + if (tor_timegm(&tm, &if_modified_since)<0) { if_modified_since = 0; + } else { + log_debug(LD_DIRSERV, "If-Modified-Since is '%s'.", escaped(header)); + } } /* The correct behavior on a malformed If-Modified-Since header is to * act as if no If-Modified-Since header had been given. */ tor_free(header); } - log_debug(LD_DIRSERV,"rewritten url as '%s'.", url); + log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); url_mem = url; url_len = strlen(url); @@ -2774,109 +2644,13 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, /* if no disclaimer file, fall through and continue */ } - if (!strcmp(url,"/tor/") || !strcmp(url,"/tor/dir")) { /* v1 dir fetch */ - cached_dir_t *d = dirserv_get_directory(); - - if (!d) { - log_info(LD_DIRSERV,"Client asked for the mirrored directory, but we " - "don't have a good one yet. Sending 503 Dir not available."); - write_http_status_line(conn, 503, "Directory unavailable"); - goto done; - } - if (d->published < if_modified_since) { - write_http_status_line(conn, 304, "Not modified"); - goto done; - } - - dlen = compressed ? d->dir_z_len : d->dir_len; - - if (global_write_bucket_low(TO_CONN(conn), dlen, 1)) { - log_debug(LD_DIRSERV, - "Client asked for the mirrored directory, but we've been " - "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); - goto done; - } - - note_request(url, dlen); - - log_debug(LD_DIRSERV,"Dumping %sdirectory to client.", - compressed?"compressed ":""); - write_http_response_header(conn, dlen, compressed, - FULL_DIR_CACHE_LIFETIME); - conn->cached_dir = d; - conn->cached_dir_offset = 0; - if (!compressed) - conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD); - ++d->refcnt; - - /* Prime the connection with some data. */ - conn->dir_spool_src = DIR_SPOOL_CACHED_DIR; - connection_dirserv_flushed_some(conn); - goto done; - } - - if (!strcmp(url,"/tor/running-routers")) { /* running-routers fetch */ - cached_dir_t *d = dirserv_get_runningrouters(); - if (!d) { - write_http_status_line(conn, 503, "Directory unavailable"); - goto done; - } - if (d->published < if_modified_since) { - write_http_status_line(conn, 304, "Not modified"); - goto done; - } - dlen = compressed ? d->dir_z_len : d->dir_len; - - if (global_write_bucket_low(TO_CONN(conn), dlen, 1)) { - log_info(LD_DIRSERV, - "Client asked for running-routers, but we've been " - "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); - goto done; - } - note_request(url, dlen); - write_http_response_header(conn, dlen, compressed, - RUNNINGROUTERS_CACHE_LIFETIME); - connection_write_to_buf(compressed ? d->dir_z : d->dir, dlen, - TO_CONN(conn)); - goto done; - } - - if (!strcmpstart(url,"/tor/status/") - || !strcmpstart(url, "/tor/status-vote/current/consensus")) { - /* v2 or v3 network status fetch. */ + if (!strcmpstart(url, "/tor/status-vote/current/consensus")) { + /* v3 network status fetch. */ smartlist_t *dir_fps = smartlist_new(); - int is_v3 = !strcmpstart(url, "/tor/status-vote"); const char *request_type = NULL; - const char *key = url + strlen("/tor/status/"); long lifetime = NETWORKSTATUS_CACHE_LIFETIME; - if (options->DisableV2DirectoryInfo_ && !is_v3) { - static ratelim_t reject_v2_ratelim = RATELIM_INIT(1800); - char *m; - write_http_status_line(conn, 404, "Not found"); - smartlist_free(dir_fps); - geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); - if ((m = rate_limit_log(&reject_v2_ratelim, approx_time()))) { - log_notice(LD_DIR, "Rejected a v2 networkstatus request.%s", m); - tor_free(m); - } - goto done; - } - - if (!is_v3) { - dirserv_get_networkstatus_v2_fingerprints(dir_fps, key); - if (!strcmpstart(key, "fp/")) - request_type = compressed?"/tor/status/fp.z":"/tor/status/fp"; - else if (!strcmpstart(key, "authority")) - request_type = compressed?"/tor/status/authority.z": - "/tor/status/authority"; - else if (!strcmpstart(key, "all")) - request_type = compressed?"/tor/status/all.z":"/tor/status/all"; - else - request_type = "/tor/status/?"; - } else { + if (1) { networkstatus_t *v; time_t now = time(NULL); const char *want_fps = NULL; @@ -2929,8 +2703,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, if (!smartlist_len(dir_fps)) { /* we failed to create/cache cp */ write_http_status_line(conn, 503, "Network status object unavailable"); smartlist_free(dir_fps); - if (is_v3) - geoip_note_ns_response(GEOIP_REJECT_UNAVAILABLE); + geoip_note_ns_response(GEOIP_REJECT_UNAVAILABLE); goto done; } @@ -2938,15 +2711,13 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, write_http_status_line(conn, 404, "Not found"); SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp)); smartlist_free(dir_fps); - if (is_v3) - geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; } else if (!smartlist_len(dir_fps)) { write_http_status_line(conn, 304, "Not modified"); SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp)); smartlist_free(dir_fps); - if (is_v3) - geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); + geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); goto done; } @@ -2958,17 +2729,19 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, write_http_status_line(conn, 503, "Directory busy, try again later"); SMARTLIST_FOREACH(dir_fps, char *, fp, tor_free(fp)); smartlist_free(dir_fps); - if (is_v3) - geoip_note_ns_response(GEOIP_REJECT_BUSY); + + geoip_note_ns_response(GEOIP_REJECT_BUSY); goto done; } - if (is_v3) { + if (1) { struct in_addr in; tor_addr_t addr; if (tor_inet_aton((TO_CONN(conn))->address, &in)) { tor_addr_from_ipv4h(&addr, ntohl(in.s_addr)); - geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, time(NULL)); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, + &addr, NULL, + time(NULL)); geoip_note_ns_response(GEOIP_SUCCESS); /* Note that a request for a network status has started, so that we * can measure the download time later on. */ @@ -2986,7 +2759,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, smartlist_len(dir_fps) == 1 ? lifetime : 0); conn->fingerprint_stack = dir_fps; if (! compressed) - conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD); + conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION); /* Prime the connection with some data. */ conn->dir_spool_src = DIR_SPOOL_NETWORKSTATUS; @@ -3074,7 +2847,8 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, if (smartlist_len(items)) { if (compressed) { - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD); + conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, + choose_compression_level(estimated_len)); SMARTLIST_FOREACH(items, const char *, c, connection_write_to_buf_zlib(c, strlen(c), conn, 0)); connection_write_to_buf_zlib("", 0, conn, 1); @@ -3123,7 +2897,8 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, conn->fingerprint_stack = fps; if (compressed) - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD); + conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, + choose_compression_level(dlen)); connection_dirserv_flushed_some(conn); goto done; @@ -3191,7 +2966,8 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, } write_http_response_header(conn, -1, compressed, cache_lifetime); if (compressed) - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD); + conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, + choose_compression_level(dlen)); /* Prime the connection with some data. */ connection_dirserv_flushed_some(conn); } @@ -3266,7 +3042,8 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, write_http_response_header(conn, compressed?-1:len, compressed, 60*60); if (compressed) { - conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD); + conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD, + choose_compression_level(len)); SMARTLIST_FOREACH(certs, authority_cert_t *, c, connection_write_to_buf_zlib(c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, @@ -3291,7 +3068,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, const char *query = url + strlen("/tor/rendezvous2/"); if (strlen(query) == REND_DESC_ID_V2_LEN_BASE32) { log_info(LD_REND, "Got a v2 rendezvous descriptor request for ID '%s'", - safe_str(query)); + safe_str(escaped(query))); switch (rend_cache_lookup_v2_desc_as_dir(query, &descp)) { case 1: /* valid */ write_http_response_header(conn, strlen(descp), 0, 0); @@ -3310,32 +3087,6 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, goto done; } - if (options->HSAuthoritativeDir && !strcmpstart(url,"/tor/rendezvous/")) { - /* rendezvous descriptor fetch */ - const char *descp; - size_t desc_len; - const char *query = url+strlen("/tor/rendezvous/"); - - log_info(LD_REND, "Handling rendezvous descriptor get"); - switch (rend_cache_lookup_desc(query, 0, &descp, &desc_len)) { - case 1: /* valid */ - write_http_response_header_impl(conn, desc_len, - "application/octet-stream", - NULL, NULL, 0); - note_request("/tor/rendezvous?/", desc_len); - /* need to send descp separately, because it may include NULs */ - connection_write_to_buf(descp, desc_len, TO_CONN(conn)); - break; - case 0: /* well-formed but not present */ - write_http_status_line(conn, 404, "Not found"); - break; - case -1: /* not well-formed */ - write_http_status_line(conn, 400, "Bad request"); - break; - } - goto done; - } - if (options->BridgeAuthoritativeDir && options->BridgePassword_AuthDigest_ && connection_dir_is_encrypted(conn) && @@ -3384,22 +3135,6 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, goto done; } - if (!strcmp(url,"/tor/dbg-stability.txt")) { - const char *stability; - size_t len; - if (options->BridgeAuthoritativeDir || - ! authdir_mode_tests_reachability(options) || - ! (stability = rep_hist_get_router_stability_doc(time(NULL)))) { - write_http_status_line(conn, 404, "Not found."); - goto done; - } - - len = strlen(stability); - write_http_response_header(conn, len, 0, 0); - connection_write_to_buf(stability, len, TO_CONN(conn)); - goto done; - } - #if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO) #define ADD_MALLINFO_LINE(x) do { \ smartlist_add_asprintf(lines, "%s %d\n", #x, mi.x); \ @@ -3467,26 +3202,27 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, write_http_status_line(conn, 400, "Bad request"); return 0; } - log_debug(LD_DIRSERV,"rewritten url as '%s'.", url); + log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); /* Handle v2 rendezvous service publish request. */ if (options->HidServDirectoryV2 && connection_dir_is_encrypted(conn) && !strcmpstart(url,"/tor/rendezvous2/publish")) { switch (rend_cache_store_v2_desc_as_dir(body)) { - case -2: + case RCS_NOTDIR: log_info(LD_REND, "Rejected v2 rend descriptor (length %d) from %s " "since we're not currently a hidden service directory.", (int)body_len, conn->base_.address); write_http_status_line(conn, 503, "Currently not acting as v2 " "hidden service directory"); break; - case -1: + case RCS_BADDESC: log_warn(LD_REND, "Rejected v2 rend descriptor (length %d) from %s.", (int)body_len, conn->base_.address); write_http_status_line(conn, 400, "Invalid v2 service descriptor rejected"); break; + case RCS_OKAY: default: write_http_status_line(conn, 200, "Service descriptor (v2) stored"); log_info(LD_REND, "Handled v2 rendezvous descriptor post: accepted"); @@ -3510,8 +3246,6 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, was_router_added_t r = dirserv_add_multiple_descriptors(body, purpose, conn->base_.address, &msg); tor_assert(msg); - if (WRA_WAS_ADDED(r)) - dirserv_get_directory(); /* rebuild and write to disk */ if (r == ROUTER_ADDED_NOTIFY_GENERATOR) { /* Accepted with a message. */ @@ -3535,22 +3269,6 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, goto done; } - if (options->HSAuthoritativeDir && - !strcmpstart(url,"/tor/rendezvous/publish")) { - /* rendezvous descriptor post */ - log_info(LD_REND, "Handling rendezvous descriptor post."); - if (rend_cache_store(body, body_len, 1, NULL) < 0) { - log_fn(LOG_PROTOCOL_WARN, LD_DIRSERV, - "Rejected rend descriptor (length %d) from %s.", - (int)body_len, conn->base_.address); - write_http_status_line(conn, 400, - "Invalid v0 service descriptor rejected"); - } else { - write_http_status_line(conn, 200, "Service descriptor (v0) stored"); - } - goto done; - } - if (authdir_mode_v3(options) && !strcmp(url,"/tor/post/vote")) { /* v3 networkstatus vote */ const char *msg = "OK"; @@ -3617,7 +3335,9 @@ directory_handle_command(dir_connection_t *conn) } http_set_address_origin(headers, TO_CONN(conn)); - //log_debug(LD_DIRSERV,"headers %s, body %s.", headers, body); + // we should escape headers here as well, + // but we can't call escaped() twice, as it uses the same buffer + //log_debug(LD_DIRSERV,"headers %s, body %s.", headers, escaped(body)); if (!strncasecmp(headers,"GET",3)) r = directory_handle_command_get(conn, headers, body, body_len); @@ -3702,80 +3422,27 @@ connection_dir_finished_connecting(dir_connection_t *conn) return 0; } -/** Called when one or more networkstatus fetches have failed (with uppercase - * fingerprints listed in <b>failed</b>). Mark those fingerprints as having - * failed once, unless they failed with status code 503. */ -static void -dir_networkstatus_download_failed(smartlist_t *failed, int status_code) -{ - if (status_code == 503) - return; - SMARTLIST_FOREACH_BEGIN(failed, const char *, fp) { - char digest[DIGEST_LEN]; - dir_server_t *dir; - if (base16_decode(digest, DIGEST_LEN, fp, strlen(fp))<0) { - log_warn(LD_BUG, "Called with bad fingerprint in list: %s", - escaped(fp)); - continue; - } - dir = router_get_fallback_dirserver_by_digest(digest); - - if (dir) - download_status_failed(&dir->v2_ns_dl_status, status_code); - } SMARTLIST_FOREACH_END(fp); -} - -/** Schedule for when servers should download things in general. */ -static const int server_dl_schedule[] = { - 0, 0, 0, 60, 60, 60*2, 60*5, 60*15, INT_MAX -}; -/** Schedule for when clients should download things in general. */ -static const int client_dl_schedule[] = { - 0, 0, 60, 60*5, 60*10, INT_MAX -}; -/** Schedule for when servers should download consensuses. */ -static const int server_consensus_dl_schedule[] = { - 0, 0, 60, 60*5, 60*10, 60*30, 60*30, 60*30, 60*30, 60*30, 60*60, 60*60*2 -}; -/** Schedule for when clients should download consensuses. */ -static const int client_consensus_dl_schedule[] = { - 0, 0, 60, 60*5, 60*10, 60*30, 60*60, 60*60, 60*60, 60*60*3, 60*60*6, 60*60*12 -}; -/** Schedule for when clients should download bridge descriptors. */ -static const int bridge_dl_schedule[] = { - 60*60, 15*60, 15*60, 60*60 -}; - -/** Decide which download schedule we want to use, and then return a - * pointer to it along with a pointer to its length. Helper function for - * download_status_increment_failure() and download_status_reset(). */ -static void -find_dl_schedule_and_len(download_status_t *dls, int server, - const int **schedule, size_t *schedule_len) +/** Decide which download schedule we want to use based on descriptor type + * in <b>dls</b> and whether we are acting as directory <b>server</b>, and + * then return a list of int pointers defining download delays in seconds. + * Helper function for download_status_increment_failure() and + * download_status_reset(). */ +static const smartlist_t * +find_dl_schedule_and_len(download_status_t *dls, int server) { switch (dls->schedule) { case DL_SCHED_GENERIC: - if (server) { - *schedule = server_dl_schedule; - *schedule_len = sizeof(server_dl_schedule)/sizeof(int); - } else { - *schedule = client_dl_schedule; - *schedule_len = sizeof(client_dl_schedule)/sizeof(int); - } - break; + if (server) + return get_options()->TestingServerDownloadSchedule; + else + return get_options()->TestingClientDownloadSchedule; case DL_SCHED_CONSENSUS: - if (server) { - *schedule = server_consensus_dl_schedule; - *schedule_len = sizeof(server_consensus_dl_schedule)/sizeof(int); - } else { - *schedule = client_consensus_dl_schedule; - *schedule_len = sizeof(client_consensus_dl_schedule)/sizeof(int); - } - break; + if (server) + return get_options()->TestingServerConsensusDownloadSchedule; + else + return get_options()->TestingClientConsensusDownloadSchedule; case DL_SCHED_BRIDGE: - *schedule = bridge_dl_schedule; - *schedule_len = sizeof(bridge_dl_schedule)/sizeof(int); - break; + return get_options()->TestingBridgeDownloadSchedule; default: tor_assert(0); } @@ -3789,8 +3456,7 @@ time_t download_status_increment_failure(download_status_t *dls, int status_code, const char *item, int server, time_t now) { - const int *schedule; - size_t schedule_len; + const smartlist_t *schedule; int increment; tor_assert(dls); if (status_code != 503 || server) { @@ -3798,14 +3464,14 @@ download_status_increment_failure(download_status_t *dls, int status_code, ++dls->n_download_failures; } - find_dl_schedule_and_len(dls, server, &schedule, &schedule_len); + schedule = find_dl_schedule_and_len(dls, server); - if (dls->n_download_failures < schedule_len) - increment = schedule[dls->n_download_failures]; + if (dls->n_download_failures < smartlist_len(schedule)) + increment = *(int *)smartlist_get(schedule, dls->n_download_failures); else if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD) increment = INT_MAX; else - increment = schedule[schedule_len-1]; + increment = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); if (increment < INT_MAX) dls->next_attempt_at = now+increment; @@ -3838,14 +3504,14 @@ download_status_increment_failure(download_status_t *dls, int status_code, void download_status_reset(download_status_t *dls) { - const int *schedule; - size_t schedule_len; + if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD) + return; /* Don't reset this. */ - find_dl_schedule_and_len(dls, get_options()->DirPort_set, - &schedule, &schedule_len); + const smartlist_t *schedule = find_dl_schedule_and_len( + dls, get_options()->DirPort_set); dls->n_download_failures = 0; - dls->next_attempt_at = time(NULL) + schedule[0]; + dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); } /** Return the number of failures on <b>dls</b> since the last success (if @@ -3890,7 +3556,8 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code, } else { dls = router_get_dl_status_by_descriptor_digest(digest); } - if (!dls || dls->n_download_failures >= MAX_ROUTERDESC_DOWNLOAD_FAILURES) + if (!dls || dls->n_download_failures >= + get_options()->TestingDescriptorMaxDownloadTries) continue; download_status_increment_failure(dls, status_code, cp, server, now); } SMARTLIST_FOREACH_END(cp); @@ -3921,7 +3588,8 @@ dir_microdesc_download_failed(smartlist_t *failed, if (!rs) continue; dls = &rs->dl_status; - if (dls->n_download_failures >= MAX_MICRODESC_DOWNLOAD_FAILURES) + if (dls->n_download_failures >= + get_options()->TestingMicrodescMaxDownloadTries) continue; { char buf[BASE64_DIGEST256_LEN+1]; diff --git a/src/or/directory.h b/src/or/directory.h index 41f18a1725..4899eb5c8c 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,9 +16,10 @@ int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, dirinfo_type_t type, const char *payload, size_t payload_len, size_t extrainfo_len); -void directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, - const char *resource, - int pds_flags); +MOCK_DECL(void, directory_get_from_dirserver, (uint8_t dir_purpose, + uint8_t router_purpose, + const char *resource, + int pds_flags)); void directory_get_from_all_authorities(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); @@ -30,7 +31,7 @@ typedef enum { DIRIND_ONEHOP=0, /** Connect over a multi-hop anonymizing Tor circuit */ DIRIND_ANONYMOUS=1, - /** Conncet to the DirPort directly */ + /** Connect to the DirPort directly */ DIRIND_DIRECT_CONN, /** Connect over a multi-hop anonymizing Tor circuit to our dirport */ DIRIND_ANON_DIRPORT, @@ -63,7 +64,7 @@ int connection_dir_process_inbuf(dir_connection_t *conn); int connection_dir_finished_flushing(dir_connection_t *conn); int connection_dir_finished_connecting(dir_connection_t *conn); void connection_dir_about_to_close(dir_connection_t *dir_conn); -void directory_initiate_command(const char *address, const tor_addr_t *addr, +void directory_initiate_command(const tor_addr_t *addr, uint16_t or_port, uint16_t dir_port, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, @@ -118,5 +119,15 @@ download_status_mark_impossible(download_status_t *dl) int download_status_get_n_failures(const download_status_t *dls); +#ifdef TOR_UNIT_TESTS +/* Used only by directory.c and test_dir.c */ + +STATIC int parse_http_url(const char *headers, char **url); +STATIC int purpose_needs_anonymity(uint8_t dir_purpose, + uint8_t router_purpose); +STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, + const char *resource); +#endif + #endif diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 3e46153a55..65bfafba6c 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRSERV_PRIVATE @@ -26,6 +26,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "routerset.h" /** * \file dirserv.c @@ -41,31 +42,10 @@ * directory authorities. */ #define MAX_UNTRUSTED_NETWORKSTATUSES 16 -/** If a v1 directory is older than this, discard it. */ -#define MAX_V1_DIRECTORY_AGE (30*24*60*60) -/** If a v1 running-routers is older than this, discard it. */ -#define MAX_V1_RR_AGE (7*24*60*60) - extern time_t time_of_process_start; /* from main.c */ extern long stats_n_seconds_working; /* from main.c */ -/** Do we need to regenerate the v1 directory when someone asks for it? */ -static time_t the_directory_is_dirty = 1; -/** Do we need to regenerate the v1 runningrouters document when somebody - * asks for it? */ -static time_t runningrouters_is_dirty = 1; -/** Do we need to regenerate our v2 networkstatus document when somebody asks - * for it? */ -static time_t the_v2_networkstatus_is_dirty = 1; - -/** Most recently generated encoded signed v1 directory. (v1 auth dirservers - * only.) */ -static cached_dir_t *the_directory = NULL; - -/** For authoritative directories: the current (v1) network status. */ -static cached_dir_t the_runningrouters; - /** Total number of routers with measured bandwidth; this is set by * dirserv_count_measured_bws() before the loop in * dirserv_generate_networkstatus_vote_obj() and checked by @@ -74,17 +54,13 @@ static cached_dir_t the_runningrouters; static int routers_with_measured_bw = 0; static void directory_remove_invalid(void); -static cached_dir_t *dirserv_regenerate_directory(void); 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_get_status_impl(const char *fp, const char *nickname, - const char *address, uint32_t addr, uint16_t or_port, - const char *platform, const char *contact, - const char **msg, int should_log); + const char *platform, const char **msg, + int should_log); static void clear_cached_dir(cached_dir_t *d); static const signed_descriptor_t *get_signed_descriptor_by_fp( const char *fp, @@ -97,19 +73,19 @@ static uint32_t dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri); /************** Fingerprint handling code ************/ -#define FP_NAMED 1 /**< Listed in fingerprint file. */ +/* 1 Historically used to indicate Named */ #define FP_INVALID 2 /**< Believed invalid. */ #define FP_REJECT 4 /**< We will not publish this router. */ -#define FP_BADDIR 8 /**< We'll tell clients to avoid using this as a dir. */ +/* 8 Historically used to avoid using this as a dir. */ #define FP_BADEXIT 16 /**< We'll tell clients not to use this as an exit. */ -#define FP_UNNAMED 32 /**< Another router has this name in fingerprint file. */ +/* 32 Historically used to indicade Unnamed */ + +/** Target of status_by_digest map. */ +typedef uint32_t router_status_t; -/** Encapsulate a nickname and an FP_* status; target of status_by_digest - * map. */ -typedef struct router_status_t { - char nickname[MAX_NICKNAME_LEN+1]; - uint32_t status; -} router_status_t; +static void add_fingerprint_to_dir(const char *fp, + struct authdir_config_t *list, + router_status_t add_status); /** List of nickname-\>identity fingerprint mappings for all the routers * that we name. Used to prevent router impersonation. */ @@ -131,18 +107,17 @@ authdir_config_new(void) return list; } -/** Add the fingerprint <b>fp</b> for <b>nickname</b> to - * the smartlist of fingerprint_entry_t's <b>list</b>. Return 0 if it's - * new, or 1 if we replaced the old value. +/** Add the fingerprint <b>fp</b> to the smartlist of fingerprint_entry_t's + * <b>list</b>, or-ing the currently set status flags with + * <b>add_status</b>. */ -/* static */ int -add_fingerprint_to_dir(const char *nickname, const char *fp, - authdir_config_t *list) +/* static */ void +add_fingerprint_to_dir(const char *fp, authdir_config_t *list, + router_status_t add_status) { char *fingerprint; char d[DIGEST_LEN]; router_status_t *status; - tor_assert(nickname); tor_assert(fp); tor_assert(list); @@ -152,14 +127,7 @@ add_fingerprint_to_dir(const char *nickname, const char *fp, log_warn(LD_DIRSERV, "Couldn't decode fingerprint \"%s\"", escaped(fp)); tor_free(fingerprint); - return 0; - } - - if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) { - log_warn(LD_DIRSERV, "Tried to add a mapping for reserved nickname %s", - UNNAMED_ROUTER_NICKNAME); - tor_free(fingerprint); - return 0; + return; } status = digestmap_get(list->status_by_digest, d); @@ -168,35 +136,15 @@ add_fingerprint_to_dir(const char *nickname, const char *fp, digestmap_set(list->status_by_digest, d, status); } - if (nickname[0] != '!') { - char *old_fp = strmap_get_lc(list->fp_by_name, nickname); - if (old_fp && !strcasecmp(fingerprint, old_fp)) { - tor_free(fingerprint); - } else { - tor_free(old_fp); - strmap_set_lc(list->fp_by_name, nickname, fingerprint); - } - status->status |= FP_NAMED; - strlcpy(status->nickname, nickname, sizeof(status->nickname)); - } else { - tor_free(fingerprint); - if (!strcasecmp(nickname, "!reject")) { - status->status |= FP_REJECT; - } else if (!strcasecmp(nickname, "!invalid")) { - status->status |= FP_INVALID; - } else if (!strcasecmp(nickname, "!baddir")) { - status->status |= FP_BADDIR; - } else if (!strcasecmp(nickname, "!badexit")) { - status->status |= FP_BADEXIT; - } - } - return 0; + tor_free(fingerprint); + *status |= add_status; + return; } -/** Add the nickname and fingerprint for this OR to the - * global list of recognized identity key fingerprints. */ +/** Add the fingerprint for this OR to the global list of recognized + * identity key fingerprints. */ int -dirserv_add_own_fingerprint(const char *nickname, crypto_pk_t *pk) +dirserv_add_own_fingerprint(crypto_pk_t *pk) { char fp[FINGERPRINT_LEN+1]; if (crypto_pk_get_fingerprint(pk, fp, 0)<0) { @@ -205,7 +153,7 @@ dirserv_add_own_fingerprint(const char *nickname, crypto_pk_t *pk) } if (!fingerprint_list) fingerprint_list = authdir_config_new(); - add_fingerprint_to_dir(nickname, fp, fingerprint_list); + add_fingerprint_to_dir(fp, fingerprint_list, 0); return 0; } @@ -223,7 +171,6 @@ dirserv_load_fingerprint_file(void) authdir_config_t *fingerprint_list_new; int result; config_line_t *front=NULL, *list; - const or_options_t *options = get_options(); fname = get_datadir_fname("approved-routers"); log_info(LD_GENERAL, @@ -231,15 +178,9 @@ dirserv_load_fingerprint_file(void) cf = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL); if (!cf) { - if (options->NamingAuthoritativeDir) { - log_warn(LD_FS, "Cannot open fingerprint file '%s'. Failing.", fname); - tor_free(fname); - return -1; - } else { - log_info(LD_FS, "Cannot open fingerprint file '%s'. That's ok.", fname); - tor_free(fname); - return 0; - } + log_warn(LD_FS, "Cannot open fingerprint file '%s'. That's ok.", fname); + tor_free(fname); + return 0; } tor_free(fname); @@ -254,22 +195,8 @@ dirserv_load_fingerprint_file(void) for (list=front; list; list=list->next) { char digest_tmp[DIGEST_LEN]; + router_status_t add_status = 0; nickname = list->key; fingerprint = list->value; - if (strlen(nickname) > MAX_NICKNAME_LEN) { - log_notice(LD_CONFIG, - "Nickname '%s' too long in fingerprint file. Skipping.", - nickname); - continue; - } - if (!is_legal_nickname(nickname) && - strcasecmp(nickname, "!reject") && - strcasecmp(nickname, "!invalid") && - strcasecmp(nickname, "!badexit")) { - log_notice(LD_CONFIG, - "Invalid nickname '%s' in fingerprint file. Skipping.", - nickname); - continue; - } tor_strstrip(fingerprint, " "); /* remove spaces */ if (strlen(fingerprint) != HEX_DIGEST_LEN || base16_decode(digest_tmp, sizeof(digest_tmp), @@ -280,26 +207,14 @@ dirserv_load_fingerprint_file(void) nickname, fingerprint); continue; } - if (0==strcasecmp(nickname, DEFAULT_CLIENT_NICKNAME)) { - /* If you approved an OR called "client", then clients who use - * the default nickname could all be rejected. That's no good. */ - log_notice(LD_CONFIG, - "Authorizing nickname '%s' would break " - "many clients; skipping.", - DEFAULT_CLIENT_NICKNAME); - continue; - } - if (0==strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) { - /* If you approved an OR called "unnamed", then clients will be - * confused. */ - log_notice(LD_CONFIG, - "Authorizing nickname '%s' is not allowed; skipping.", - UNNAMED_ROUTER_NICKNAME); - continue; + if (!strcasecmp(nickname, "!reject")) { + add_status = FP_REJECT; + } else if (!strcasecmp(nickname, "!badexit")) { + add_status = FP_BADEXIT; + } else if (!strcasecmp(nickname, "!invalid")) { + add_status = FP_INVALID; } - if (add_fingerprint_to_dir(nickname, fingerprint, fingerprint_list_new) - != 0) - log_notice(LD_CONFIG, "Duplicate nickname '%s'.", nickname); + add_fingerprint_to_dir(fingerprint, fingerprint_list_new, add_status); } config_free_lines(front); @@ -329,10 +244,8 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg) } return dirserv_get_status_impl(d, router->nickname, - router->address, router->addr, router->or_port, - router->platform, router->contact_info, - msg, 1); + router->platform, msg, 1); } /** Return true if there is no point in downloading the router described by @@ -343,52 +256,25 @@ dirserv_would_reject_router(const routerstatus_t *rs) uint32_t res; res = dirserv_get_status_impl(rs->identity_digest, rs->nickname, - "", /* address is only used in logs */ rs->addr, rs->or_port, - NULL, NULL, - NULL, 0); + NULL, NULL, 0); return (res & FP_REJECT) != 0; } -/** Helper: Based only on the ID/Nickname combination, - * return FP_UNNAMED (unnamed), FP_NAMED (named), or 0 (neither). - */ -static uint32_t -dirserv_get_name_status(const char *id_digest, const char *nickname) -{ - char fp[HEX_DIGEST_LEN+1]; - char *fp_by_name; - - base16_encode(fp, sizeof(fp), id_digest, DIGEST_LEN); - - if ((fp_by_name = - strmap_get_lc(fingerprint_list->fp_by_name, nickname))) { - if (!strcasecmp(fp, fp_by_name)) { - return FP_NAMED; - } else { - return FP_UNNAMED; /* Wrong fingerprint. */ - } - } - return 0; -} - /** 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. + * port and platform (logging only) as arguments. * * If should_log is false, do not log messages. (There's not much point in * logging that we're rejecting servers we'll not download.) */ static uint32_t dirserv_get_status_impl(const char *id_digest, const char *nickname, - const char *address, uint32_t addr, uint16_t or_port, - const char *platform, const char *contact, - const char **msg, int should_log) + const char *platform, const char **msg, int should_log) { - int reject_unlisted = get_options()->AuthDirRejectUnlisted; - uint32_t result; + uint32_t result = 0; router_status_t *status_by_digest; if (!fingerprint_list) @@ -399,47 +285,18 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, strmap_size(fingerprint_list->fp_by_name), digestmap_size(fingerprint_list->status_by_digest)); - /* Versions before Tor 0.2.2.35 have known security issues that - * make them unsuitable for the current network. */ - if (platform && !tor_version_as_new_as(platform,"0.2.2.35")) { + /* Versions before Tor 0.2.4.18-rc are too old to support, and are + * missing some important security fixes too. Disable them. */ + if (platform && !tor_version_as_new_as(platform,"0.2.4.18-rc")) { if (msg) *msg = "Tor version is insecure or unsupported. Please upgrade!"; return FP_REJECT; - } else if (platform && tor_version_as_new_as(platform,"0.2.3.0-alpha")) { - /* Versions from 0.2.3-alpha...0.2.3.9-alpha have known security - * issues that make them unusable for the current network */ - if (!tor_version_as_new_as(platform, "0.2.3.10-alpha")) { - if (msg) - *msg = "Tor version is insecure or unsupported. Please upgrade!"; - return FP_REJECT; - } - } - - result = dirserv_get_name_status(id_digest, nickname); - if (result & FP_NAMED) { - if (should_log) - log_debug(LD_DIRSERV,"Good fingerprint for '%s'",nickname); - } - if (result & FP_UNNAMED) { - if (should_log) { - char *esc_contact = esc_for_log(contact); - log_info(LD_DIRSERV, - "Mismatched fingerprint for '%s'. " - "ContactInfo '%s', platform '%s'.)", - nickname, - esc_contact, - platform ? escaped(platform) : ""); - tor_free(esc_contact); - } - if (msg) - *msg = "Rejected: There is already a named server with this nickname " - "and a different fingerprint."; } status_by_digest = digestmap_get(fingerprint_list->status_by_digest, id_digest); if (status_by_digest) - result |= (status_by_digest->status & ~FP_NAMED); + result |= *status_by_digest; if (result & FP_REJECT) { if (msg) @@ -450,61 +307,31 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, *msg = "Fingerprint is marked invalid"; } - if (authdir_policy_baddir_address(addr, or_port)) { - if (should_log) - log_info(LD_DIRSERV, - "Marking '%s' as bad directory because of address '%s'", - nickname, address); - result |= FP_BADDIR; - } - if (authdir_policy_badexit_address(addr, or_port)) { if (should_log) log_info(LD_DIRSERV, "Marking '%s' as bad exit because of address '%s'", - nickname, address); + nickname, fmt_addr32(addr)); result |= FP_BADEXIT; } - if (!(result & FP_NAMED)) { - if (!authdir_policy_permits_address(addr, or_port)) { - if (should_log) - log_info(LD_DIRSERV, "Rejecting '%s' because of address '%s'", - nickname, address); - if (msg) - *msg = "Authdir is rejecting routers in this range."; - return FP_REJECT; - } - if (!authdir_policy_valid_address(addr, or_port)) { - if (should_log) - log_info(LD_DIRSERV, "Not marking '%s' valid because of address '%s'", - nickname, address); - result |= FP_INVALID; - } - if (reject_unlisted) { - if (msg) - *msg = "Authdir rejects unknown routers."; - return FP_REJECT; - } + if (!authdir_policy_permits_address(addr, or_port)) { + if (should_log) + log_info(LD_DIRSERV, "Rejecting '%s' because of address '%s'", + nickname, fmt_addr32(addr)); + if (msg) + *msg = "Authdir is rejecting routers in this range."; + return FP_REJECT; + } + if (!authdir_policy_valid_address(addr, or_port)) { + if (should_log) + log_info(LD_DIRSERV, "Not marking '%s' valid because of address '%s'", + nickname, fmt_addr32(addr)); + result |= FP_INVALID; } return result; } -/** If we are an authoritative dirserver, and the list of approved - * servers contains one whose identity key digest is <b>digest</b>, - * return that router's nickname. Otherwise return NULL. */ -const char * -dirserv_get_nickname_by_digest(const char *digest) -{ - router_status_t *status; - if (!fingerprint_list) - return NULL; - tor_assert(digest); - - status = digestmap_get(fingerprint_list->status_by_digest, digest); - return status ? status->nickname : NULL; -} - /** Clear the current fingerprint list. */ void dirserv_free_fingerprint_list(void) @@ -526,26 +353,22 @@ dirserv_free_fingerprint_list(void) static int dirserv_router_has_valid_address(routerinfo_t *ri) { - struct in_addr iaddr; + tor_addr_t addr; if (get_options()->DirAllowPrivateAddresses) return 0; /* whatever it is, we're fine with it */ - if (!tor_inet_aton(ri->address, &iaddr)) { - log_info(LD_DIRSERV,"Router %s published non-IP address '%s'. Refusing.", - router_describe(ri), - ri->address); - return -1; - } - if (is_internal_IP(ntohl(iaddr.s_addr), 0)) { + tor_addr_from_ipv4h(&addr, ri->addr); + + if (tor_addr_is_internal(&addr, 0)) { log_info(LD_DIRSERV, - "Router %s published internal IP address '%s'. Refusing.", - router_describe(ri), ri->address); + "Router %s published internal IP address. Refusing.", + router_describe(ri)); return -1; /* it's a private IP, we should reject it */ } return 0; } /** Check whether we, as a directory server, want to accept <b>ri</b>. If so, - * set its is_valid,named,running fields and return 0. Otherwise, return -1. + * set its is_valid,running fields and return 0. Otherwise, return -1. * * If the router is rejected, set *<b>msg</b> to an explanation of why. * @@ -590,12 +413,10 @@ authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, } if (dirserv_router_has_valid_address(ri) < 0) { log_fn(severity, LD_DIRSERV, - "Router %s has invalid address '%s'. " - "Not adding (%s).", + "Router %s has invalid address. Not adding (%s).", router_describe(ri), - ri->address, esc_router_info(ri)); - *msg = "Rejected: Address is not an IP, or IP is a private address."; + *msg = "Rejected: Address is a private address."; return -1; } @@ -612,7 +433,6 @@ 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; } @@ -658,7 +478,7 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose, s = desc; list = smartlist_new(); if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 0, 0, - annotation_buf)) { + annotation_buf, NULL)) { SMARTLIST_FOREACH(list, routerinfo_t *, ri, { msg_out = NULL; tor_assert(ri->purpose == purpose); @@ -674,7 +494,7 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose, s = desc; if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 1, 0, - NULL)) { + NULL, NULL)) { SMARTLIST_FOREACH(list, extrainfo_t *, ei, { msg_out = NULL; @@ -692,7 +512,7 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose, if (!n_parsed) { *msg = "No descriptors found in your POST."; if (WRA_WAS_ADDED(r)) - r = ROUTER_WAS_NOT_NEW; + r = ROUTER_IS_ALREADY_KNOWN; } else { *msg = "(no message)"; } @@ -717,7 +537,8 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) was_router_added_t r; routerinfo_t *ri_old; char *desc, *nickname; - size_t desclen = 0; + const size_t desclen = ri->cache_info.signed_descriptor_len + + ri->cache_info.annotations_len; *msg = NULL; /* If it's too big, refuse it now. Otherwise we'll cache it all over the @@ -731,7 +552,7 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) *msg = "Router descriptor was too large."; control_event_or_authdir_new_descriptor("REJECTED", ri->cache_info.signed_descriptor_body, - ri->cache_info.signed_descriptor_len, *msg); + desclen, *msg); routerinfo_free(ri); return ROUTER_AUTHDIR_REJECTS; } @@ -752,14 +573,13 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) "the last one with this identity."; control_event_or_authdir_new_descriptor("DROPPED", ri->cache_info.signed_descriptor_body, - ri->cache_info.signed_descriptor_len, *msg); + desclen, *msg); routerinfo_free(ri); - return ROUTER_WAS_NOT_NEW; + return ROUTER_IS_ALREADY_KNOWN; } /* Make a copy of desc, since router_add_to_routerlist might free * ri and its associated signed_descriptor_t. */ - desclen = ri->cache_info.signed_descriptor_len; desc = tor_strndup(ri->cache_info.signed_descriptor_body, desclen); nickname = tor_strdup(ri->nickname); @@ -826,7 +646,7 @@ dirserv_add_extrainfo(extrainfo_t *ei, const char **msg) if ((r = routerinfo_incompatible_with_extrainfo(ri, ei, NULL, msg))) { extrainfo_free(ei); - return r < 0 ? ROUTER_WAS_NOT_NEW : ROUTER_BAD_EI; + return r < 0 ? ROUTER_IS_ALREADY_KNOWN : ROUTER_BAD_EI; } router_add_extrainfo_to_routerlist(ei, msg, 0, 0); return ROUTER_ADDED_SUCCESSFULLY; @@ -839,13 +659,12 @@ dirserv_add_extrainfo(extrainfo_t *ei, const char **msg) static void directory_remove_invalid(void) { - int changed = 0; routerlist_t *rl = router_get_routerlist(); smartlist_t *nodes = smartlist_new(); smartlist_add_all(nodes, nodelist_get_list()); SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) { - const char *msg; + const char *msg = NULL; routerinfo_t *ent = node->ri; char description[NODE_DESC_BUF_LEN]; uint32_t r; @@ -857,81 +676,24 @@ directory_remove_invalid(void) log_info(LD_DIRSERV, "Router %s is now rejected: %s", description, msg?msg:""); routerlist_remove(rl, ent, 0, time(NULL)); - changed = 1; continue; } -#if 0 - if (bool_neq((r & FP_NAMED), ent->auth_says_is_named)) { - log_info(LD_DIRSERV, - "Router %s is now %snamed.", description, - (r&FP_NAMED)?"":"un"); - ent->is_named = (r&FP_NAMED)?1:0; - changed = 1; - } - if (bool_neq((r & FP_UNNAMED), ent->auth_says_is_unnamed)) { - log_info(LD_DIRSERV, - "Router '%s' is now %snamed. (FP_UNNAMED)", description, - (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.", description, (r&FP_INVALID) ? "in" : ""); node->is_valid = (r&FP_INVALID)?0:1; - changed = 1; - } - if (bool_neq((r & FP_BADDIR), node->is_bad_directory)) { - log_info(LD_DIRSERV, "Router '%s' is now a %s directory", description, - (r & FP_BADDIR) ? "bad" : "good"); - node->is_bad_directory = (r&FP_BADDIR) ? 1: 0; - changed = 1; } if (bool_neq((r & FP_BADEXIT), node->is_bad_exit)) { log_info(LD_DIRSERV, "Router '%s' is now a %s exit", description, (r & FP_BADEXIT) ? "bad" : "good"); 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 - * directory, we will rebuild it instead of reusing the most recently - * generated one. - */ -void -directory_set_dirty(void) -{ - time_t now = time(NULL); - int set_v1_dirty=0; - - /* Regenerate stubs only every 8 hours. - * XXXX It would be nice to generate less often, but these are just - * stubs: it doesn't matter. */ -#define STUB_REGENERATE_INTERVAL (8*60*60) - if (!the_directory || !the_runningrouters.dir) - set_v1_dirty = 1; - else if (the_directory->published < now - STUB_REGENERATE_INTERVAL || - the_runningrouters.published < now - STUB_REGENERATE_INTERVAL) - set_v1_dirty = 1; - - if (set_v1_dirty) { - if (!the_directory_is_dirty) - the_directory_is_dirty = now; - if (!runningrouters_is_dirty) - runningrouters_is_dirty = now; - } - if (!the_v2_networkstatus_is_dirty) - the_v2_networkstatus_is_dirty = now; -} - /** * Allocate and return a description of the status of the server <b>desc</b>, * for use in a v1-style router-status line. The server is listed @@ -971,7 +733,7 @@ running_long_enough_to_decide_unreachable(void) } /** Each server needs to have passed a reachability test no more - * than this number of seconds ago, or he is listed as down in + * than this number of seconds ago, or it is listed as down in * the directory. */ #define REACHABLE_TIMEOUT (45*60) @@ -1118,16 +880,33 @@ format_versions_list(config_line_t *ln) } /** Return 1 if <b>ri</b>'s descriptor is "active" -- running, valid, - * not hibernating, and not too old. Else return 0. + * not hibernating, having observed bw greater 0, and not too old. Else + * return 0. */ static int 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) + if (ri->cache_info.published_on < cutoff) { return 0; - if (!node->is_running || !node->is_valid || ri->is_hibernating) + } + if (!node->is_running || !node->is_valid || ri->is_hibernating) { return 0; + } + /* Only require bandwith capacity in non-test networks, or + * if TestingTorNetwork, and TestingMinExitFlagThreshold is non-zero */ + if (!ri->bandwidthcapacity) { + if (get_options()->TestingTorNetwork) { + if (get_options()->TestingMinExitFlagThreshold > 0) { + /* If we're in a TestingTorNetwork, and TestingMinExitFlagThreshold is, + * then require bandwidthcapacity */ + return 0; + } + } else { + /* If we're not in a TestingTorNetwork, then require bandwidthcapacity */ + return 0; + } + } return 1; } @@ -1271,16 +1050,8 @@ directory_fetches_dir_info_later(const or_options_t *options) return options->UseBridges != 0; } -/** Return 1 if we want to cache v2 dir info (each status file). - */ -int -directory_caches_v2_dir_info(const or_options_t *options) -{ - return options->DirPort_set; -} - /** Return true iff we want to fetch and keep certificates for authorities - * that we don't acknowledge as aurthorities ourself. + * that we don't acknowledge as authorities ourself. */ int directory_caches_unknown_auth_certs(const or_options_t *options) @@ -1313,15 +1084,6 @@ directory_permits_begindir_requests(const or_options_t *options) return options->BridgeRelay != 0 || options->DirPort_set; } -/** Return 1 if we want to allow controllers to ask us directory - * requests via the controller interface, which doesn't require - * having any separate port open. */ -int -directory_permits_controller_requests(const or_options_t *options) -{ - return options->DirPort_set; -} - /** Return 1 if we have no need to fetch new descriptors. This generally * happens when we're not a dir cache and we haven't built any circuits * lately. @@ -1337,55 +1099,10 @@ directory_too_idle_to_fetch_descriptors(const or_options_t *options, /********************************************************************/ -/* Used only by non-v1-auth dirservers: The v1 directory and - * runningrouters we'll serve when requested. */ - -/** The v1 directory we'll serve (as a cache or as an authority) if - * requested. */ -static cached_dir_t *cached_directory = NULL; -/** The v1 runningrouters document we'll serve (as a cache or as an authority) - * if requested. */ -static cached_dir_t cached_runningrouters; - -/** Used for other dirservers' v2 network statuses. Map from hexdigest to - * cached_dir_t. */ -static digestmap_t *cached_v2_networkstatus = NULL; - /** 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 - * <b>directory</b> published on <b>when</b>, unless <b>when</b> is older than - * the last value, or too far in the future. - * - * Does not copy <b>directory</b>; frees it if it isn't used. - */ -static void -set_cached_dir(cached_dir_t *d, char *directory, time_t when) -{ - time_t now = time(NULL); - if (when<=d->published) { - log_info(LD_DIRSERV, "Ignoring old directory; not caching."); - tor_free(directory); - } else if (when>=now+ROUTER_MAX_AGE_TO_PUBLISH) { - log_info(LD_DIRSERV, "Ignoring future directory; not caching."); - tor_free(directory); - } else { - /* if (when>d->published && when<now+ROUTER_MAX_AGE) */ - log_debug(LD_DIRSERV, "Caching directory."); - tor_free(d->dir); - d->dir = directory; - d->dir_len = strlen(directory); - tor_free(d->dir_z); - if (tor_gzip_compress(&(d->dir_z), &(d->dir_z_len), d->dir, d->dir_len, - ZLIB_METHOD)) { - log_warn(LD_BUG,"Error compressing cached directory"); - } - d->published = when; - } -} - /** Decrement the reference count on <b>d</b>, and free it if it no longer has * any references. */ void @@ -1435,86 +1152,6 @@ free_cached_dir_(void *_d) cached_dir_decref(d); } -/** If we have no cached v1 directory, or it is older than <b>published</b>, - * then replace it with <b>directory</b>, published at <b>published</b>. - * - * If <b>published</b> is too old, do nothing. - * - * If <b>is_running_routers</b>, this is really a v1 running_routers - * document rather than a v1 directory. - */ -static void -dirserv_set_cached_directory(const char *directory, time_t published) -{ - - cached_dir_decref(cached_directory); - cached_directory = new_cached_dir(tor_strdup(directory), published); -} - -/** If <b>networkstatus</b> is non-NULL, we've just received a v2 - * network-status for an authoritative directory with identity digest - * <b>identity</b> published at <b>published</b> -- store it so we can - * serve it to others. - * - * If <b>networkstatus</b> is NULL, remove the entry with the given - * identity fingerprint from the v2 cache. - */ -void -dirserv_set_cached_networkstatus_v2(const char *networkstatus, - const char *identity, - time_t published) -{ - cached_dir_t *d, *old_d; - if (!cached_v2_networkstatus) - cached_v2_networkstatus = digestmap_new(); - - old_d = digestmap_get(cached_v2_networkstatus, identity); - if (!old_d && !networkstatus) - return; - - if (networkstatus) { - if (!old_d || published > old_d->published) { - d = new_cached_dir(tor_strdup(networkstatus), published); - digestmap_set(cached_v2_networkstatus, identity, d); - if (old_d) - cached_dir_decref(old_d); - } - } else { - if (old_d) { - digestmap_remove(cached_v2_networkstatus, identity); - cached_dir_decref(old_d); - } - } - - /* Now purge old entries. */ - - if (digestmap_size(cached_v2_networkstatus) > - get_n_authorities(V2_DIRINFO) + MAX_UNTRUSTED_NETWORKSTATUSES) { - /* We need to remove the oldest untrusted networkstatus. */ - const char *oldest = NULL; - time_t oldest_published = TIME_MAX; - digestmap_iter_t *iter; - - for (iter = digestmap_iter_init(cached_v2_networkstatus); - !digestmap_iter_done(iter); - iter = digestmap_iter_next(cached_v2_networkstatus, iter)) { - const char *ident; - void *val; - digestmap_iter_get(iter, &ident, &val); - d = val; - if (d->published < oldest_published && - !router_digest_is_trusted_dir(ident)) { - oldest = ident; - oldest_published = d->published; - } - } - tor_assert(oldest); - d = digestmap_remove(cached_v2_networkstatus, oldest); - if (d) - cached_dir_decref(d); - } -} - /** Replace the v3 consensus networkstatus of type <b>flavor_name</b> that * we're serving with <b>networkstatus</b>, published at <b>published</b>. No * validation is performed. */ @@ -1537,186 +1174,6 @@ dirserv_set_cached_consensus_networkstatus(const char *networkstatus, cached_dir_decref(old_networkstatus); } -/** Remove any v2 networkstatus from the directory cache that was published - * before <b>cutoff</b>. */ -void -dirserv_clear_old_networkstatuses(time_t cutoff) -{ - if (!cached_v2_networkstatus) - return; - - DIGESTMAP_FOREACH_MODIFY(cached_v2_networkstatus, id, cached_dir_t *, dir) { - if (dir->published < cutoff) { - char *fname; - fname = networkstatus_get_cache_filename(id); - if (file_status(fname) == FN_FILE) { - log_info(LD_DIR, "Removing too-old untrusted networkstatus in %s", - fname); - unlink(fname); - } - tor_free(fname); - cached_dir_decref(dir); - MAP_DEL_CURRENT(id); - } - } DIGESTMAP_FOREACH_END -} - -/** Remove any v1 info from the directory cache that was published - * too long ago. */ -void -dirserv_clear_old_v1_info(time_t now) -{ - if (cached_directory && - cached_directory->published < (now - MAX_V1_DIRECTORY_AGE)) { - cached_dir_decref(cached_directory); - cached_directory = NULL; - } - if (cached_runningrouters.published < (now - MAX_V1_RR_AGE)) { - clear_cached_dir(&cached_runningrouters); - } -} - -/** Helper: If we're an authority for the right directory version (v1 or v2) - * (based on <b>auth_type</b>), try to regenerate - * auth_src as appropriate and return it, falling back to cache_src on - * failure. If we're a cache, simply return cache_src. - */ -static cached_dir_t * -dirserv_pick_cached_dir_obj(cached_dir_t *cache_src, - cached_dir_t *auth_src, - time_t dirty, cached_dir_t *(*regenerate)(void), - const char *name, - dirinfo_type_t auth_type) -{ - const or_options_t *options = get_options(); - int authority = (auth_type == V1_DIRINFO && authdir_mode_v1(options)) || - (auth_type == V2_DIRINFO && authdir_mode_v2(options)); - - if (!authority || authdir_mode_bridge(options)) { - return cache_src; - } else { - /* We're authoritative. */ - if (regenerate != NULL) { - if (dirty && dirty + DIR_REGEN_SLACK_TIME < time(NULL)) { - if (!(auth_src = regenerate())) { - log_err(LD_BUG, "Couldn't generate %s?", name); - exit(1); - } - } else { - log_info(LD_DIRSERV, "The %s is still clean; reusing.", name); - } - } - return auth_src ? auth_src : cache_src; - } -} - -/** Return the most recently generated encoded signed v1 directory, - * generating a new one as necessary. If not a v1 authoritative directory - * may return NULL if no directory is yet cached. */ -cached_dir_t * -dirserv_get_directory(void) -{ - return dirserv_pick_cached_dir_obj(cached_directory, the_directory, - the_directory_is_dirty, - dirserv_regenerate_directory, - "v1 server directory", V1_DIRINFO); -} - -/** Only called by v1 auth dirservers. - * Generate a fresh v1 directory; set the_directory and return a pointer - * to the new value. - */ -static cached_dir_t * -dirserv_regenerate_directory(void) -{ - /* XXXX 024 Get rid of this function if we can confirm that nobody's - * fetching these any longer */ - char *new_directory=NULL; - - if (dirserv_dump_directory_to_string(&new_directory, - get_server_identity_key())) { - log_warn(LD_BUG, "Error creating directory."); - tor_free(new_directory); - return NULL; - } - cached_dir_decref(the_directory); - the_directory = new_cached_dir(new_directory, time(NULL)); - log_info(LD_DIRSERV,"New directory (size %d) has been built.", - (int)the_directory->dir_len); - log_debug(LD_DIRSERV,"New directory (size %d):\n%s", - (int)the_directory->dir_len, the_directory->dir); - - the_directory_is_dirty = 0; - - /* Save the directory to disk so we re-load it quickly on startup. - */ - dirserv_set_cached_directory(the_directory->dir, time(NULL)); - - return the_directory; -} - -/** Only called by v1 auth dirservers. - * Replace the current running-routers list with a newly generated one. */ -static cached_dir_t * -generate_runningrouters(void) -{ - char *s=NULL; - char digest[DIGEST_LEN]; - char published[ISO_TIME_LEN+1]; - size_t len; - crypto_pk_t *private_key = get_server_identity_key(); - char *identity_pkey; /* Identity key, DER64-encoded. */ - size_t identity_pkey_len; - - if (crypto_pk_write_public_key_to_string(private_key,&identity_pkey, - &identity_pkey_len)<0) { - log_warn(LD_BUG,"write identity_pkey to string failed!"); - goto err; - } - format_iso_time(published, time(NULL)); - - len = 2048; - s = tor_malloc_zero(len); - tor_snprintf(s, len, - "network-status\n" - "published %s\n" - "router-status %s\n" - "dir-signing-key\n%s" - "directory-signature %s\n", - published, "", identity_pkey, - get_options()->Nickname); - tor_free(identity_pkey); - if (router_get_runningrouters_hash(s,digest)) { - log_warn(LD_BUG,"couldn't compute digest"); - goto err; - } - note_crypto_pk_op(SIGN_DIR); - if (router_append_dirobj_signature(s, len, digest, DIGEST_LEN, - private_key)<0) - goto err; - - set_cached_dir(&the_runningrouters, s, time(NULL)); - runningrouters_is_dirty = 0; - - return &the_runningrouters; - err: - tor_free(s); - return NULL; -} - -/** Set *<b>rr</b> to the most recently generated encoded signed - * running-routers list, generating a new one as necessary. Return the - * size of the directory on success, and 0 on failure. */ -cached_dir_t * -dirserv_get_runningrouters(void) -{ - return dirserv_pick_cached_dir_obj( - &cached_runningrouters, &the_runningrouters, - runningrouters_is_dirty, - generate_runningrouters, - "v1 network status list", V1_DIRINFO); -} - /** Return the latest downloaded consensus networkstatus in encoded, signed, * optionally compressed format, suitable for sending to clients. */ cached_dir_t * @@ -1727,19 +1184,6 @@ dirserv_get_consensus(const char *flavor_name) return strmap_get(cached_consensuses, flavor_name); } -/** For authoritative directories: the current (v2) network status. */ -static cached_dir_t *the_v2_networkstatus = NULL; - -/** Return true iff our opinion of the routers has been stale for long - * enough that we should generate a new v2 network status doc. */ -static int -should_generate_v2_networkstatus(void) -{ - return authdir_mode_v2(get_options()) && - the_v2_networkstatus_is_dirty && - the_v2_networkstatus_is_dirty + DIR_REGEN_SLACK_TIME < time(NULL); -} - /** If a router's uptime is at least this value, then it is always * considered stable, regardless of the rest of the network. This * way we resist attacks where an attacker doubles the size of the @@ -1834,8 +1278,9 @@ dirserv_thinks_router_is_unreliable(time_t now, } /** Return true iff <b>router</b> should be assigned the "HSDir" flag. - * Right now this means it advertises support for it, it has a high - * uptime, it has a DirPort open, and it's currently considered Running. + * Right now this means it advertises support for it, it has a high uptime, + * it has a DirPort open, it has the Stable flag and it's currently + * considered Running. * * This function needs to be called after router-\>is_running has * been set. @@ -1861,16 +1306,10 @@ dirserv_thinks_router_is_hs_dir(const routerinfo_t *router, else uptime = real_uptime(router, now); - /* XXX We shouldn't need to check dir_port, but we do because of - * bug 1693. In the future, once relays set wants_to_be_hs_dir - * correctly, we can revert to only checking dir_port if router's - * version is too old. */ - /* XXX Unfortunately, we need to keep checking dir_port until all - * *clients* suffering from bug 2722 are obsolete. The first version - * to fix the bug was 0.2.2.25-alpha. */ return (router->wants_to_be_hs_dir && router->dir_port && + node->is_stable && uptime >= get_options()->MinUptimeHidServDirectoryV2 && - node->is_running); + router_is_active(router, node, now)); } /** Don't consider routers with less bandwidth than this when computing @@ -1907,7 +1346,7 @@ router_counts_toward_thresholds(const node_t *node, time_t now, * the Weighted Fractional Uptime history, and use them to set thresholds for * the Stable, Fast, and Guard flags. Update the fields stable_uptime, * stable_mtbf, enough_mtbf_info, guard_wfu, guard_tk, fast_bandwidth, - * guard_bandwidh_including_exits, guard_bandwidth_excluding_exits, + * guard_bandwidth_including_exits, and guard_bandwidth_excluding_exits. * * Also, set the is_exit flag of each router appropriately. */ static void @@ -1939,28 +1378,34 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, * sort them and use that to compute thresholds. */ n_active = n_active_nonexit = 0; /* Uptime for every active router. */ - uptimes = tor_malloc(sizeof(uint32_t)*smartlist_len(rl->routers)); + uptimes = tor_calloc(smartlist_len(rl->routers), sizeof(uint32_t)); /* Bandwidth for every active router. */ - bandwidths_kb = tor_malloc(sizeof(uint32_t)*smartlist_len(rl->routers)); + bandwidths_kb = tor_calloc(smartlist_len(rl->routers), sizeof(uint32_t)); /* Bandwidth for every active non-exit router. */ bandwidths_excluding_exits_kb = - tor_malloc(sizeof(uint32_t)*smartlist_len(rl->routers)); + tor_calloc(smartlist_len(rl->routers), sizeof(uint32_t)); /* Weighted mean time between failure for each active router. */ - mtbfs = tor_malloc(sizeof(double)*smartlist_len(rl->routers)); + mtbfs = tor_calloc(smartlist_len(rl->routers), sizeof(double)); /* Time-known for each active router. */ - tks = tor_malloc(sizeof(long)*smartlist_len(rl->routers)); + tks = tor_calloc(smartlist_len(rl->routers), sizeof(long)); /* Weighted fractional uptime for each active router. */ - wfus = tor_malloc(sizeof(double)*smartlist_len(rl->routers)); + wfus = tor_calloc(smartlist_len(rl->routers), sizeof(double)); nodelist_assert_ok(); /* Now, fill in the arrays. */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + if (options->BridgeAuthoritativeDir && + node->ri && + node->ri->purpose != ROUTER_PURPOSE_BRIDGE) + continue; if (router_counts_toward_thresholds(node, now, omit_as_sybil, require_mbw)) { routerinfo_t *ri = node->ri; const char *id = node->identity; uint32_t bw_kb; + /* resolve spurious clang shallow analysis null pointer errors */ + tor_assert(ri); 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); @@ -1984,9 +1429,10 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, /* The 12.5th percentile bandwidth is fast. */ fast_bandwidth_kb = find_nth_uint32(bandwidths_kb, n_active, n_active/8); /* (Now bandwidths is sorted.) */ - if (fast_bandwidth_kb < ROUTER_REQUIRED_MIN_BANDWIDTH/(2 * 1000)) + if (fast_bandwidth_kb < RELAY_REQUIRED_MIN_BANDWIDTH/(2 * 1000)) fast_bandwidth_kb = bandwidths_kb[n_active/4]; - guard_bandwidth_including_exits_kb = bandwidths_kb[(n_active-1)/2]; + guard_bandwidth_including_exits_kb = + third_quartile_uint32(bandwidths_kb, n_active); guard_tk = find_nth_long(tks, n_active, n_active/8); } @@ -2044,7 +1490,8 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, if (n_active_nonexit) { guard_bandwidth_excluding_exits_kb = - median_uint32(bandwidths_excluding_exits_kb, n_active_nonexit); + find_nth_uint32(bandwidths_excluding_exits_kb, + n_active_nonexit, n_active_nonexit*3/4); } log_info(LD_DIRSERV, @@ -2060,7 +1507,7 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, (unsigned long)guard_tk, (unsigned long)guard_bandwidth_including_exits_kb, (unsigned long)guard_bandwidth_excluding_exits_kb, - enough_mtbf_info ? "" : " don't "); + enough_mtbf_info ? "" : " don't"); tor_free(uptimes); tor_free(mtbfs); @@ -2070,6 +1517,21 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, tor_free(wfus); } +/* Use dirserv_compute_performance_thresholds() to compute the thresholds + * for the status flags, specifically for bridges. + * + * This is only called by a Bridge Authority from + * networkstatus_getinfo_by_purpose(). + */ +void +dirserv_compute_bridge_flag_thresholds(routerlist_t *rl) +{ + + digestmap_t *omit_as_sybil = digestmap_new(); + dirserv_compute_performance_thresholds(rl, omit_as_sybil); + digestmap_free(omit_as_sybil, NULL); +} + /** Measured bandwidth cache entry */ typedef struct mbw_cache_entry_s { long mbw_kb; @@ -2082,7 +1544,7 @@ static digestmap_t *mbw_cache = NULL; /** Store a measured bandwidth cache entry when reading the measured * bandwidths file. */ -void +STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, time_t as_of) { @@ -2112,7 +1574,7 @@ dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, } /** Clear and free the measured bandwidth cache */ -void +STATIC void dirserv_clear_measured_bw_cache(void) { if (mbw_cache) { @@ -2123,7 +1585,7 @@ dirserv_clear_measured_bw_cache(void) } /** Scan the measured bandwidth cache and remove expired entries */ -void +STATIC void dirserv_expire_measured_bw_cache(time_t now) { @@ -2145,7 +1607,7 @@ dirserv_expire_measured_bw_cache(time_t now) } /** Get the current size of the measured bandwidth cache */ -int +STATIC int dirserv_get_measured_bw_cache_size(void) { if (mbw_cache) return digestmap_size(mbw_cache); @@ -2155,7 +1617,7 @@ dirserv_get_measured_bw_cache_size(void) /** Query the cache by identity digest, return value indicates whether * we found it. The bw_out and as_of_out pointers receive the cached * bandwidth value and the time it was cached if not NULL. */ -int +STATIC int dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out, time_t *as_of_out) { @@ -2176,7 +1638,7 @@ dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out, } /** Predicate wrapper for dirserv_query_measured_bw_cache() */ -int +STATIC int dirserv_has_measured_bw(const char *node_id) { return dirserv_query_measured_bw_cache_kb(node_id, NULL, NULL); @@ -2341,13 +1803,12 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, char published[ISO_TIME_LEN+1]; char identity64[BASE64_DIGEST_LEN+1]; char digest64[BASE64_DIGEST_LEN+1]; - smartlist_t *chunks = NULL; + smartlist_t *chunks = smartlist_new(); format_iso_time(published, rs->published_on); digest_to_base64(identity64, rs->identity_digest); digest_to_base64(digest64, rs->descriptor_digest); - chunks = smartlist_new(); smartlist_add_asprintf(chunks, "r %s %s %s%s%s %s %d %d\n", rs->nickname, @@ -2378,20 +1839,17 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, goto done; smartlist_add_asprintf(chunks, - "s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + "s%s%s%s%s%s%s%s%s%s%s\n", /* These must stay in alphabetical order. */ rs->is_authority?" Authority":"", - rs->is_bad_directory?" BadDirectory":"", rs->is_bad_exit?" BadExit":"", rs->is_exit?" Exit":"", rs->is_fast?" Fast":"", rs->is_possible_guard?" Guard":"", rs->is_hs_dir?" HSDir":"", - rs->is_named?" Named":"", rs->is_flagged_running?" Running":"", rs->is_stable?" Stable":"", - rs->is_unnamed?" Unnamed":"", - rs->is_v2_dir?" V2Dir":"", + (rs->dir_port!=0)?" V2Dir":"", rs->is_valid?" Valid":""); /* length of "opt v \n" */ @@ -2459,6 +1917,13 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, smartlist_add_asprintf(chunks, " Measured=%d", vrs->measured_bw_kb); } + /* Write down guardfraction information if we have it. */ + if (format == NS_V3_VOTE && vrs && vrs->status.has_guardfraction) { + smartlist_add_asprintf(chunks, + " GuardFraction=%d", + vrs->status.guardfraction_percentage); + } + smartlist_add(chunks, tor_strdup("\n")); if (desc) { @@ -2472,10 +1937,8 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, result = smartlist_join_strings(chunks, "", 0, NULL); err: - if (chunks) { - SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); - smartlist_free(chunks); - } + SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); + smartlist_free(chunks); return result; } @@ -2581,78 +2044,8 @@ get_possible_sybil_list(const smartlist_t *routers) return omit_as_sybil; } -/** Return non-zero iff a relay running the Tor version specified in - * <b>platform</b> is suitable for use as a potential entry guard. */ -static int -is_router_version_good_for_possible_guard(const char *platform) -{ - static int parsed_versions_initialized = 0; - static tor_version_t first_good_0_2_1_guard_version; - static tor_version_t first_good_0_2_2_guard_version; - static tor_version_t first_good_later_guard_version; - - tor_version_t router_version; - - /* XXX024 This block should be extracted into its own function. */ - /* XXXX Begin code copied from tor_version_as_new_as (in routerparse.c) */ - { - char *s, *s2, *start; - char tmp[128]; - - tor_assert(platform); - - /* nonstandard Tor; be safe and say yes */ - if (strcmpstart(platform,"Tor ")) - return 1; - - start = (char *)eat_whitespace(platform+3); - if (!*start) return 0; - s = (char *)find_whitespace(start); /* also finds '\0', which is fine */ - s2 = (char*)eat_whitespace(s); - if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-")) - s = (char*)find_whitespace(s2); - - if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */ - return 0; - strlcpy(tmp, start, s-start+1); - - if (tor_version_parse(tmp, &router_version)<0) { - log_info(LD_DIR,"Router version '%s' unparseable.",tmp); - return 1; /* be safe and say yes */ - } - } - /* XXXX End code copied from tor_version_as_new_as (in routerparse.c) */ - - if (!parsed_versions_initialized) { - /* CVE-2011-2769 was fixed on the relay side in Tor versions - * 0.2.1.31, 0.2.2.34, and 0.2.3.6-alpha. */ - tor_assert(tor_version_parse("0.2.1.31", - &first_good_0_2_1_guard_version)>=0); - tor_assert(tor_version_parse("0.2.2.34", - &first_good_0_2_2_guard_version)>=0); - tor_assert(tor_version_parse("0.2.3.6-alpha", - &first_good_later_guard_version)>=0); - - /* Don't parse these constant version strings once for every relay - * for every vote. */ - parsed_versions_initialized = 1; - } - - return ((tor_version_same_series(&first_good_0_2_1_guard_version, - &router_version) && - tor_version_compare(&first_good_0_2_1_guard_version, - &router_version) <= 0) || - (tor_version_same_series(&first_good_0_2_2_guard_version, - &router_version) && - tor_version_compare(&first_good_0_2_2_guard_version, - &router_version) <= 0) || - (tor_version_compare(&first_good_later_guard_version, - &router_version) <= 0)); -} - /** Extract status information from <b>ri</b> and from other authority - * functions and store it in <b>rs</b>>. If <b>naming</b>, consider setting - * the named flag in <b>rs</b>. + * functions and store it in <b>rs</b>>. * * We assume that ri-\>is_running has already been set, e.g. by * dirserv_set_router_is_running(ri, now); @@ -2662,8 +2055,8 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, node_t *node, routerinfo_t *ri, time_t now, - int naming, int listbadexits, - int listbaddirs, int vote_on_hsdirs) + int listbadexits, + int vote_on_hsdirs) { const or_options_t *options = get_options(); uint32_t routerbw_kb = dirserv_get_credible_bandwidth_kb(ri); @@ -2683,20 +2076,13 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, !dirserv_thinks_router_is_unreliable(now, ri, 0, 1); rs->is_flagged_running = node->is_running; /* computed above */ - if (naming) { - uint32_t name_status = dirserv_get_name_status( - 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 = node->is_valid; if (node->is_fast && ((options->AuthDirGuardBWGuarantee && routerbw_kb >= options->AuthDirGuardBWGuarantee/1000) || routerbw_kb >= MIN(guard_bandwidth_including_exits_kb, - guard_bandwidth_excluding_exits_kb)) && - is_router_version_good_for_possible_guard(ri->platform)) { + guard_bandwidth_excluding_exits_kb))) { long tk = rep_hist_get_weighted_time_known( node->identity, now); double wfu = rep_hist_get_weighted_fractional_uptime( @@ -2706,14 +2092,11 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, rs->is_possible_guard = 0; } - 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 = vote_on_hsdirs && 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->is_named = rs->is_unnamed = 0; rs->published_on = ri->cache_info.published_on; memcpy(rs->identity_digest, node->identity, DIGEST_LEN); @@ -2731,6 +2114,28 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, tor_addr_copy(&rs->ipv6_addr, &ri->ipv6_addr); rs->ipv6_orport = ri->ipv6_orport; } + + /* Iff we are in a testing network, use TestingDirAuthVoteExit, + TestingDirAuthVoteGuard, and TestingDirAuthVoteHSDir to + give out the Exit, Guard, and HSDir flags, respectively. + But don't set the corresponding node flags. */ + if (options->TestingTorNetwork) { + if (routerset_contains_routerstatus(options->TestingDirAuthVoteExit, + rs, 0)) { + rs->is_exit = 1; + } + + if (routerset_contains_routerstatus(options->TestingDirAuthVoteGuard, + rs, 0)) { + rs->is_possible_guard = 1; + } + + if (routerset_contains_routerstatus(options->TestingDirAuthVoteHSDir, + rs, 0)) { + /* TestingDirAuthVoteHSDir respects VoteOnHidServDirectoriesV2 */ + rs->is_hs_dir = vote_on_hsdirs; + } + } } /** Routerstatus <b>rs</b> is part of a group of routers that are on @@ -2741,20 +2146,332 @@ static void clear_status_flags_on_sybil(routerstatus_t *rs) { rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = - 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; + rs->is_flagged_running = rs->is_named = rs->is_valid = + rs->is_hs_dir = rs->is_possible_guard = rs->is_bad_exit = 0; /* FFFF we might want some mechanism to check later on if we * missed zeroing any flags: it's easy to add a new flag but * forget to add it to this clause. */ } +/** The guardfraction of the guard with identity fingerprint <b>guard_id</b> + * is <b>guardfraction_percentage</b>. See if we have a vote routerstatus for + * this guard in <b>vote_routerstatuses</b>, and if we do, register the + * information to it. + * + * Return 1 if we applied the information and 0 if we couldn't find a + * matching guard. + * + * Requires that <b>vote_routerstatuses</b> be sorted. + */ +static int +guardfraction_line_apply(const char *guard_id, + uint32_t guardfraction_percentage, + smartlist_t *vote_routerstatuses) +{ + vote_routerstatus_t *vrs = NULL; + + tor_assert(vote_routerstatuses); + + vrs = smartlist_bsearch(vote_routerstatuses, guard_id, + compare_digest_to_vote_routerstatus_entry); + + if (!vrs) { + return 0; + } + + vrs->status.has_guardfraction = 1; + vrs->status.guardfraction_percentage = guardfraction_percentage; + + return 1; +} + +/* Given a guard line from a guardfraction file, parse it and register + * its information to <b>vote_routerstatuses</b>. + * + * Return: + * * 1 if the line was proper and its information got registered. + * * 0 if the line was proper but no currently active guard was found + * to register the guardfraction information to. + * * -1 if the line could not be parsed and set <b>err_msg</b> to a + newly allocated string containing the error message. + */ +static int +guardfraction_file_parse_guard_line(const char *guard_line, + smartlist_t *vote_routerstatuses, + char **err_msg) +{ + char guard_id[DIGEST_LEN]; + uint32_t guardfraction; + char *inputs_tmp = NULL; + int num_ok = 1; + + smartlist_t *sl = smartlist_new(); + int retval = -1; + + tor_assert(err_msg); + + /* guard_line should contain something like this: + <hex digest> <guardfraction> <appearances> */ + smartlist_split_string(sl, guard_line, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3); + if (smartlist_len(sl) < 3) { + tor_asprintf(err_msg, "bad line '%s'", guard_line); + goto done; + } + + inputs_tmp = smartlist_get(sl, 0); + if (strlen(inputs_tmp) != HEX_DIGEST_LEN || + base16_decode(guard_id, DIGEST_LEN, inputs_tmp, HEX_DIGEST_LEN)) { + tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp); + goto done; + } + + inputs_tmp = smartlist_get(sl, 1); + /* Guardfraction is an integer in [0, 100]. */ + guardfraction = + (uint32_t) tor_parse_long(inputs_tmp, 10, 0, 100, &num_ok, NULL); + if (!num_ok) { + tor_asprintf(err_msg, "wrong percentage '%s'", inputs_tmp); + goto done; + } + + /* If routerstatuses were provided, apply this info to actual routers. */ + if (vote_routerstatuses) { + retval = guardfraction_line_apply(guard_id, guardfraction, + vote_routerstatuses); + } else { + retval = 0; /* If we got this far, line was correctly formatted. */ + } + + done: + + SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); + smartlist_free(sl); + + return retval; +} + +/** Given an inputs line from a guardfraction file, parse it and + * register its information to <b>total_consensuses</b> and + * <b>total_days</b>. + * + * Return 0 if it parsed well. Return -1 if there was an error, and + * set <b>err_msg</b> to a newly allocated string containing the + * error message. + */ +static int +guardfraction_file_parse_inputs_line(const char *inputs_line, + int *total_consensuses, + int *total_days, + char **err_msg) +{ + int retval = -1; + char *inputs_tmp = NULL; + int num_ok = 1; + smartlist_t *sl = smartlist_new(); + + tor_assert(err_msg); + + /* Second line is inputs information: + * n-inputs <total_consensuses> <total_days>. */ + smartlist_split_string(sl, inputs_line, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3); + if (smartlist_len(sl) < 2) { + tor_asprintf(err_msg, "incomplete line '%s'", inputs_line); + goto done; + } + + inputs_tmp = smartlist_get(sl, 0); + *total_consensuses = + (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL); + if (!num_ok) { + tor_asprintf(err_msg, "unparseable consensus '%s'", inputs_tmp); + goto done; + } + + inputs_tmp = smartlist_get(sl, 1); + *total_days = + (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL); + if (!num_ok) { + tor_asprintf(err_msg, "unparseable days '%s'", inputs_tmp); + goto done; + } + + retval = 0; + + done: + SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); + smartlist_free(sl); + + return retval; +} + +/* Maximum age of a guardfraction file that we are willing to accept. */ +#define MAX_GUARDFRACTION_FILE_AGE (7*24*60*60) /* approx a week */ + +/** Static strings of guardfraction files. */ +#define GUARDFRACTION_DATE_STR "written-at" +#define GUARDFRACTION_INPUTS "n-inputs" +#define GUARDFRACTION_GUARD "guard-seen" +#define GUARDFRACTION_VERSION "guardfraction-file-version" + +/** Given a guardfraction file in a string, parse it and register the + * guardfraction information to the provided vote routerstatuses. + * + * This is the rough format of the guardfraction file: + * + * guardfraction-file-version 1 + * written-at <date and time> + * n-inputs <number of consesuses parsed> <number of days considered> + * + * guard-seen <fpr 1> <guardfraction percentage> <consensus appearances> + * guard-seen <fpr 2> <guardfraction percentage> <consensus appearances> + * guard-seen <fpr 3> <guardfraction percentage> <consensus appearances> + * guard-seen <fpr 4> <guardfraction percentage> <consensus appearances> + * guard-seen <fpr 5> <guardfraction percentage> <consensus appearances> + * ... + * + * Return -1 if the parsing failed and 0 if it went smoothly. Parsing + * should tolerate errors in all lines but the written-at header. + */ +STATIC int +dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str, + smartlist_t *vote_routerstatuses) +{ + config_line_t *front=NULL, *line; + int ret_tmp; + int retval = -1; + int current_line_n = 0; /* line counter for better log messages */ + + /* Guardfraction info to be parsed */ + int total_consensuses = 0; + int total_days = 0; + + /* Stats */ + int guards_read_n = 0; + int guards_applied_n = 0; + + /* Parse file and split it in lines */ + ret_tmp = config_get_lines(guardfraction_file_str, &front, 0); + if (ret_tmp < 0) { + log_warn(LD_CONFIG, "Error reading from guardfraction file"); + goto done; + } + + /* Sort routerstatuses (needed later when applying guardfraction info) */ + if (vote_routerstatuses) + smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries); + + for (line = front; line; line=line->next) { + current_line_n++; + + if (!strcmp(line->key, GUARDFRACTION_VERSION)) { + int num_ok = 1; + unsigned int version; + + version = + (unsigned int) tor_parse_long(line->value, + 10, 0, INT_MAX, &num_ok, NULL); + + if (!num_ok || version != 1) { + log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version); + goto done; + } + } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) { + time_t file_written_at; + time_t now = time(NULL); + + /* First line is 'written-at <date>' */ + if (parse_iso_time(line->value, &file_written_at) < 0) { + log_warn(LD_CONFIG, "Guardfraction:%d: Bad date '%s'. Ignoring", + current_line_n, line->value); + goto done; /* don't tolerate failure here. */ + } + if (file_written_at < now - MAX_GUARDFRACTION_FILE_AGE) { + log_warn(LD_CONFIG, "Guardfraction:%d: was written very long ago '%s'", + current_line_n, line->value); + goto done; /* don't tolerate failure here. */ + } + } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) { + char *err_msg = NULL; + + if (guardfraction_file_parse_inputs_line(line->value, + &total_consensuses, + &total_days, + &err_msg) < 0) { + log_warn(LD_CONFIG, "Guardfraction:%d: %s", + current_line_n, err_msg); + tor_free(err_msg); + continue; + } + + } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) { + char *err_msg = NULL; + + ret_tmp = guardfraction_file_parse_guard_line(line->value, + vote_routerstatuses, + &err_msg); + if (ret_tmp < 0) { /* failed while parsing the guard line */ + log_warn(LD_CONFIG, "Guardfraction:%d: %s", + current_line_n, err_msg); + tor_free(err_msg); + continue; + } + + /* Successfully parsed guard line. Check if it was applied properly. */ + guards_read_n++; + if (ret_tmp > 0) { + guards_applied_n++; + } + } else { + log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)", + current_line_n, line->key, line->value); + } + } + + retval = 0; + + log_info(LD_CONFIG, + "Successfully parsed guardfraction file with %d consensuses over " + "%d days. Parsed %d nodes and applied %d of them%s.", + total_consensuses, total_days, guards_read_n, guards_applied_n, + vote_routerstatuses ? "" : " (no routerstatus provided)" ); + + done: + config_free_lines(front); + + if (retval < 0) { + return retval; + } else { + return guards_read_n; + } +} + +/** Read a guardfraction file at <b>fname</b> and load all its + * information to <b>vote_routerstatuses</b>. */ +int +dirserv_read_guardfraction_file(const char *fname, + smartlist_t *vote_routerstatuses) +{ + char *guardfraction_file_str; + + /* Read file to a string */ + guardfraction_file_str = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL); + if (!guardfraction_file_str) { + log_warn(LD_FS, "Cannot open guardfraction file '%s'. Failing.", fname); + return -1; + } + + return dirserv_read_guardfraction_file_from_str(guardfraction_file_str, + vote_routerstatuses); +} + /** * Helper function to parse out a line in the measured bandwidth file * into a measured_bw_line_t output structure. Returns -1 on failure * or 0 on success. */ -int +STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) { char *line = tor_strdup(orig_line); @@ -2835,7 +2552,7 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) * of bandwidth statuses. Returns true if a line is found, * false otherwise. */ -int +STATIC int measured_bw_line_apply(measured_bw_line_t *parsed_line, smartlist_t *routerstatuses) { @@ -2865,7 +2582,7 @@ int dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses) { - char line[256]; + char line[512]; FILE *fp = tor_fopen_cloexec(from_file, "r"); int applied_lines = 0; time_t file_time, now; @@ -2886,7 +2603,7 @@ dirserv_read_measured_bandwidths(const char *from_file, } line[strlen(line)-1] = '\0'; - file_time = tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL); + file_time = (time_t)tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL); if (!ok) { log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s", escaped(line)); @@ -2941,9 +2658,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, smartlist_t *routers, *routerstatuses; char identity_digest[DIGEST_LEN]; char signing_key_digest[DIGEST_LEN]; - int naming = options->NamingAuthoritativeDir; int listbadexits = options->AuthDirListBadExits; - int listbaddirs = options->AuthDirListBadDirs; int vote_on_hsdirs = options->VoteOnHidServDirectoriesV2; routerlist_t *rl = router_get_routerlist(); time_t now = time(NULL); @@ -2957,14 +2672,6 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, tor_assert(private_key); tor_assert(cert); - if (resolve_my_address(LOG_WARN, options, &addr, NULL, &hostname)<0) { - log_warn(LD_NET, "Couldn't resolve my hostname"); - return NULL; - } - if (!hostname || !strchr(hostname, '.')) { - tor_free(hostname); - hostname = tor_dup_ip(addr); - } if (crypto_pk_get_digest(private_key, signing_key_digest)<0) { log_err(LD_BUG, "Error computing signing key digest"); return NULL; @@ -2973,6 +2680,14 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, log_err(LD_BUG, "Error computing identity key digest"); return NULL; } + if (resolve_my_address(LOG_WARN, options, &addr, NULL, &hostname)<0) { + log_warn(LD_NET, "Couldn't resolve my hostname"); + return NULL; + } + if (!hostname || !strchr(hostname, '.')) { + tor_free(hostname); + hostname = tor_dup_ip(addr); + } if (options->VersioningAuthoritativeDir) { client_versions = format_versions_list(options->RecommendedClientVersions); @@ -3035,7 +2750,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); rs = &vrs->status; set_routerstatus_from_routerinfo(rs, node, ri, now, - naming, listbadexits, listbaddirs, + listbadexits, vote_on_hsdirs); if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest)) @@ -3063,6 +2778,12 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, smartlist_free(routers); digestmap_free(omit_as_sybil, NULL); + /* Apply guardfraction information to routerstatuses. */ + if (options->GuardfractionFile) { + dirserv_read_guardfraction_file(options->GuardfractionFile, + routerstatuses); + } + /* This pass through applies the measured bw lines to the routerstatuses */ if (options->V3BandwidthsFile) { dirserv_read_measured_bandwidths(options->V3BandwidthsFile, @@ -3093,7 +2814,8 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, else last_consensus_interval = options->TestingV3AuthInitialVotingInterval; v3_out->valid_after = - dirvote_get_start_of_next_interval(now, (int)last_consensus_interval); + dirvote_get_start_of_next_interval(now, (int)last_consensus_interval, + options->TestingV3AuthVotingStartOffset); format_iso_time(tbuf, v3_out->valid_after); log_notice(LD_DIR,"Choosing valid-after time in vote as %s: " "consensus_set=%d, last_interval=%d", @@ -3110,20 +2832,23 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, v3_out->client_versions = client_versions; v3_out->server_versions = server_versions; + v3_out->package_lines = smartlist_new(); + { + config_line_t *cl; + for (cl = get_options()->RecommendedPackages; cl; cl = cl->next) { + if (validate_recommended_package_line(cl->value)) + smartlist_add(v3_out->package_lines, tor_strdup(cl->value)); + } + } + v3_out->known_flags = smartlist_new(); smartlist_split_string(v3_out->known_flags, "Authority Exit Fast Guard Stable V2Dir Valid", 0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); if (vote_on_reachability) smartlist_add(v3_out->known_flags, tor_strdup("Running")); - if (listbaddirs) - smartlist_add(v3_out->known_flags, tor_strdup("BadDirectory")); if (listbadexits) smartlist_add(v3_out->known_flags, tor_strdup("BadExit")); - if (naming) { - smartlist_add(v3_out->known_flags, tor_strdup("Named")); - smartlist_add(v3_out->known_flags, tor_strdup("Unnamed")); - } if (vote_on_hsdirs) smartlist_add(v3_out->known_flags, tor_strdup("HSDir")); smartlist_sort_strings(v3_out->known_flags); @@ -3164,270 +2889,6 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, return v3_out; } -/** For v2 authoritative directories only: Replace the contents of - * <b>the_v2_networkstatus</b> with a newly generated network status - * object. */ -cached_dir_t * -generate_v2_networkstatus_opinion(void) -{ - cached_dir_t *r = NULL; - size_t identity_pkey_len; - char *status = NULL, *client_versions = NULL, *server_versions = NULL, - *identity_pkey = NULL, *hostname = NULL; - const or_options_t *options = get_options(); - char fingerprint[FINGERPRINT_LEN+1]; - char published[ISO_TIME_LEN+1]; - char digest[DIGEST_LEN]; - uint32_t addr; - crypto_pk_t *private_key; - routerlist_t *rl = router_get_routerlist(); - time_t now = time(NULL); - time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH; - int naming = options->NamingAuthoritativeDir; - int versioning = options->VersioningAuthoritativeDir; - int listbaddirs = options->AuthDirListBadDirs; - int listbadexits = options->AuthDirListBadExits; - int vote_on_hsdirs = options->VoteOnHidServDirectoriesV2; - const char *contact; - char *version_lines = NULL; - smartlist_t *routers = NULL; - digestmap_t *omit_as_sybil = NULL; - smartlist_t *chunks = NULL; - - private_key = get_server_identity_key(); - - if (resolve_my_address(LOG_WARN, options, &addr, NULL, &hostname)<0) { - log_warn(LD_NET, "Couldn't resolve my hostname"); - goto done; - } - if (!hostname) - hostname = tor_dup_ip(addr); - - format_iso_time(published, now); - - client_versions = format_versions_list(options->RecommendedClientVersions); - server_versions = format_versions_list(options->RecommendedServerVersions); - - if (crypto_pk_write_public_key_to_string(private_key, &identity_pkey, - &identity_pkey_len)<0) { - log_warn(LD_BUG,"Writing public key to string failed."); - goto done; - } - - if (crypto_pk_get_fingerprint(private_key, fingerprint, 0)<0) { - log_err(LD_BUG, "Error computing fingerprint"); - goto done; - } - - contact = options->ContactInfo; - if (!contact) - contact = "(none)"; - - if (versioning) { - tor_asprintf(&version_lines, - "client-versions %s\nserver-versions %s\n", - client_versions, server_versions); - } else { - version_lines = tor_strdup(""); - } - - chunks = smartlist_new(); - smartlist_add_asprintf(chunks, - "network-status-version 2\n" - "dir-source %s %s %d\n" - "fingerprint %s\n" - "contact %s\n" - "published %s\n" - "dir-options%s%s%s%s\n" - "%s" /* client version line, server version line. */ - "dir-signing-key\n%s", - hostname, fmt_addr32(addr), - (int)router_get_advertised_dir_port(options, 0), - fingerprint, - contact, - published, - naming ? " Names" : "", - listbaddirs ? " BadDirectories" : "", - listbadexits ? " BadExits" : "", - versioning ? " Versions" : "", - version_lines, - identity_pkey); - - /* precompute this part, since we need it to decide what "stable" - * means. */ - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { - dirserv_set_router_is_running(ri, now); - }); - - routers = smartlist_new(); - smartlist_add_all(routers, rl->routers); - routers_sort_by_identity(routers); - omit_as_sybil = get_possible_sybil_list(routers); - - dirserv_compute_performance_thresholds(rl, omit_as_sybil); - - SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) { - if (ri->cache_info.published_on >= cutoff) { - routerstatus_t rs; - char *version = version_from_platform(ri->platform); - 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, - vote_on_hsdirs); - - if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest)) - clear_status_flags_on_sybil(&rs); - - { - char *rsf = routerstatus_format_entry(&rs, version, NS_V2, NULL); - if (rsf) - smartlist_add(chunks, rsf); - } - tor_free(version); - } - } SMARTLIST_FOREACH_END(ri); - - smartlist_add_asprintf(chunks, "directory-signature %s\n", - options->Nickname); - - crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1); - - note_crypto_pk_op(SIGN_DIR); - { - char *sig; - if (!(sig = router_get_dirobj_signature(digest,DIGEST_LEN, - private_key))) { - log_warn(LD_BUG, "Unable to sign router status."); - goto done; - } - smartlist_add(chunks, sig); - } - - status = smartlist_join_strings(chunks, "", 0, NULL); - - { - networkstatus_v2_t *ns; - if (!(ns = networkstatus_v2_parse_from_string(status))) { - log_err(LD_BUG,"Generated a networkstatus we couldn't parse."); - goto done; - } - networkstatus_v2_free(ns); - } - - { - cached_dir_t **ns_ptr = &the_v2_networkstatus; - if (*ns_ptr) - cached_dir_decref(*ns_ptr); - *ns_ptr = new_cached_dir(status, now); - status = NULL; /* So it doesn't get double-freed. */ - the_v2_networkstatus_is_dirty = 0; - router_set_networkstatus_v2((*ns_ptr)->dir, now, NS_GENERATED, NULL); - r = *ns_ptr; - } - - done: - if (chunks) { - SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); - smartlist_free(chunks); - } - tor_free(client_versions); - tor_free(server_versions); - tor_free(version_lines); - tor_free(status); - tor_free(hostname); - tor_free(identity_pkey); - smartlist_free(routers); - digestmap_free(omit_as_sybil, NULL); - return r; -} - -/** Given the portion of a networkstatus request URL after "tor/status/" in - * <b>key</b>, append to <b>result</b> the digests of the identity keys of the - * networkstatus objects that the client has requested. */ -void -dirserv_get_networkstatus_v2_fingerprints(smartlist_t *result, - const char *key) -{ - tor_assert(result); - - if (!cached_v2_networkstatus) - cached_v2_networkstatus = digestmap_new(); - - if (should_generate_v2_networkstatus()) - generate_v2_networkstatus_opinion(); - - if (!strcmp(key,"authority")) { - if (authdir_mode_v2(get_options())) { - const routerinfo_t *me = router_get_my_routerinfo(); - if (me) - smartlist_add(result, - tor_memdup(me->cache_info.identity_digest, DIGEST_LEN)); - } - } else if (!strcmp(key, "all")) { - if (digestmap_size(cached_v2_networkstatus)) { - digestmap_iter_t *iter; - iter = digestmap_iter_init(cached_v2_networkstatus); - while (!digestmap_iter_done(iter)) { - const char *ident; - void *val; - digestmap_iter_get(iter, &ident, &val); - smartlist_add(result, tor_memdup(ident, DIGEST_LEN)); - iter = digestmap_iter_next(cached_v2_networkstatus, iter); - } - } else { - SMARTLIST_FOREACH(router_get_trusted_dir_servers(), - dir_server_t *, ds, - if (ds->type & V2_DIRINFO) - smartlist_add(result, tor_memdup(ds->digest, DIGEST_LEN))); - } - smartlist_sort_digests(result); - if (smartlist_len(result) == 0) - log_info(LD_DIRSERV, - "Client requested 'all' network status objects; we have none."); - } else if (!strcmpstart(key, "fp/")) { - dir_split_resource_into_fingerprints(key+3, result, NULL, - DSR_HEX|DSR_SORT_UNIQ); - } -} - -/** Look for a network status object as specified by <b>key</b>, which should - * be either "authority" (to find a network status generated by us), a hex - * identity digest (to find a network status generated by given directory), or - * "all" (to return all the v2 network status objects we have). - */ -void -dirserv_get_networkstatus_v2(smartlist_t *result, - const char *key) -{ - cached_dir_t *cached; - smartlist_t *fingerprints = smartlist_new(); - tor_assert(result); - - if (!cached_v2_networkstatus) - cached_v2_networkstatus = digestmap_new(); - - dirserv_get_networkstatus_v2_fingerprints(fingerprints, key); - SMARTLIST_FOREACH_BEGIN(fingerprints, const char *, fp) { - if (router_digest_is_me(fp) && should_generate_v2_networkstatus()) - generate_v2_networkstatus_opinion(); - cached = digestmap_get(cached_v2_networkstatus, fp); - if (cached) { - smartlist_add(result, cached); - } else { - char hexbuf[HEX_DIGEST_LEN+1]; - base16_encode(hexbuf, sizeof(hexbuf), fp, DIGEST_LEN); - log_info(LD_DIRSERV, "Don't know about any network status with " - "fingerprint '%s'", hexbuf); - } - } SMARTLIST_FOREACH_END(fp); - SMARTLIST_FOREACH(fingerprints, char *, cp, tor_free(cp)); - smartlist_free(fingerprints); -} - /** As dirserv_get_routerdescs(), but instead of getting signed_descriptor_t * pointers, adds copies of digests to fps_out, and doesn't use the * /tor/server/ prefix. For a /d/ request, adds descriptor digests; for other @@ -3661,7 +3122,7 @@ dirserv_single_reachability_test(time_t now, routerinfo_t *router) /* IPv4. */ log_debug(LD_OR,"Testing reachability of %s at %s:%u.", - router->nickname, router->address, router->or_port); + router->nickname, fmt_addr32(router->addr), router->or_port); tor_addr_from_ipv4h(&router_addr, router->addr); chan = channel_tls_connect(&router_addr, router->or_port, router->cache_info.identity_digest); @@ -3727,15 +3188,12 @@ static cached_dir_t * lookup_cached_dir_by_fp(const char *fp) { cached_dir_t *d = NULL; - if (tor_digest_is_zero(fp) && cached_consensuses) + if (tor_digest_is_zero(fp) && cached_consensuses) { d = strmap_get(cached_consensuses, "ns"); - else if (memchr(fp, '\0', DIGEST_LEN) && cached_consensuses && + } else if (memchr(fp, '\0', DIGEST_LEN) && cached_consensuses && (d = strmap_get(cached_consensuses, fp))) { /* this here interface is a nasty hack XXXX024 */; - } else if (router_digest_is_me(fp) && the_v2_networkstatus) - d = the_v2_networkstatus; - else if (cached_v2_networkstatus) - d = digestmap_get(cached_v2_networkstatus, fp); + } return d; } @@ -3941,8 +3399,6 @@ connection_dirserv_add_servers_to_outbuf(dir_connection_t *conn) } body = signed_descriptor_get_body(sd); if (conn->zlib_state) { - /* XXXX024 This 'last' business should actually happen on the last - * routerinfo, not on the last fingerprint. */ int last = ! smartlist_len(conn->fingerprint_stack); connection_write_to_buf_zlib(body, sd->signed_descriptor_len, conn, last); @@ -3959,6 +3415,11 @@ connection_dirserv_add_servers_to_outbuf(dir_connection_t *conn) if (!smartlist_len(conn->fingerprint_stack)) { /* We just wrote the last one; finish up. */ + if (conn->zlib_state) { + connection_write_to_buf_zlib("", 0, conn, 1); + tor_zlib_free(conn->zlib_state); + conn->zlib_state = NULL; + } conn->dir_spool_src = DIR_SPOOL_NONE; smartlist_free(conn->fingerprint_stack); conn->fingerprint_stack = NULL; @@ -3984,8 +3445,6 @@ connection_dirserv_add_microdescs_to_outbuf(dir_connection_t *conn) if (!md || !md->body) continue; if (conn->zlib_state) { - /* XXXX024 This 'last' business should actually happen on the last - * routerinfo, not on the last fingerprint. */ int last = !smartlist_len(conn->fingerprint_stack); connection_write_to_buf_zlib(md->body, md->bodylen, conn, last); if (last) { @@ -3997,6 +3456,11 @@ connection_dirserv_add_microdescs_to_outbuf(dir_connection_t *conn) } } if (!smartlist_len(conn->fingerprint_stack)) { + if (conn->zlib_state) { + connection_write_to_buf_zlib("", 0, conn, 1); + tor_zlib_free(conn->zlib_state); + conn->zlib_state = NULL; + } conn->dir_spool_src = DIR_SPOOL_NONE; smartlist_free(conn->fingerprint_stack); conn->fingerprint_stack = NULL; @@ -4069,7 +3533,7 @@ connection_dirserv_add_networkstatus_bytes_to_outbuf(dir_connection_t *conn) if (uncompressing && ! conn->zlib_state && conn->fingerprint_stack && smartlist_len(conn->fingerprint_stack)) { - conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD); + conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION); } } if (r) return r; @@ -4122,20 +3586,86 @@ connection_dirserv_flushed_some(dir_connection_t *conn) } } +/** Return true iff <b>line</b> is a valid RecommendedPackages line. + */ +/* + The grammar is: + + "package" SP PACKAGENAME SP VERSION SP URL SP DIGESTS NL + + PACKAGENAME = NONSPACE + VERSION = NONSPACE + URL = NONSPACE + DIGESTS = DIGEST | DIGESTS SP DIGEST + DIGEST = DIGESTTYPE "=" DIGESTVAL + + NONSPACE = one or more non-space printing characters + + DIGESTVAL = DIGESTTYPE = one or more non-=, non-" " characters. + + SP = " " + NL = a newline + + */ +int +validate_recommended_package_line(const char *line) +{ + const char *cp = line; + +#define WORD() \ + do { \ + if (*cp == ' ') \ + return 0; \ + cp = strchr(cp, ' '); \ + if (!cp) \ + return 0; \ + } while (0) + + WORD(); /* skip packagename */ + ++cp; + WORD(); /* skip version */ + ++cp; + WORD(); /* Skip URL */ + ++cp; + + /* Skip digesttype=digestval + */ + int n_entries = 0; + while (1) { + const char *start_of_word = cp; + const char *end_of_word = strchr(cp, ' '); + if (! end_of_word) + end_of_word = cp + strlen(cp); + + if (start_of_word == end_of_word) + return 0; + + const char *eq = memchr(start_of_word, '=', end_of_word - start_of_word); + + if (!eq) + return 0; + if (eq == start_of_word) + return 0; + if (eq == end_of_word - 1) + return 0; + if (memchr(eq+1, '=', end_of_word - (eq+1))) + return 0; + + ++n_entries; + if (0 == *end_of_word) + break; + + cp = end_of_word + 1; + } + + return (n_entries == 0) ? 0 : 1; +} + /** Release all storage used by the directory server. */ void dirserv_free_all(void) { dirserv_free_fingerprint_list(); - cached_dir_decref(the_directory); - clear_cached_dir(&the_runningrouters); - cached_dir_decref(the_v2_networkstatus); - cached_dir_decref(cached_directory); - clear_cached_dir(&cached_runningrouters); - - digestmap_free(cached_v2_networkstatus, free_cached_dir_); - cached_v2_networkstatus = NULL; strmap_free(cached_consensuses, free_cached_dir_); cached_consensuses = NULL; diff --git a/src/or/dirserv.h b/src/or/dirserv.h index f9d36d760f..311a513dbe 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,6 +12,8 @@ #ifndef TOR_DIRSERV_H #define TOR_DIRSERV_H +#include "testsupport.h" + /** What fraction (1 over this number) of the relay ID space do we * (as a directory authority) launch connections to at each reachability * test? */ @@ -32,10 +34,9 @@ int connection_dirserv_flushed_some(dir_connection_t *conn); -int dirserv_add_own_fingerprint(const char *nickname, crypto_pk_t *pk); +int dirserv_add_own_fingerprint(crypto_pk_t *pk); int dirserv_load_fingerprint_file(void); void dirserv_free_fingerprint_list(void); -const char *dirserv_get_nickname_by_digest(const char *digest); enum was_router_added_t dirserv_add_multiple_descriptors( const char *desc, uint8_t purpose, const char *source, @@ -49,34 +50,23 @@ int list_server_status_v1(smartlist_t *routers, char **router_status_out, int dirserv_dump_directory_to_string(char **dir_out, crypto_pk_t *private_key); char *dirserv_get_flag_thresholds_line(void); +void dirserv_compute_bridge_flag_thresholds(routerlist_t *rl); int directory_fetches_from_authorities(const or_options_t *options); int directory_fetches_dir_info_early(const or_options_t *options); int directory_fetches_dir_info_later(const or_options_t *options); -int directory_caches_v2_dir_info(const or_options_t *options); int directory_caches_unknown_auth_certs(const or_options_t *options); int directory_caches_dir_info(const or_options_t *options); int directory_permits_begindir_requests(const or_options_t *options); -int directory_permits_controller_requests(const or_options_t *options); int directory_too_idle_to_fetch_descriptors(const or_options_t *options, time_t now); -void directory_set_dirty(void); -cached_dir_t *dirserv_get_directory(void); -cached_dir_t *dirserv_get_runningrouters(void); cached_dir_t *dirserv_get_consensus(const char *flavor_name); -void dirserv_set_cached_networkstatus_v2(const char *directory, - const char *identity, - time_t published); void dirserv_set_cached_consensus_networkstatus(const char *consensus, const char *flavor_name, const digests_t *digests, time_t published); void dirserv_clear_old_networkstatuses(time_t cutoff); -void dirserv_clear_old_v1_info(time_t now); -void dirserv_get_networkstatus_v2(smartlist_t *result, const char *key); -void dirserv_get_networkstatus_v2_fingerprints(smartlist_t *result, - const char *key); int dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, const char **msg, int for_unencrypted_conn, @@ -114,29 +104,38 @@ void dirserv_free_all(void); void cached_dir_decref(cached_dir_t *d); cached_dir_t *new_cached_dir(char *s, time_t published); +int validate_recommended_package_line(const char *line); + #ifdef DIRSERV_PRIVATE /* Put the MAX_MEASUREMENT_AGE #define here so unit tests can see it */ #define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */ -int measured_bw_line_parse(measured_bw_line_t *out, const char *line); +STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *line); -int measured_bw_line_apply(measured_bw_line_t *parsed_line, +STATIC int measured_bw_line_apply(measured_bw_line_t *parsed_line, smartlist_t *routerstatuses); -void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, +STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, time_t as_of); -void dirserv_clear_measured_bw_cache(void); -void dirserv_expire_measured_bw_cache(time_t now); -int dirserv_get_measured_bw_cache_size(void); -int dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_out, - time_t *as_of_out); -int dirserv_has_measured_bw(const char *node_id); -cached_dir_t *generate_v2_networkstatus_opinion(void); +STATIC void dirserv_clear_measured_bw_cache(void); +STATIC void dirserv_expire_measured_bw_cache(time_t now); +STATIC int dirserv_get_measured_bw_cache_size(void); +STATIC int dirserv_query_measured_bw_cache_kb(const char *node_id, + long *bw_out, + time_t *as_of_out); +STATIC int dirserv_has_measured_bw(const char *node_id); + +STATIC int +dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str, + smartlist_t *vote_routerstatuses); #endif int dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses); +int dirserv_read_guardfraction_file(const char *fname, + smartlist_t *vote_routerstatuses); + #endif diff --git a/src/or/dirvote.c b/src/or/dirvote.c index c6d1244902..7a5154dae5 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRVOTE_PRIVATE @@ -16,6 +16,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "entrynodes.h" /* needed for guardfraction methods */ /** * \file dirvote.c @@ -60,12 +61,13 @@ static char *make_consensus_method_list(int low, int high, const char *sep); /** Return a new string containing the string representation of the vote in * <b>v3_ns</b>, signed with our v3 signing key <b>private_signing_key</b>. * For v3 authorities. */ -char * +STATIC char * format_networkstatus_vote(crypto_pk_t *private_signing_key, networkstatus_t *v3_ns) { - smartlist_t *chunks; + smartlist_t *chunks = smartlist_new(); const char *client_versions = NULL, *server_versions = NULL; + char *packages = NULL; char fingerprint[FINGERPRINT_LEN+1]; char digest[DIGEST_LEN]; uint32_t addr; @@ -98,7 +100,18 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, server_versions_line = tor_strdup(""); } - chunks = smartlist_new(); + if (v3_ns->package_lines) { + smartlist_t *tmp = smartlist_new(); + SMARTLIST_FOREACH(v3_ns->package_lines, const char *, p, + if (validate_recommended_package_line(p)) + smartlist_add_asprintf(tmp, "package %s\n", p)); + packages = smartlist_join_strings(tmp, "", 0, NULL); + SMARTLIST_FOREACH(tmp, char *, cp, tor_free(cp)); + smartlist_free(tmp); + } else { + packages = tor_strdup(""); + } + { char published[ISO_TIME_LEN+1]; char va[ISO_TIME_LEN+1]; @@ -110,7 +123,8 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, char *params; authority_cert_t *cert = v3_ns->cert; char *methods = - make_consensus_method_list(1, MAX_SUPPORTED_CONSENSUS_METHOD, " "); + make_consensus_method_list(MIN_SUPPORTED_CONSENSUS_METHOD, + MAX_SUPPORTED_CONSENSUS_METHOD, " "); format_iso_time(published, v3_ns->published); format_iso_time(va, v3_ns->valid_after); format_iso_time(fu, v3_ns->fresh_until); @@ -132,6 +146,7 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, "valid-until %s\n" "voting-delay %d %d\n" "%s%s" /* versions */ + "%s" /* packages */ "known-flags %s\n" "flag-thresholds %s\n" "params %s\n" @@ -143,6 +158,7 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, v3_ns->vote_seconds, v3_ns->dist_seconds, client_versions_line, server_versions_line, + packages, flags, flag_thresholds, params, @@ -230,10 +246,10 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, done: tor_free(client_versions_line); tor_free(server_versions_line); - if (chunks) { - SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); - smartlist_free(chunks); - } + tor_free(packages); + + SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); + smartlist_free(chunks); return status; } @@ -335,6 +351,9 @@ static int compare_vote_rs(const vote_routerstatus_t *a, const vote_routerstatus_t *b) { int r; + tor_assert(a); + tor_assert(b); + if ((r = fast_memcmp(a->status.identity_digest, b->status.identity_digest, DIGEST_LEN))) return r; @@ -432,6 +451,7 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, const tor_addr_port_t *most_alt_orport = NULL; SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) { + tor_assert(rs); if (compare_vote_rs(most, rs) == 0 && !tor_addr_is_null(&rs->status.ipv6_addr) && rs->status.ipv6_orport) { @@ -454,8 +474,7 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, smartlist_free(alt_orports); } - if (consensus_method >= MIN_METHOD_FOR_MICRODESC && - microdesc_digest256_out) { + if (microdesc_digest256_out) { smartlist_t *digests = smartlist_new(); const char *best_microdesc_digest; SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) { @@ -537,7 +556,8 @@ compute_consensus_method(smartlist_t *votes) static int consensus_method_is_supported(int method) { - return (method >= 1) && (method <= MAX_SUPPORTED_CONSENSUS_METHOD); + return (method >= MIN_SUPPORTED_CONSENSUS_METHOD) && + (method <= MAX_SUPPORTED_CONSENSUS_METHOD); } /** Return a newly allocated string holding the numbers between low and high @@ -587,7 +607,7 @@ compute_consensus_versions_list(smartlist_t *lst, int n_versioning) /** Helper: given a list of valid networkstatus_t, return a new string * containing the contents of the consensus network parameter set. */ -/* private */ char * +STATIC char * dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) { int i; @@ -601,13 +621,14 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) const int n_votes = smartlist_len(votes); smartlist_t *output; smartlist_t *param_list = smartlist_new(); + (void) method; /* We require that the parameter lists in the votes are well-formed: that is, that their keywords are unique and sorted, and that their values are between INT32_MIN and INT32_MAX inclusive. This should be guaranteed by the parsing code. */ - vals = tor_malloc(sizeof(int)*n_votes); + vals = tor_calloc(n_votes, sizeof(int)); SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) { if (!v->net_params) @@ -643,12 +664,13 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) next_param = NULL; else next_param = smartlist_get(param_list, param_sl_idx+1); + /* resolve spurious clang shallow analysis null pointer errors */ + tor_assert(param); if (!next_param || strncmp(next_param, param, cur_param_len)) { /* We've reached the end of a series. */ /* Make sure enough authorities voted on this param, unless the * the consensus method we use is too old for that. */ - if (method < MIN_METHOD_FOR_MAJORITY_PARAMS || - i > total_authorities/2 || + if (i > total_authorities/2 || i >= MIN_VOTES_FOR_PARAM) { int32_t median = median_int32(vals, i); char *out_string = tor_malloc(64+cur_param_len); @@ -1001,299 +1023,87 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); return 1; } -/** - * This function computes the bandwidth weights for consensus method 9. - * - * It has been obsoleted in favor of consensus method 10. - */ + +/** Update total bandwidth weights (G/M/E/D/T) with the bandwidth of + * the router in <b>rs</b>. */ static void -networkstatus_compute_bw_weights_v9(smartlist_t *chunks, int64_t G, int64_t M, - int64_t E, int64_t D, int64_t T, - int64_t weight_scale) +update_total_bandwidth_weights(const routerstatus_t *rs, + int is_exit, int is_guard, + int64_t *G, int64_t *M, int64_t *E, int64_t *D, + int64_t *T) { - int64_t Wgg = -1, Wgd = -1; - int64_t Wmg = -1, Wme = -1, Wmd = -1; - int64_t Wed = -1, Wee = -1; - const char *casename; + int default_bandwidth = rs->bandwidth_kb; + int guardfraction_bandwidth = 0; - if (G <= 0 || M <= 0 || E <= 0 || D <= 0) { - log_warn(LD_DIR, "Consensus with empty bandwidth: " - "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT - " D="I64_FORMAT" T="I64_FORMAT, - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); + if (!rs->has_bandwidth) { + log_info(LD_BUG, "Missing consensus bandwidth for router %s", + rs->nickname); return; } - /* - * Computed from cases in 3.4.3 of dir-spec.txt + /* If this routerstatus represents a guard that we have + * guardfraction information on, use it to calculate its actual + * bandwidth. From proposal236: * - * 1. Neither are scarce - * 2. Both Guard and Exit are scarce - * a. R+D <= S - * b. R+D > S - * 3. One of Guard or Exit is scarce - * a. S+D < T/3 - * b. S+D >= T/3 + * Similarly, when calculating the bandwidth-weights line as in + * section 3.8.3 of dir-spec.txt, directory authorities should treat N + * as if fraction F of its bandwidth has the guard flag and (1-F) does + * not. So when computing the totals G,M,E,D, each relay N with guard + * visibility fraction F and bandwidth B should be added as follows: + * + * G' = G + F*B, if N does not have the exit flag + * M' = M + (1-F)*B, if N does not have the exit flag + * + * or + * + * D' = D + F*B, if N has the exit flag + * E' = E + (1-F)*B, if N has the exit flag + * + * In this block of code, we prepare the bandwidth values by setting + * the default_bandwidth to F*B and guardfraction_bandwidth to (1-F)*B. */ - if (3*E >= T && 3*G >= T) { // E >= T/3 && G >= T/3 - bw_weights_error_t berr = 0; - /* Case 1: Neither are scarce. - * - * Attempt to ensure that we have a large amount of exit bandwidth - * in the middle position. - */ - casename = "Case 1 (Wme*E = Wmd*D)"; - Wgg = (weight_scale*(D+E+G+M))/(3*G); - if (D==0) Wmd = 0; - else Wmd = (weight_scale*(2*D + 2*E - G - M))/(6*D); - Wme = (weight_scale*(2*D + 2*E - G - M))/(6*E); - Wee = (weight_scale*(-2*D + 4*E + G + M))/(6*E); - Wgd = 0; - Wmg = weight_scale - Wgg; - Wed = weight_scale - Wmd; + if (rs->has_guardfraction) { + guardfraction_bandwidth_t guardfraction_bw; - berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed, - weight_scale, G, M, E, D, T, 10, 1); + tor_assert(is_guard); - if (berr) { - log_warn(LD_DIR, "Bw Weights error %d for case %s. " - "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT - " D="I64_FORMAT" T="I64_FORMAT, - berr, casename, - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - } - } else if (3*E < T && 3*G < T) { // E < T/3 && G < T/3 - int64_t R = MIN(E, G); - int64_t S = MAX(E, G); - /* - * Case 2: Both Guards and Exits are scarce - * Balance D between E and G, depending upon - * D capacity and scarcity. - */ - if (R+D < S) { // Subcase a - Wgg = weight_scale; - Wee = weight_scale; - Wmg = 0; - Wme = 0; - Wmd = 0; - if (E < G) { - casename = "Case 2a (E scarce)"; - Wed = weight_scale; - Wgd = 0; - } else { /* E >= G */ - casename = "Case 2a (G scarce)"; - Wed = 0; - Wgd = weight_scale; - } - } else { // Subcase b: R+D > S - bw_weights_error_t berr = 0; - casename = "Case 2b (Wme*E == Wmd*D)"; - if (D != 0) { - Wgg = weight_scale; - Wgd = (weight_scale*(D + E - 2*G + M))/(3*D); // T/3 >= G (Ok) - Wmd = (weight_scale*(D + E + G - 2*M))/(6*D); // T/3 >= M - Wme = (weight_scale*(D + E + G - 2*M))/(6*E); - Wee = (weight_scale*(-D + 5*E - G + 2*M))/(6*E); // 2E+M >= T/3 - Wmg = 0; - Wed = weight_scale - Wgd - Wmd; + guard_get_guardfraction_bandwidth(&guardfraction_bw, + rs->bandwidth_kb, + rs->guardfraction_percentage); - berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed, - weight_scale, G, M, E, D, T, 10, 1); - } + default_bandwidth = guardfraction_bw.guard_bw; + guardfraction_bandwidth = guardfraction_bw.non_guard_bw; + } - if (D == 0 || berr) { // Can happen if M > T/3 - casename = "Case 2b (E=G)"; - Wgg = weight_scale; - Wee = weight_scale; - Wmg = 0; - Wme = 0; - Wmd = 0; - if (D == 0) Wgd = 0; - else Wgd = (weight_scale*(D+E-G))/(2*D); - Wed = weight_scale - Wgd; - berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, - Wed, weight_scale, G, M, E, D, T, 10, 1); - } - if (berr != BW_WEIGHTS_NO_ERROR && - berr != BW_WEIGHTS_BALANCE_MID_ERROR) { - log_warn(LD_DIR, "Bw Weights error %d for case %s. " - "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT - " D="I64_FORMAT" T="I64_FORMAT, - berr, casename, - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - } - } - } else { // if (E < T/3 || G < T/3) { - int64_t S = MIN(E, G); - // Case 3: Exactly one of Guard or Exit is scarce - if (!(3*E < T || 3*G < T) || !(3*G >= T || 3*E >= T)) { - log_warn(LD_BUG, - "Bw-Weights Case 3 but with G="I64_FORMAT" M=" - I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT, - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); + /* Now calculate the total bandwidth weights with or without + * guardfraction. Depending on the flags of the relay, add its + * bandwidth to the appropriate weight pool. If it's a guard and + * guardfraction is enabled, add its bandwidth to both pools as + * indicated by the previous comment. + */ + *T += default_bandwidth; + if (is_exit && is_guard) { + + *D += default_bandwidth; + if (rs->has_guardfraction) { + *E += guardfraction_bandwidth; } - if (3*(S+D) < T) { // Subcase a: S+D < T/3 - if (G < E) { - casename = "Case 3a (G scarce)"; - Wgg = Wgd = weight_scale; - Wmd = Wed = Wmg = 0; - // Minor subcase, if E is more scarce than M, - // keep its bandwidth in place. - if (E < M) Wme = 0; - else Wme = (weight_scale*(E-M))/(2*E); - Wee = weight_scale-Wme; - } else { // G >= E - casename = "Case 3a (E scarce)"; - Wee = Wed = weight_scale; - Wmd = Wgd = Wme = 0; - // Minor subcase, if G is more scarce than M, - // keep its bandwidth in place. - if (G < M) Wmg = 0; - else Wmg = (weight_scale*(G-M))/(2*G); - Wgg = weight_scale-Wmg; - } - } else { // Subcase b: S+D >= T/3 - bw_weights_error_t berr = 0; - // D != 0 because S+D >= T/3 - if (G < E) { - casename = "Case 3b (G scarce, Wme*E == Wmd*D)"; - Wgd = (weight_scale*(D + E - 2*G + M))/(3*D); - Wmd = (weight_scale*(D + E + G - 2*M))/(6*D); - Wme = (weight_scale*(D + E + G - 2*M))/(6*E); - Wee = (weight_scale*(-D + 5*E - G + 2*M))/(6*E); - Wgg = weight_scale; - Wmg = 0; - Wed = weight_scale - Wgd - Wmd; + } else if (is_exit) { - berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, - Wed, weight_scale, G, M, E, D, T, 10, 1); - } else { // G >= E - casename = "Case 3b (E scarce, Wme*E == Wmd*D)"; - Wgg = (weight_scale*(D + E + G + M))/(3*G); - Wmd = (weight_scale*(2*D + 2*E - G - M))/(6*D); - Wme = (weight_scale*(2*D + 2*E - G - M))/(6*E); - Wee = (weight_scale*(-2*D + 4*E + G + M))/(6*E); - Wgd = 0; - Wmg = weight_scale - Wgg; - Wed = weight_scale - Wmd; + *E += default_bandwidth; - berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, - Wed, weight_scale, G, M, E, D, T, 10, 1); - } - if (berr) { - log_warn(LD_DIR, "Bw Weights error %d for case %s. " - "G="I64_FORMAT" M="I64_FORMAT - " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT, - berr, casename, - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - } - } - } + } else if (is_guard) { - /* We cast down the weights to 32 bit ints on the assumption that - * weight_scale is ~= 10000. We need to ensure a rogue authority - * doesn't break this assumption to rig our weights */ - tor_assert(0 < weight_scale && weight_scale <= INT32_MAX); + *G += default_bandwidth; + if (rs->has_guardfraction) { + *M += guardfraction_bandwidth; + } - if (Wgg < 0 || Wgg > weight_scale) { - log_warn(LD_DIR, "Bw %s: Wgg="I64_FORMAT"! G="I64_FORMAT - " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, I64_PRINTF_ARG(Wgg), - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); + } else { - Wgg = MAX(MIN(Wgg, weight_scale), 0); - } - if (Wgd < 0 || Wgd > weight_scale) { - log_warn(LD_DIR, "Bw %s: Wgd="I64_FORMAT"! G="I64_FORMAT - " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, I64_PRINTF_ARG(Wgd), - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - Wgd = MAX(MIN(Wgd, weight_scale), 0); - } - if (Wmg < 0 || Wmg > weight_scale) { - log_warn(LD_DIR, "Bw %s: Wmg="I64_FORMAT"! G="I64_FORMAT - " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, I64_PRINTF_ARG(Wmg), - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - Wmg = MAX(MIN(Wmg, weight_scale), 0); - } - if (Wme < 0 || Wme > weight_scale) { - log_warn(LD_DIR, "Bw %s: Wme="I64_FORMAT"! G="I64_FORMAT - " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, I64_PRINTF_ARG(Wme), - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - Wme = MAX(MIN(Wme, weight_scale), 0); + *M += default_bandwidth; } - if (Wmd < 0 || Wmd > weight_scale) { - log_warn(LD_DIR, "Bw %s: Wmd="I64_FORMAT"! G="I64_FORMAT - " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, I64_PRINTF_ARG(Wmd), - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - Wmd = MAX(MIN(Wmd, weight_scale), 0); - } - if (Wee < 0 || Wee > weight_scale) { - log_warn(LD_DIR, "Bw %s: Wee="I64_FORMAT"! G="I64_FORMAT - " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, I64_PRINTF_ARG(Wee), - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - Wee = MAX(MIN(Wee, weight_scale), 0); - } - if (Wed < 0 || Wed > weight_scale) { - log_warn(LD_DIR, "Bw %s: Wed="I64_FORMAT"! G="I64_FORMAT - " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, I64_PRINTF_ARG(Wed), - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); - Wed = MAX(MIN(Wed, weight_scale), 0); - } - - // Add consensus weight keywords - smartlist_add(chunks, tor_strdup("bandwidth-weights ")); - /* - * Provide Wgm=Wgg, Wmm=1, Wem=Wee, Weg=Wed. May later determine - * that middle nodes need different bandwidth weights for dirport traffic, - * or that weird exit policies need special weight, or that bridges - * need special weight. - * - * NOTE: This list is sorted. - */ - smartlist_add_asprintf(chunks, - "Wbd=%d Wbe=%d Wbg=%d Wbm=%d " - "Wdb=%d " - "Web=%d Wed=%d Wee=%d Weg=%d Wem=%d " - "Wgb=%d Wgd=%d Wgg=%d Wgm=%d " - "Wmb=%d Wmd=%d Wme=%d Wmg=%d Wmm=%d\n", - (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale, - (int)weight_scale, - (int)weight_scale, (int)Wed, (int)Wee, (int)Wed, (int)Wee, - (int)weight_scale, (int)Wgd, (int)Wgg, (int)Wgg, - (int)weight_scale, (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale); - - log_notice(LD_CIRC, "Computed bandwidth weights for %s with v9: " - "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT - " T="I64_FORMAT, - casename, - I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E), - I64_PRINTF_ARG(D), I64_PRINTF_ARG(T)); } /** Given a list of vote networkstatus_t in <b>votes</b>, our public @@ -1326,6 +1136,7 @@ networkstatus_compute_consensus(smartlist_t *votes, const routerstatus_format_type_t rs_format = flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC; char *params = NULL; + char *packages = NULL; int added_weights = 0; tor_assert(flavor == FLAV_NS || flavor == FLAV_MICRODESC); tor_assert(total_authorities >= smartlist_len(votes)); @@ -1346,18 +1157,18 @@ networkstatus_compute_consensus(smartlist_t *votes, log_warn(LD_DIR, "The other authorities will use consensus method %d, " "which I don't support. Maybe I should upgrade!", consensus_method); - consensus_method = 1; + consensus_method = MAX_SUPPORTED_CONSENSUS_METHOD; } /* Compute medians of time-related things, and figure out how many * routers we might need to talk about. */ { int n_votes = smartlist_len(votes); - time_t *va_times = tor_malloc(n_votes * sizeof(time_t)); - time_t *fu_times = tor_malloc(n_votes * sizeof(time_t)); - time_t *vu_times = tor_malloc(n_votes * sizeof(time_t)); - int *votesec_list = tor_malloc(n_votes * sizeof(int)); - int *distsec_list = tor_malloc(n_votes * sizeof(int)); + time_t *va_times = tor_calloc(n_votes, sizeof(time_t)); + time_t *fu_times = tor_calloc(n_votes, sizeof(time_t)); + time_t *vu_times = tor_calloc(n_votes, sizeof(time_t)); + int *votesec_list = tor_calloc(n_votes, sizeof(int)); + int *distsec_list = tor_calloc(n_votes, sizeof(int)); int n_versioning_clients = 0, n_versioning_servers = 0; smartlist_t *combined_client_versions = smartlist_new(); smartlist_t *combined_server_versions = smartlist_new(); @@ -1396,8 +1207,12 @@ networkstatus_compute_consensus(smartlist_t *votes, vote_seconds = median_int(votesec_list, n_votes); dist_seconds = median_int(distsec_list, n_votes); - tor_assert(valid_after+MIN_VOTE_INTERVAL <= fresh_until); - tor_assert(fresh_until+MIN_VOTE_INTERVAL <= valid_until); + tor_assert(valid_after + + (get_options()->TestingTorNetwork ? + MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) <= fresh_until); + tor_assert(fresh_until + + (get_options()->TestingTorNetwork ? + MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) <= valid_until); tor_assert(vote_seconds >= MIN_VOTE_SECONDS); tor_assert(dist_seconds >= MIN_DIST_SECONDS); @@ -1405,6 +1220,11 @@ networkstatus_compute_consensus(smartlist_t *votes, n_versioning_servers); client_versions = compute_consensus_versions_list(combined_client_versions, n_versioning_clients); + if (consensus_method >= MIN_METHOD_FOR_PACKAGE_LINES) { + packages = compute_consensus_package_lines(votes); + } else { + packages = tor_strdup(""); + } SMARTLIST_FOREACH(combined_server_versions, char *, cp, tor_free(cp)); SMARTLIST_FOREACH(combined_client_versions, char *, cp, tor_free(cp)); @@ -1437,10 +1257,8 @@ networkstatus_compute_consensus(smartlist_t *votes, flavor == FLAV_NS ? "" : " ", flavor == FLAV_NS ? "" : flavor_name); - if (consensus_method >= 2) { - smartlist_add_asprintf(chunks, "consensus-method %d\n", - consensus_method); - } + smartlist_add_asprintf(chunks, "consensus-method %d\n", + consensus_method); smartlist_add_asprintf(chunks, "valid-after %s\n" @@ -1449,22 +1267,23 @@ networkstatus_compute_consensus(smartlist_t *votes, "voting-delay %d %d\n" "client-versions %s\n" "server-versions %s\n" + "%s" /* packages */ "known-flags %s\n", va_buf, fu_buf, vu_buf, vote_seconds, dist_seconds, - client_versions, server_versions, flaglist); + client_versions, server_versions, + packages, + flaglist); tor_free(flaglist); } - if (consensus_method >= MIN_METHOD_FOR_PARAMS) { - params = dirvote_compute_params(votes, consensus_method, - total_authorities); - if (params) { - smartlist_add(chunks, tor_strdup("params ")); - smartlist_add(chunks, params); - smartlist_add(chunks, tor_strdup("\n")); - } + params = dirvote_compute_params(votes, consensus_method, + total_authorities); + if (params) { + smartlist_add(chunks, tor_strdup("params ")); + smartlist_add(chunks, params); + smartlist_add(chunks, tor_strdup("\n")); } /* Sort the votes. */ @@ -1478,8 +1297,7 @@ networkstatus_compute_consensus(smartlist_t *votes, e->digest = get_voter(v)->identity_digest; e->is_legacy = 0; smartlist_add(dir_sources, e); - if (consensus_method >= 3 && - !tor_digest_is_zero(get_voter(v)->legacy_id_digest)) { + if (!tor_digest_is_zero(get_voter(v)->legacy_id_digest)) { dir_src_ent_t *e_legacy = tor_malloc_zero(sizeof(dir_src_ent_t)); e_legacy->v = v; e_legacy->digest = get_voter(v)->legacy_id_digest; @@ -1495,9 +1313,6 @@ networkstatus_compute_consensus(smartlist_t *votes, networkstatus_t *v = e->v; networkstatus_voter_info_t *voter = get_voter(v); - if (e->is_legacy) - tor_assert(consensus_method >= 2); - base16_encode(fingerprint, sizeof(fingerprint), e->digest, DIGEST_LEN); base16_encode(votedigest, sizeof(votedigest), voter->vote_digest, DIGEST_LEN); @@ -1555,12 +1370,15 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_t *chosen_flags = smartlist_new(); smartlist_t *versions = smartlist_new(); smartlist_t *exitsummaries = smartlist_new(); - uint32_t *bandwidths_kb = tor_malloc(sizeof(uint32_t) * - smartlist_len(votes)); - uint32_t *measured_bws_kb = tor_malloc(sizeof(uint32_t) * - smartlist_len(votes)); + uint32_t *bandwidths_kb = tor_calloc(smartlist_len(votes), + sizeof(uint32_t)); + uint32_t *measured_bws_kb = tor_calloc(smartlist_len(votes), + sizeof(uint32_t)); + uint32_t *measured_guardfraction = tor_calloc(smartlist_len(votes), + sizeof(uint32_t)); int num_bandwidths; int num_mbws; + int num_guardfraction_inputs; int *n_voter_flags; /* n_voter_flags[j] is the number of flags that * votes[j] knows about. */ @@ -1570,7 +1388,6 @@ networkstatus_compute_consensus(smartlist_t *votes, * is the same flag as votes[j]->known_flags[b]. */ int *named_flag; /* Index of the flag "Named" for votes[j] */ int *unnamed_flag; /* Index of the flag "Unnamed" for votes[j] */ - int chosen_named_idx; int n_authorities_measuring_bandwidth; strmap_t *name_to_id_map = strmap_new(); @@ -1579,16 +1396,15 @@ networkstatus_compute_consensus(smartlist_t *votes, memset(conflict, 0, sizeof(conflict)); memset(unknown, 0xff, sizeof(conflict)); - index = tor_malloc_zero(sizeof(int)*smartlist_len(votes)); - size = tor_malloc_zero(sizeof(int)*smartlist_len(votes)); - n_voter_flags = tor_malloc_zero(sizeof(int) * smartlist_len(votes)); - n_flag_voters = tor_malloc_zero(sizeof(int) * smartlist_len(flags)); - flag_map = tor_malloc_zero(sizeof(int*) * smartlist_len(votes)); - named_flag = tor_malloc_zero(sizeof(int) * smartlist_len(votes)); - unnamed_flag = tor_malloc_zero(sizeof(int) * smartlist_len(votes)); + index = tor_calloc(smartlist_len(votes), sizeof(int)); + size = tor_calloc(smartlist_len(votes), sizeof(int)); + n_voter_flags = tor_calloc(smartlist_len(votes), sizeof(int)); + n_flag_voters = tor_calloc(smartlist_len(flags), sizeof(int)); + flag_map = tor_calloc(smartlist_len(votes), sizeof(int *)); + named_flag = tor_calloc(smartlist_len(votes), sizeof(int)); + unnamed_flag = tor_calloc(smartlist_len(votes), sizeof(int)); for (i = 0; i < smartlist_len(votes); ++i) unnamed_flag[i] = named_flag[i] = -1; - chosen_named_idx = smartlist_string_pos(flags, "Named"); /* Build the flag indexes. Note that no vote can have more than 64 members * for known_flags, so no value will be greater than 63, so it's safe to @@ -1597,8 +1413,8 @@ networkstatus_compute_consensus(smartlist_t *votes, * that they're actually set before doing U64_LITERAL(1) << index with * them.*/ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) { - flag_map[v_sl_idx] = tor_malloc_zero( - sizeof(int)*smartlist_len(v->known_flags)); + flag_map[v_sl_idx] = tor_calloc(smartlist_len(v->known_flags), + sizeof(int)); if (smartlist_len(v->known_flags) > MAX_KNOWN_FLAGS_IN_VOTE) { log_warn(LD_BUG, "Somehow, a vote has %d entries in known_flags", smartlist_len(v->known_flags)); @@ -1618,7 +1434,7 @@ networkstatus_compute_consensus(smartlist_t *votes, } SMARTLIST_FOREACH_END(v); /* Named and Unnamed get treated specially */ - if (consensus_method >= 2) { + { SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) { uint64_t nf; if (named_flag[v_sl_idx]<0) @@ -1671,14 +1487,14 @@ networkstatus_compute_consensus(smartlist_t *votes, /* We need to know how many votes measure bandwidth. */ n_authorities_measuring_bandwidth = 0; - SMARTLIST_FOREACH(votes, networkstatus_t *, v, + SMARTLIST_FOREACH(votes, const networkstatus_t *, v, if (v->has_measured_bws) { ++n_authorities_measuring_bandwidth; } ); /* Now go through all the votes */ - flag_counts = tor_malloc(sizeof(int) * smartlist_len(flags)); + flag_counts = tor_calloc(smartlist_len(flags), sizeof(int)); while (1) { vote_routerstatus_t *rs; routerstatus_t rs_out; @@ -1713,6 +1529,7 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_clear(versions); num_bandwidths = 0; num_mbws = 0; + num_guardfraction_inputs = 0; /* Okay, go through all the entries for this digest. */ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) { @@ -1746,6 +1563,12 @@ networkstatus_compute_consensus(smartlist_t *votes, chosen_name = rs->status.nickname; } + /* Count guardfraction votes and note down the values. */ + if (rs->status.has_guardfraction) { + measured_guardfraction[num_guardfraction_inputs++] = + rs->status.guardfraction_percentage; + } + /* count bandwidths */ if (rs->has_measured_bw) measured_bws_kb[num_mbws++] = rs->measured_bw_kb; @@ -1787,10 +1610,7 @@ networkstatus_compute_consensus(smartlist_t *votes, strlcpy(rs_out.nickname, rs->status.nickname, sizeof(rs_out.nickname)); } - if (consensus_method == 1) { - is_named = chosen_named_idx >= 0 && - (!naming_conflict && flag_counts[chosen_named_idx]); - } else { + { const char *d = strmap_get_lc(name_to_id_map, rs_out.nickname); if (!d) { is_named = is_unnamed = 0; @@ -1807,7 +1627,7 @@ networkstatus_compute_consensus(smartlist_t *votes, if (!strcmp(fl, "Named")) { if (is_named) smartlist_add(chosen_flags, (char*)fl); - } else if (!strcmp(fl, "Unnamed") && consensus_method >= 2) { + } else if (!strcmp(fl, "Unnamed")) { if (is_unnamed) smartlist_add(chosen_flags, (char*)fl); } else { @@ -1827,7 +1647,7 @@ networkstatus_compute_consensus(smartlist_t *votes, /* Starting with consensus method 4 we do not list servers * that are not running in a consensus. See Proposal 138 */ - if (consensus_method >= 4 && !is_running) + if (!is_running) continue; /* Pick the version. */ @@ -1838,12 +1658,23 @@ networkstatus_compute_consensus(smartlist_t *votes, chosen_version = NULL; } + /* If it's a guard and we have enough guardfraction votes, + calculate its consensus guardfraction value. */ + if (is_guard && num_guardfraction_inputs > 2 && + consensus_method >= MIN_METHOD_FOR_GUARDFRACTION) { + rs_out.has_guardfraction = 1; + rs_out.guardfraction_percentage = median_uint32(measured_guardfraction, + num_guardfraction_inputs); + /* final value should be an integer percentage! */ + tor_assert(rs_out.guardfraction_percentage <= 100); + } + /* Pick a bandwidth */ - if (consensus_method >= 6 && num_mbws > 2) { + if (num_mbws > 2) { rs_out.has_bandwidth = 1; rs_out.bw_is_unmeasured = 0; rs_out.bandwidth_kb = median_uint32(measured_bws_kb, num_mbws); - } else if (consensus_method >= 5 && num_bandwidths > 0) { + } else if (num_bandwidths > 0) { rs_out.has_bandwidth = 1; rs_out.bw_is_unmeasured = 1; rs_out.bandwidth_kb = median_uint32(bandwidths_kb, num_bandwidths); @@ -1857,25 +1688,13 @@ networkstatus_compute_consensus(smartlist_t *votes, } /* Fix bug 2203: Do not count BadExit nodes as Exits for bw weights */ - if (consensus_method >= MIN_METHOD_TO_CUT_BADEXIT_WEIGHT) { - is_exit = is_exit && !is_bad_exit; - } + is_exit = is_exit && !is_bad_exit; - if (consensus_method >= MIN_METHOD_FOR_BW_WEIGHTS) { - if (rs_out.has_bandwidth) { - T += rs_out.bandwidth_kb; - if (is_exit && is_guard) - D += rs_out.bandwidth_kb; - else if (is_exit) - E += rs_out.bandwidth_kb; - else if (is_guard) - G += rs_out.bandwidth_kb; - else - M += rs_out.bandwidth_kb; - } else { - log_warn(LD_BUG, "Missing consensus bandwidth for router %s", - rs_out.nickname); - } + /* Update total bandwidth weights with the bandwidths of this router. */ + { + update_total_bandwidth_weights(&rs_out, + is_exit, is_guard, + &G, &M, &E, &D, &T); } /* Ok, we already picked a descriptor digest we want to list @@ -1892,7 +1711,7 @@ networkstatus_compute_consensus(smartlist_t *votes, * the policy that was most often listed in votes, again breaking * ties like in the previous case. */ - if (consensus_method >= 5) { + { /* Okay, go through all the votes for this router. We prepared * that list previously */ const char *chosen_exitsummary = NULL; @@ -1963,7 +1782,6 @@ networkstatus_compute_consensus(smartlist_t *votes, } if (flavor == FLAV_MICRODESC && - consensus_method >= MIN_METHOD_FOR_MANDATORY_MICRODESC && tor_digest256_is_zero(microdesc_digest)) { /* With no microdescriptor digest, we omit the entry entirely. */ continue; @@ -1995,11 +1813,21 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_add(chunks, tor_strdup("\n")); /* Now the weight line. */ if (rs_out.has_bandwidth) { + char *guardfraction_str = NULL; int unmeasured = rs_out.bw_is_unmeasured && consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW; - smartlist_add_asprintf(chunks, "w Bandwidth=%d%s\n", + + /* If we have guardfraction info, include it in the 'w' line. */ + if (rs_out.has_guardfraction) { + tor_asprintf(&guardfraction_str, + " GuardFraction=%u", rs_out.guardfraction_percentage); + } + smartlist_add_asprintf(chunks, "w Bandwidth=%d%s%s\n", rs_out.bandwidth_kb, - unmeasured?" Unmeasured=1":""); + unmeasured?" Unmeasured=1":"", + guardfraction_str ? guardfraction_str : ""); + + tor_free(guardfraction_str); } /* Now the exitpolicy summary line. */ @@ -2027,15 +1855,13 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_free(exitsummaries); tor_free(bandwidths_kb); tor_free(measured_bws_kb); + tor_free(measured_guardfraction); } - if (consensus_method >= MIN_METHOD_FOR_FOOTER) { - /* Starting with consensus method 9, we clearly mark the directory - * footer region */ - smartlist_add(chunks, tor_strdup("directory-footer\n")); - } + /* Mark the directory footer region */ + smartlist_add(chunks, tor_strdup("directory-footer\n")); - if (consensus_method >= MIN_METHOD_FOR_BW_WEIGHTS) { + { int64_t weight_scale = BW_WEIGHT_SCALE; char *bw_weight_param = NULL; @@ -2068,13 +1894,8 @@ networkstatus_compute_consensus(smartlist_t *votes, } } - if (consensus_method < 10) { - networkstatus_compute_bw_weights_v9(chunks, G, M, E, D, T, weight_scale); - added_weights = 1; - } else { - added_weights = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, - T, weight_scale); - } + added_weights = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, + T, weight_scale); } /* Add a signature. */ @@ -2115,7 +1936,7 @@ networkstatus_compute_consensus(smartlist_t *votes, } smartlist_add(chunks, signature); - if (legacy_id_key_digest && legacy_signing_key && consensus_method >= 3) { + if (legacy_id_key_digest && legacy_signing_key) { smartlist_add(chunks, tor_strdup("directory-signature ")); base16_encode(fingerprint, sizeof(fingerprint), legacy_id_key_digest, DIGEST_LEN); @@ -2151,7 +1972,7 @@ networkstatus_compute_consensus(smartlist_t *votes, goto done; } // Verify balancing parameters - if (consensus_method >= MIN_METHOD_FOR_BW_WEIGHTS && added_weights) { + if (added_weights) { networkstatus_verify_bw_weights(c, consensus_method); } networkstatus_vote_free(c); @@ -2161,6 +1982,7 @@ networkstatus_compute_consensus(smartlist_t *votes, tor_free(client_versions); tor_free(server_versions); + tor_free(packages); SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp)); smartlist_free(flags); SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); @@ -2169,6 +1991,78 @@ networkstatus_compute_consensus(smartlist_t *votes, return result; } +/** Given a list of networkstatus_t for each vote, return a newly allocated + * string containing the "package" lines for the vote. */ +STATIC char * +compute_consensus_package_lines(smartlist_t *votes) +{ + const int n_votes = smartlist_len(votes); + + /* This will be a map from "packagename version" strings to arrays + * of const char *, with the i'th member of the array corresponding to the + * package line from the i'th vote. + */ + strmap_t *package_status = strmap_new(); + + SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) { + if (! v->package_lines) + continue; + SMARTLIST_FOREACH_BEGIN(v->package_lines, const char *, line) { + if (! validate_recommended_package_line(line)) + continue; + + /* Skip 'cp' to the second space in the line. */ + const char *cp = strchr(line, ' '); + if (!cp) continue; + ++cp; + cp = strchr(cp, ' '); + if (!cp) continue; + + char *key = tor_strndup(line, cp - line); + + const char **status = strmap_get(package_status, key); + if (!status) { + status = tor_calloc(n_votes, sizeof(const char *)); + strmap_set(package_status, key, status); + } + status[v_sl_idx] = line; /* overwrite old value */ + tor_free(key); + } SMARTLIST_FOREACH_END(line); + } SMARTLIST_FOREACH_END(v); + + smartlist_t *entries = smartlist_new(); /* temporary */ + smartlist_t *result_list = smartlist_new(); /* output */ + STRMAP_FOREACH(package_status, key, const char **, values) { + int i, count=-1; + for (i = 0; i < n_votes; ++i) { + if (values[i]) + smartlist_add(entries, (void*) values[i]); + } + smartlist_sort_strings(entries); + int n_voting_for_entry = smartlist_len(entries); + const char *most_frequent = + smartlist_get_most_frequent_string_(entries, &count); + + if (n_voting_for_entry >= 3 && count > n_voting_for_entry / 2) { + smartlist_add_asprintf(result_list, "package %s\n", most_frequent); + } + + smartlist_clear(entries); + + } STRMAP_FOREACH_END; + + smartlist_sort_strings(result_list); + + char *result = smartlist_join_strings(result_list, "", 0, NULL); + + SMARTLIST_FOREACH(result_list, char *, cp, tor_free(cp)); + smartlist_free(result_list); + smartlist_free(entries); + strmap_free(package_status, tor_free_); + + return result; +} + /** Given a consensus vote <b>target</b> and a set of detached signatures in * <b>sigs</b> that correspond to the same consensus, check whether there are * any new signatures in <b>src_voter_list</b> that should be added to @@ -2275,8 +2169,11 @@ networkstatus_add_detached_signatures(networkstatus_t *target, if (!sig->good_signature && !sig->bad_signature) { cert = authority_cert_get_by_digests(sig->identity_digest, sig->signing_key_digest); - if (cert) - networkstatus_check_document_signature(target, sig, cert); + if (cert) { + /* Not checking the return value here, since we are going to look + * at the status of sig->good_signature in a moment. */ + (void) networkstatus_check_document_signature(target, sig, cert); + } } /* If this signature is good, or we don't have any signature yet, @@ -2533,12 +2430,13 @@ dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out) timing_out->dist_delay = options->V3AuthDistDelay; } -/** Return the start of the next interval of size <b>interval</b> (in seconds) - * after <b>now</b>. Midnight always starts a fresh interval, and if the last - * interval of a day would be truncated to less than half its size, it is - * rolled into the previous interval. */ +/** Return the start of the next interval of size <b>interval</b> (in + * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always + * starts a fresh interval, and if the last interval of a day would be + * truncated to less than half its size, it is rolled into the + * previous interval. */ time_t -dirvote_get_start_of_next_interval(time_t now, int interval) +dirvote_get_start_of_next_interval(time_t now, int interval, int offset) { struct tm tm; time_t midnight_today=0; @@ -2566,6 +2464,10 @@ dirvote_get_start_of_next_interval(time_t now, int interval) if (next + interval/2 > midnight_tomorrow) next = midnight_tomorrow; + next += offset; + if (next - interval > now) + next -= interval; + return next; } @@ -2629,8 +2531,10 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now) vote_delay = dist_delay = interval / 4; start = voting_schedule.interval_starts = - dirvote_get_start_of_next_interval(now,interval); - end = dirvote_get_start_of_next_interval(start+1, interval); + dirvote_get_start_of_next_interval(now,interval, + options->TestingV3AuthVotingStartOffset); + end = dirvote_get_start_of_next_interval(start+1, interval, + options->TestingV3AuthVotingStartOffset); tor_assert(end > start); @@ -3009,7 +2913,7 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) goto discard; } else if (v->vote->published < vote->published) { log_notice(LD_DIR, "Replacing an older pending vote from this " - "directory."); + "directory (%s)", vi->address); cached_dir_decref(v->vote_body); networkstatus_vote_free(v->vote); v->vote_body = new_cached_dir(tor_strndup(vote_body, @@ -3136,7 +3040,7 @@ dirvote_compute_consensuses(void) }); votefile = get_datadir_fname("v3-status-votes"); - write_chunks_to_file(votefile, votestrings, 0); + write_chunks_to_file(votefile, votestrings, 0, 0); tor_free(votefile); SMARTLIST_FOREACH(votestrings, sized_chunk_t *, c, tor_free(c)); smartlist_free(votestrings); @@ -3581,12 +3485,18 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method) tor_free(p6); } + if (consensus_method >= MIN_METHOD_FOR_ID_HASH_IN_MD) { + char idbuf[BASE64_DIGEST_LEN+1]; + digest_to_base64(idbuf, ri->cache_info.identity_digest); + smartlist_add_asprintf(chunks, "id rsa1024 %s\n", idbuf); + } + output = smartlist_join_strings(chunks, "", 0, NULL); { smartlist_t *lst = microdescs_parse_from_string(output, - output+strlen(output), 0, - SAVED_NOWHERE); + output+strlen(output), 0, + SAVED_NOWHERE, NULL); if (smartlist_len(lst) != 1) { log_warn(LD_DIR, "We generated a microdescriptor we couldn't parse."); SMARTLIST_FOREACH(lst, microdesc_t *, md, microdesc_free(md)); @@ -3647,10 +3557,11 @@ static const struct consensus_method_range_t { int low; int high; } microdesc_consensus_methods[] = { - {MIN_METHOD_FOR_MICRODESC, MIN_METHOD_FOR_A_LINES - 1}, + {MIN_SUPPORTED_CONSENSUS_METHOD, MIN_METHOD_FOR_A_LINES - 1}, {MIN_METHOD_FOR_A_LINES, MIN_METHOD_FOR_P6_LINES - 1}, {MIN_METHOD_FOR_P6_LINES, MIN_METHOD_FOR_NTOR_KEY - 1}, - {MIN_METHOD_FOR_NTOR_KEY, MAX_SUPPORTED_CONSENSUS_METHOD}, + {MIN_METHOD_FOR_NTOR_KEY, MIN_METHOD_FOR_ID_HASH_IN_MD - 1}, + {MIN_METHOD_FOR_ID_HASH_IN_MD, MAX_SUPPORTED_CONSENSUS_METHOD}, {-1, -1} }; diff --git a/src/or/dirvote.h b/src/or/dirvote.h index b236452122..542563b708 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,34 +12,50 @@ #ifndef TOR_DIRVOTE_H #define TOR_DIRVOTE_H -/** Lowest allowable value for VoteSeconds. */ -#define MIN_VOTE_SECONDS 20 -/** Lowest allowable value for DistSeconds. */ -#define MIN_DIST_SECONDS 20 -/** Smallest allowable voting interval. */ -#define MIN_VOTE_INTERVAL 300 +#include "testsupport.h" -/** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 17 +/* + * Ideally, assuming synced clocks, we should only need 1 second for each of: + * - Vote + * - Distribute + * - Consensus Publication + * As we can gather descriptors continuously. + * (Could we even go as far as publishing the previous consensus, + * in the same second that we vote for the next one?) + * But we're not there yet: these are the lowest working values at this time. + */ -/** Lowest consensus method that contains a 'directory-footer' marker */ -#define MIN_METHOD_FOR_FOOTER 9 - -/** Lowest consensus method that contains bandwidth weights */ -#define MIN_METHOD_FOR_BW_WEIGHTS 9 - -/** Lowest consensus method that contains consensus params */ -#define MIN_METHOD_FOR_PARAMS 7 +/** Lowest allowable value for VoteSeconds. */ +#define MIN_VOTE_SECONDS 2 +/** Lowest allowable value for VoteSeconds when TestingTorNetwork is 1 */ +#define MIN_VOTE_SECONDS_TESTING 2 -/** Lowest consensus method that generates microdescriptors */ -#define MIN_METHOD_FOR_MICRODESC 8 +/** Lowest allowable value for DistSeconds. */ +#define MIN_DIST_SECONDS 2 +/** Lowest allowable value for DistSeconds when TestingTorNetwork is 1 */ +#define MIN_DIST_SECONDS_TESTING 2 -/** Lowest consensus method that doesn't count bad exits as exits for weight */ -#define MIN_METHOD_TO_CUT_BADEXIT_WEIGHT 11 +/** Lowest allowable voting interval. */ +#define MIN_VOTE_INTERVAL 300 +/** Lowest allowable voting interval when TestingTorNetwork is 1: + * Voting Interval can be: + * 10, 12, 15, 18, 20, 24, 25, 30, 36, 40, 45, 50, 60, ... + * Testing Initial Voting Interval can be: + * 5, 6, 8, 9, or any of the possible values for Voting Interval, + * as they both need to evenly divide 30 minutes. + * If clock desynchronisation is an issue, use an interval of at least: + * 18 * drift in seconds, to allow for a clock slop factor */ +#define MIN_VOTE_INTERVAL_TESTING \ + (((MIN_VOTE_SECONDS_TESTING)+(MIN_DIST_SECONDS_TESTING)+1)*2) + +#define MIN_VOTE_INTERVAL_TESTING_INITIAL \ + ((MIN_VOTE_SECONDS_TESTING)+(MIN_DIST_SECONDS_TESTING)+1) + +/** The lowest consensus method that we currently support. */ +#define MIN_SUPPORTED_CONSENSUS_METHOD 13 -/** Lowest consensus method that ensures a majority of authorities voted - * for a param. */ -#define MIN_METHOD_FOR_MAJORITY_PARAMS 12 +/** The highest consensus method that we currently support. */ +#define MAX_SUPPORTED_CONSENSUS_METHOD 20 /** Lowest consensus method where microdesc consensuses omit any entry * with no microdesc. */ @@ -59,8 +75,20 @@ * Unmeasured=1 flag for unmeasured bandwidths */ #define MIN_METHOD_TO_CLIP_UNMEASURED_BW 17 +/** Lowest consensus method where authorities may include an "id" line in + * microdescriptors. */ +#define MIN_METHOD_FOR_ID_HASH_IN_MD 18 + +/** Lowest consensus method where we include "package" lines*/ +#define MIN_METHOD_FOR_PACKAGE_LINES 19 + +/** Lowest consensus method where authorities may include + * GuardFraction information in microdescriptors. */ +#define MIN_METHOD_FOR_GUARDFRACTION 20 + /** Default bandwidth to clip unmeasured bandwidths to using method >= - * MIN_METHOD_TO_CLIP_UNMEASURED_BW */ + * MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not + * get confused with the above macros.) */ #define DEFAULT_MAX_UNMEASURED_BW_KB 20 void dirvote_free_all(void); @@ -86,7 +114,9 @@ authority_cert_t *authority_cert_dup(authority_cert_t *cert); /* vote scheduling */ void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out); -time_t dirvote_get_start_of_next_interval(time_t now, int interval); +time_t dirvote_get_start_of_next_interval(time_t now, + int interval, + int offset); void dirvote_recalculate_timing(const or_options_t *options, time_t now); void dirvote_act(const or_options_t *options, time_t now); @@ -108,8 +138,8 @@ 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, int vote_on_hsdirs); + int listbadexits, + int vote_on_hsdirs); networkstatus_t * dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, authority_cert_t *cert); @@ -134,10 +164,11 @@ document_signature_t *voter_get_sig_by_algorithm( digest_algorithm_t alg); #ifdef DIRVOTE_PRIVATE -char *format_networkstatus_vote(crypto_pk_t *private_key, +STATIC char *format_networkstatus_vote(crypto_pk_t *private_key, networkstatus_t *v3_ns); -char *dirvote_compute_params(smartlist_t *votes, int method, +STATIC char *dirvote_compute_params(smartlist_t *votes, int method, int total_authorities); +STATIC char *compute_consensus_package_lines(smartlist_t *votes); #endif #endif diff --git a/src/or/dns.c b/src/or/dns.c index 8b6e3b0543..cc4a169422 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -24,6 +24,7 @@ #include "relay.h" #include "router.h" #include "ht.h" +#include "../common/sandbox.h" #ifdef HAVE_EVENT2_DNS_H #include <event2/event.h> #include <event2/dns.h> @@ -238,13 +239,13 @@ cached_resolves_eq(cached_resolve_t *a, cached_resolve_t *b) static INLINE unsigned int cached_resolve_hash(cached_resolve_t *a) { - return ht_string_hash(a->address); + return (unsigned) siphash24g((const uint8_t*)a->address, strlen(a->address)); } HT_PROTOTYPE(cache_map, cached_resolve_t, node, cached_resolve_hash, cached_resolves_eq) -HT_GENERATE(cache_map, cached_resolve_t, node, cached_resolve_hash, - cached_resolves_eq, 0.6, malloc, realloc, free) +HT_GENERATE2(cache_map, cached_resolve_t, node, cached_resolve_hash, + cached_resolves_eq, 0.6, tor_reallocarray_, tor_free_) /** Initialize the DNS cache. */ static void @@ -1448,13 +1449,14 @@ configure_nameservers(int force) const or_options_t *options; const char *conf_fname; struct stat st; - int r; + int r, flags; options = get_options(); conf_fname = options->ServerDNSResolvConfFile; #ifndef _WIN32 if (!conf_fname) conf_fname = "/etc/resolv.conf"; #endif + flags = DNS_OPTIONS_ALL; if (!the_evdns_base) { if (!(the_evdns_base = evdns_base_new(tor_libevent_get_base(), 0))) { @@ -1482,7 +1484,8 @@ configure_nameservers(int force) evdns_set_log_fn(evdns_log_cb); if (conf_fname) { - if (stat(conf_fname, &st)) { + log_debug(LD_FS, "stat()ing %s", conf_fname); + if (stat(sandbox_intern_string(conf_fname), &st)) { log_warn(LD_EXIT, "Unable to stat resolver configuration in '%s': %s", conf_fname, strerror(errno)); goto err; @@ -1496,9 +1499,17 @@ configure_nameservers(int force) evdns_base_search_clear(the_evdns_base); evdns_base_clear_nameservers_and_suspend(the_evdns_base); } +#if defined(DNS_OPTION_HOSTSFILE) && defined(USE_LIBSECCOMP) + if (flags & DNS_OPTION_HOSTSFILE) { + flags ^= DNS_OPTION_HOSTSFILE; + log_debug(LD_FS, "Loading /etc/hosts"); + evdns_base_load_hosts(the_evdns_base, + sandbox_intern_string("/etc/hosts")); + } +#endif log_info(LD_EXIT, "Parsing resolver configuration in '%s'", conf_fname); - if ((r = evdns_base_resolv_conf_parse(the_evdns_base, - DNS_OPTIONS_ALL, conf_fname))) { + if ((r = evdns_base_resolv_conf_parse(the_evdns_base, flags, + sandbox_intern_string(conf_fname)))) { log_warn(LD_EXIT, "Unable to parse '%s', or no nameservers in '%s' (%d)", conf_fname, conf_fname, r); goto err; @@ -2167,7 +2178,7 @@ static void assert_cache_ok_(void) { cached_resolve_t **resolve; - int bad_rep = _cache_map_HT_REP_IS_BAD(&cache_root); + int bad_rep = HT_REP_IS_BAD_(cache_map, &cache_root); if (bad_rep) { log_err(LD_BUG, "Bad rep type %d on dns cache hash table", bad_rep); tor_assert(!bad_rep); diff --git a/src/or/dns.h b/src/or/dns.h index 022cd4ac63..b13ab0f890 100644 --- a/src/or/dns.h +++ b/src/or/dns.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index ebff7b524c..f7710908bd 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -35,7 +35,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) entry_connection_t *entry_conn; edge_connection_t *conn; int i = 0; - struct evdns_server_question *q = NULL; + struct evdns_server_question *q = NULL, *supported_q = NULL; struct sockaddr_storage addr; struct sockaddr *sa; int addrlen; @@ -87,31 +87,37 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) for (i = 0; i < req->nquestions; ++i) { if (req->questions[i]->dns_question_class != EVDNS_CLASS_INET) continue; + if (! q) + q = req->questions[i]; switch (req->questions[i]->type) { case EVDNS_TYPE_A: case EVDNS_TYPE_AAAA: case EVDNS_TYPE_PTR: - q = req->questions[i]; + /* We always pick the first one of these questions, if there is + one. */ + if (! supported_q) + supported_q = q; + break; default: break; } } + if (supported_q) + q = supported_q; if (!q) { log_info(LD_APP, "None of the questions we got were ones we're willing " "to support. Sending NOTIMPL."); evdns_server_request_respond(req, DNS_ERR_NOTIMPL); return; } - if (q->type != EVDNS_TYPE_A && q->type != EVDNS_TYPE_AAAA) { - tor_assert(q->type == EVDNS_TYPE_PTR); - } /* Make sure the name isn't too long: This should be impossible, I think. */ if (err == DNS_ERR_NONE && strlen(q->name) > MAX_SOCKS_ADDR_LEN-1) err = DNS_ERR_FORMAT; - if (err != DNS_ERR_NONE) { - /* We got an error? Then send back an answer immediately; we're done. */ + if (err != DNS_ERR_NONE || !supported_q) { + /* We got an error? There's no question we're willing to answer? Then + * send back an answer immediately; we're done. */ evdns_server_request_respond(req, err); return; } @@ -126,18 +132,31 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) TO_CONN(conn)->port = port; TO_CONN(conn)->address = tor_dup_addr(&tor_addr); - if (q->type == EVDNS_TYPE_A || q->type == EVDNS_TYPE_AAAA) + if (q->type == EVDNS_TYPE_A || q->type == EVDNS_TYPE_AAAA || + q->type == EVDNS_QTYPE_ALL) { entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE; - else + } else { + tor_assert(q->type == EVDNS_TYPE_PTR); entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; + } + + if (q->type == EVDNS_TYPE_A || q->type == EVDNS_QTYPE_ALL) { + entry_conn->entry_cfg.ipv4_traffic = 1; + entry_conn->entry_cfg.ipv6_traffic = 0; + entry_conn->entry_cfg.prefer_ipv6 = 0; + } else if (q->type == EVDNS_TYPE_AAAA) { + entry_conn->entry_cfg.ipv4_traffic = 0; + entry_conn->entry_cfg.ipv6_traffic = 1; + entry_conn->entry_cfg.prefer_ipv6 = 1; + } strlcpy(entry_conn->socks_request->address, q->name, sizeof(entry_conn->socks_request->address)); entry_conn->socks_request->listener_type = listener->base_.type; entry_conn->dns_server_request = req; - entry_conn->isolation_flags = listener->isolation_flags; - entry_conn->session_group = listener->session_group; + entry_conn->entry_cfg.isolation_flags = listener->entry_cfg.isolation_flags; + entry_conn->entry_cfg.session_group = listener->entry_cfg.session_group; entry_conn->nym_epoch = get_signewnym_epoch(); if (connection_add(ENTRY_TO_CONN(entry_conn)) < 0) { @@ -213,9 +232,9 @@ dnsserv_launch_request(const char *name, int reverse, entry_conn->socks_request->listener_type = CONN_TYPE_CONTROL_LISTENER; entry_conn->original_dest_address = tor_strdup(name); - entry_conn->session_group = SESSION_GROUP_CONTROL_RESOLVE; + entry_conn->entry_cfg.session_group = SESSION_GROUP_CONTROL_RESOLVE; entry_conn->nym_epoch = get_signewnym_epoch(); - entry_conn->isolation_flags = ISO_DEFAULT; + entry_conn->entry_cfg.isolation_flags = ISO_DEFAULT; if (connection_add(TO_CONN(conn))<0) { log_warn(LD_APP, "Couldn't register dummy connection for RESOLVE request"); diff --git a/src/or/dnsserv.h b/src/or/dnsserv.h index 687a77e59e..09ad5d7759 100644 --- a/src/or/dnsserv.h +++ b/src/or/dnsserv.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 484b88dbf8..30108b6041 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,7 +12,10 @@ * circumvention). **/ +#define ENTRYNODES_PRIVATE + #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitstats.h" #include "config.h" @@ -54,6 +57,10 @@ typedef struct { /** When should we next try to fetch a descriptor for this bridge? */ download_status_t fetch_status; + + /** A smartlist of k=v values to be passed to the SOCKS proxy, if + transports are used for this bridge. */ + smartlist_t *socks_args; } bridge_info_t; /** A list of our chosen entry guards. */ @@ -65,7 +72,9 @@ static int entry_guards_dirty = 0; static void bridge_free(bridge_info_t *bridge); static const node_t *choose_random_entry_impl(cpath_build_state_t *state, int for_directory, - dirinfo_type_t dirtype); + dirinfo_type_t dirtype, + int *n_options_out); +static int num_bridges_usable(void); /** Return the list of entry guards, creating it if necessary. */ const smartlist_t * @@ -147,21 +156,41 @@ entry_guard_set_status(entry_guard_t *e, const node_t *node, /** Return true iff enough time has passed since we last tried to connect * to the unreachable guard <b>e</b> that we're willing to try again. */ -static int -entry_is_time_to_retry(entry_guard_t *e, time_t now) +STATIC int +entry_is_time_to_retry(const entry_guard_t *e, time_t now) { - long diff; + struct guard_retry_period_s { + time_t period_duration; + time_t interval_during_period; + }; + + struct guard_retry_period_s periods[] = { + { 6*60*60, 60*60 }, /* For first 6 hrs., retry hourly; */ + { 3*24*60*60, 4*60*60 }, /* Then retry every 4 hrs. until the + 3-day mark; */ + { 7*24*60*60, 18*60*60 }, /* After 3 days, retry every 18 hours until + 1 week mark. */ + { TIME_MAX, 36*60*60 } /* After 1 week, retry every 36 hours. */ + }; + + time_t ith_deadline_for_retry; + time_t unreachable_for; + unsigned i; + if (e->last_attempted < e->unreachable_since) return 1; - diff = now - e->unreachable_since; - if (diff < 6*60*60) - return now > (e->last_attempted + 60*60); - else if (diff < 3*24*60*60) - return now > (e->last_attempted + 4*60*60); - else if (diff < 7*24*60*60) - return now > (e->last_attempted + 18*60*60); - else - return now > (e->last_attempted + 36*60*60); + + unreachable_for = now - e->unreachable_since; + + for (i = 0; i < ARRAY_LENGTH(periods); i++) { + if (unreachable_for <= periods[i].period_duration) { + ith_deadline_for_retry = e->last_attempted + + periods[i].interval_during_period; + + return (now > ith_deadline_for_retry); + } + } + return 0; } /** Return the node corresponding to <b>e</b>, if <b>e</b> is @@ -181,12 +210,17 @@ entry_is_time_to_retry(entry_guard_t *e, time_t now) * If need_descriptor is true, only return the node if we currently have * a descriptor (routerinfo or microdesc) for it. */ -static INLINE const node_t * -entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, - int assume_reachable, int need_descriptor, const char **msg) +STATIC const node_t * +entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags, + const char **msg) { const node_t *node; const or_options_t *options = get_options(); + int need_uptime = (flags & ENTRY_NEED_UPTIME) != 0; + int need_capacity = (flags & ENTRY_NEED_CAPACITY) != 0; + const int assume_reachable = (flags & ENTRY_ASSUME_REACHABLE) != 0; + const int need_descriptor = (flags & ENTRY_NEED_DESCRIPTOR) != 0; + tor_assert(msg); if (e->path_bias_disabled) { @@ -248,12 +282,18 @@ num_live_entry_guards(int for_directory) { int n = 0; const char *msg; + /* Set the entry node attributes we are interested in. */ + entry_is_live_flags_t entry_flags = ENTRY_NEED_CAPACITY; + if (!for_directory) { + entry_flags |= ENTRY_NEED_DESCRIPTOR; + } + if (! entry_guards) return 0; SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { if (for_directory && !entry->is_dir_cache) continue; - if (entry_is_live(entry, 0, 1, 0, !for_directory, &msg)) + if (entry_is_live(entry, entry_flags, &msg)) ++n; } SMARTLIST_FOREACH_END(entry); return n; @@ -282,7 +322,7 @@ log_entry_guards(int severity) SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { const char *msg = NULL; - if (entry_is_live(e, 0, 1, 0, 0, &msg)) + if (entry_is_live(e, ENTRY_NEED_CAPACITY, &msg)) smartlist_add_asprintf(elements, "%s [%s] (up %s)", e->nickname, hex_str(e->identity, DIGEST_LEN), @@ -343,7 +383,7 @@ 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 const node_t * +STATIC const node_t * add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, int for_discovery, int for_directory) { @@ -359,7 +399,7 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, entry->can_retry = 1; } entry->is_dir_cache = node->rs && - node->rs->version_supports_microdesc_cache; + node->rs->version_supports_microdesc_cache; if (get_options()->UseBridges && node_is_a_configured_bridge(node)) entry->is_dir_cache = 1; return NULL; @@ -371,7 +411,7 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, } else { const routerstatus_t *rs; rs = router_pick_directory_server(MICRODESC_DIRINFO|V3_DIRINFO, - PDS_PREFER_TUNNELED_DIR_CONNS_|PDS_FOR_GUARD); + PDS_FOR_GUARD); if (!rs) return NULL; node = node_get_by_id(rs->identity_digest); @@ -392,8 +432,8 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, node_describe(node)); strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); memcpy(entry->identity, node->identity, DIGEST_LEN); - entry->is_dir_cache = node_is_dir(node) && - node->rs && node->rs->version_supports_microdesc_cache; + entry->is_dir_cache = node_is_dir(node) && node->rs && + node->rs->version_supports_microdesc_cache; if (get_options()->UseBridges && node_is_a_configured_bridge(node)) entry->is_dir_cache = 1; @@ -430,7 +470,7 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, /** Choose how many entry guards or directory guards we'll use. If * <b>for_directory</b> is true, we return how many directory guards to * use; else we return how many entry guards to use. */ -static int +STATIC int decide_num_guards(const or_options_t *options, int for_directory) { if (for_directory) { @@ -605,6 +645,25 @@ remove_dead_entry_guards(time_t now) return changed ? 1 : 0; } +/** Remove all currently listed entry guards. So new ones will be chosen. */ +void +remove_all_entry_guards(void) +{ + char dbuf[HEX_DIGEST_LEN+1]; + + while (smartlist_len(entry_guards)) { + entry_guard_t *entry = smartlist_get(entry_guards, 0); + base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); + log_info(LD_CIRC, "Entry guard '%s' (%s) has been dropped.", + entry->nickname, dbuf); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del(entry_guards, 0); + } + log_entry_guards(LOG_INFO); + entry_guards_changed(); +} + /** A new directory or router-status has arrived; update the down/listed * status of the entry guards. * @@ -650,7 +709,7 @@ entry_guards_compute_status(const 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 = ""; - const node_t *r = entry_is_live(entry, 0, 1, 0, 0, &live_msg); + const node_t *r = entry_is_live(entry, ENTRY_NEED_CAPACITY, &live_msg); log_info(LD_CIRC, "Summary: Entry %s [%s] is %s, %s%s%s, and %s%s.", entry->nickname, hex_str(entry->identity, DIGEST_LEN), @@ -768,7 +827,9 @@ entry_guard_register_connect_status(const char *digest, int succeeded, break; if (e->made_contact) { const char *msg; - const node_t *r = entry_is_live(e, 0, 1, 1, 0, &msg); + const node_t *r = entry_is_live(e, + ENTRY_NEED_CAPACITY | ENTRY_ASSUME_REACHABLE, + &msg); if (r && e->unreachable_since) { refuse_conn = 1; e->can_retry = 1; @@ -821,7 +882,7 @@ update_node_guard_status(void) /** Adjust the entry guards list so that it only contains entries from * EntryNodes, adding new entries from EntryNodes to the list as needed. */ -static void +STATIC void entry_guards_set_from_config(const or_options_t *options) { smartlist_t *entry_nodes, *worse_entry_nodes, *entry_fps; @@ -942,7 +1003,8 @@ node_understands_microdescriptors(const node_t *node) } /** Return true iff <b>node</b> is able to answer directory questions - * of type <b>dirinfo</b>. */ + * of type <b>dirinfo</b>. Always returns true if <b>dirinfo</b> is + * NO_DIRINFO (zero). */ static int node_can_handle_dirinfo(const node_t *node, dirinfo_type_t dirinfo) { @@ -964,13 +1026,13 @@ node_can_handle_dirinfo(const node_t *node, dirinfo_type_t dirinfo) * <b>state</b> is non-NULL, this is for a specific circuit -- * 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). If <b>dirinfo</b> is not NO_DIRINFO, then - * only select from nodes that know how to answer directory questions + * guard (likely a bridge). If <b>dirinfo</b> is not NO_DIRINFO (zero), + * then only select from nodes that know how to answer directory questions * of that type. */ const node_t * choose_random_entry(cpath_build_state_t *state) { - return choose_random_entry_impl(state, 0, 0); + return choose_random_entry_impl(state, 0, NO_DIRINFO, NULL); } /** Pick a live (up and listed) directory guard from entry_guards for @@ -978,47 +1040,64 @@ choose_random_entry(cpath_build_state_t *state) const node_t * choose_random_dirguard(dirinfo_type_t type) { - return choose_random_entry_impl(NULL, 1, type); + return choose_random_entry_impl(NULL, 1, type, NULL); } -/** Helper for choose_random{entry,dirguard}. */ -static const node_t * -choose_random_entry_impl(cpath_build_state_t *state, int for_directory, - dirinfo_type_t dirinfo_type) +/** Filter <b>all_entry_guards</b> for usable entry guards and put them + * in <b>live_entry_guards</b>. We filter based on whether the node is + * currently alive, and on whether it satisfies the restrictions + * imposed by the other arguments of this function. + * + * We don't place more guards than NumEntryGuards in <b>live_entry_guards</b>. + * + * If <b>chosen_exit</b> is set, it contains the exit node of this + * circuit. Make sure to not use it or its family as an entry guard. + * + * If <b>need_uptime</b> is set, we are looking for a stable entry guard. + * if <b>need_capacity</b> is set, we are looking for a fast entry guard. + * + * The rest of the arguments are the same as in choose_random_entry_impl(). + * + * Return 1 if we should choose a guard right away. Return 0 if we + * should try to add more nodes to our list before deciding on a + * guard. + */ +STATIC int +populate_live_entry_guards(smartlist_t *live_entry_guards, + const smartlist_t *all_entry_guards, + const node_t *chosen_exit, + dirinfo_type_t dirinfo_type, + int for_directory, + int need_uptime, int need_capacity) { const or_options_t *options = get_options(); - smartlist_t *live_entry_guards = smartlist_new(); - smartlist_t *exit_family = smartlist_new(); - 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; - int need_descriptor = !for_directory; const int num_needed = decide_num_guards(options, for_directory); + smartlist_t *exit_family = smartlist_new(); + int retval = 0; + entry_is_live_flags_t entry_flags = 0; - if (chosen_exit) { - nodelist_add_node_and_family(exit_family, chosen_exit); - consider_exit_family = 1; + { /* Set the flags we want our entry node to have */ + if (need_uptime) { + entry_flags |= ENTRY_NEED_UPTIME; + } + if (need_capacity) { + entry_flags |= ENTRY_NEED_CAPACITY; + } + if (!for_directory) { + entry_flags |= ENTRY_NEED_DESCRIPTOR; + } } - if (!entry_guards) - entry_guards = smartlist_new(); + tor_assert(all_entry_guards); - if (should_add_entry_nodes) - entry_guards_set_from_config(options); - - if (!entry_list_is_constrained(options) && - smartlist_len(entry_guards) < num_needed) - pick_entry_guards(options, for_directory); + if (chosen_exit) { + nodelist_add_node_and_family(exit_family, chosen_exit); + } - retry: - smartlist_clear(live_entry_guards); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { + SMARTLIST_FOREACH_BEGIN(all_entry_guards, const entry_guard_t *, entry) { const char *msg; - node = entry_is_live(entry, need_uptime, need_capacity, 0, - need_descriptor, &msg); + node = entry_is_live(entry, entry_flags, &msg); if (!node) continue; /* down, no point */ if (for_directory) { @@ -1027,39 +1106,96 @@ choose_random_entry_impl(cpath_build_state_t *state, int for_directory, } if (node == chosen_exit) continue; /* don't pick the same node for entry and exit */ - if (consider_exit_family && smartlist_contains(exit_family, node)) + if (smartlist_contains(exit_family, node)) continue; /* avoid relays that are family members of our exit */ if (dirinfo_type != NO_DIRINFO && !node_can_handle_dirinfo(node, dirinfo_type)) continue; /* this node won't be able to answer our dir questions */ -#if 0 /* since EntryNodes is always strict now, this clause is moot */ - if (options->EntryNodes && - !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 */ - if (options->StrictNodes) { - /* in theory this case should never happen, since - * entry_guards_set_from_config() drops unwanted relays */ - tor_fragile_assert(); - } else { - log_info(LD_CIRC, - "No relays from EntryNodes available. Using others."); - } - } -#endif 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 * the second new one, and now we've expanded our entry * guard list without needing to. */ - goto choose_and_finish; + retval = 1; + goto done; + } + if (smartlist_len(live_entry_guards) >= num_needed) { + retval = 1; + goto done; /* We picked enough entry guards. Done! */ } - if (smartlist_len(live_entry_guards) >= num_needed) - goto choose_and_finish; /* we have enough */ } SMARTLIST_FOREACH_END(entry); + done: + smartlist_free(exit_family); + + return retval; +} + +/** Pick a node to be used as the entry guard of a circuit. + * + * If <b>state</b> is set, it contains the information we know about + * the upcoming circuit. + * + * If <b>for_directory</b> is set, we are looking for a directory guard. + * + * <b>dirinfo_type</b> contains the kind of directory information we + * are looking for in our node, or NO_DIRINFO (zero) if we are not + * looking for any particular directory information (when set to + * NO_DIRINFO, the <b>dirinfo_type</b> filter is ignored). + * + * If <b>n_options_out</b> is set, we set it to the number of + * candidate guard nodes we had before picking a specific guard node. + * + * On success, return the node that should be used as the entry guard + * of the circuit. Return NULL if no such node could be found. + * + * Helper for choose_random{entry,dirguard}. +*/ +static const node_t * +choose_random_entry_impl(cpath_build_state_t *state, int for_directory, + dirinfo_type_t dirinfo_type, int *n_options_out) +{ + const or_options_t *options = get_options(); + smartlist_t *live_entry_guards = smartlist_new(); + 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 = 0; + const int num_needed = decide_num_guards(options, for_directory); + int retval = 0; + + if (n_options_out) + *n_options_out = 0; + + if (!entry_guards) + entry_guards = smartlist_new(); + + if (should_add_entry_nodes) + entry_guards_set_from_config(options); + + if (!entry_list_is_constrained(options) && + smartlist_len(entry_guards) < num_needed) + pick_entry_guards(options, for_directory); + + retry: + smartlist_clear(live_entry_guards); + + /* Populate the list of live entry guards so that we pick one of + them. */ + retval = populate_live_entry_guards(live_entry_guards, + entry_guards, + chosen_exit, + dirinfo_type, + for_directory, + need_uptime, need_capacity); + + if (retval == 1) { /* We should choose a guard right now. */ + goto choose_and_finish; + } + if (entry_list_is_constrained(options)) { /* If we prefer the entry nodes we've got, and we have at least * one choice, that's great. Use it. */ @@ -1098,18 +1234,7 @@ choose_random_entry_impl(cpath_build_state_t *state, int for_directory, need_capacity = 0; goto retry; } -#if 0 - /* Removing this retry logic: if we only allow one exit, and it is in the - same family as all our entries, then we are just plain not going to win - here. */ - 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. */ - consider_exit_family = 0; - goto retry; - } -#endif + /* live_entry_guards may be empty below. Oh well, we tried. */ } @@ -1124,8 +1249,9 @@ choose_random_entry_impl(cpath_build_state_t *state, int for_directory, * *double*-weight our guard selection. */ node = smartlist_choose(live_entry_guards); } + if (n_options_out) + *n_options_out = smartlist_len(live_entry_guards); smartlist_free(live_entry_guards); - smartlist_free(exit_family); return node; } @@ -1193,7 +1319,7 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) "EntryGuardDownSince/UnlistedSince without EntryGuard"); break; } - if (parse_iso_time(line->value, &when)<0) { + if (parse_iso_time_(line->value, &when, 0)<0) { *msg = tor_strdup("Unable to parse entry nodes: " "Bad time in EntryGuardDownSince/UnlistedSince"); break; @@ -1397,6 +1523,13 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) return *msg ? -1 : 0; } +/** How long will we let a change in our guard nodes stay un-saved + * when we are trying to avoid disk writes? */ +#define SLOW_GUARD_STATE_FLUSH_TIME 600 +/** How long will we let a change in our guard nodes stay un-saved + * when we are not trying to avoid disk writes? */ +#define FAST_GUARD_STATE_FLUSH_TIME 30 + /** Our list of entry guards has changed, or some element of one * of our entry guards has changed. Write the changes to disk within * the next few minutes. @@ -1407,8 +1540,12 @@ entry_guards_changed(void) time_t when; entry_guards_dirty = 1; + if (get_options()->AvoidDiskWrites) + when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME; + else + when = time(NULL) + FAST_GUARD_STATE_FLUSH_TIME; + /* or_state_save() will call entry_guards_update_state(). */ - when = get_options()->AvoidDiskWrites ? time(NULL) + 3600 : time(NULL)+600; or_state_mark_dirty(get_or_state(), when); } @@ -1529,6 +1666,9 @@ getinfo_helper_entry_guards(control_connection_t *conn, } else if (e->bad_since) { when = e->bad_since; status = "unusable"; + } else if (e->unreachable_since) { + when = e->unreachable_since; + status = "down"; } else { status = "up"; } @@ -1557,6 +1697,63 @@ getinfo_helper_entry_guards(control_connection_t *conn, return 0; } +/** Return 0 if we should apply guardfraction information found in the + * consensus. A specific consensus can be specified with the + * <b>ns</b> argument, if NULL the most recent one will be picked.*/ +int +should_apply_guardfraction(const networkstatus_t *ns) +{ + /* We need to check the corresponding torrc option and the consensus + * parameter if we need to. */ + const or_options_t *options = get_options(); + + /* If UseGuardFraction is 'auto' then check the same-named consensus + * parameter. If the consensus parameter is not present, default to + * "off". */ + if (options->UseGuardFraction == -1) { + return networkstatus_get_param(ns, "UseGuardFraction", + 0, /* default to "off" */ + 0, 1); + } + + return options->UseGuardFraction; +} + +/* Given the original bandwidth of a guard and its guardfraction, + * calculate how much bandwidth the guard should have as a guard and + * as a non-guard. + * + * Quoting from proposal236: + * + * Let Wpf denote the weight from the 'bandwidth-weights' line a + * client would apply to N for position p if it had the guard + * flag, Wpn the weight if it did not have the guard flag, and B the + * measured bandwidth of N in the consensus. Then instead of choosing + * N for position p proportionally to Wpf*B or Wpn*B, clients should + * choose N proportionally to F*Wpf*B + (1-F)*Wpn*B. + * + * This function fills the <b>guardfraction_bw</b> structure. It sets + * <b>guard_bw</b> to F*B and <b>non_guard_bw</b> to (1-F)*B. + */ +void +guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, + int orig_bandwidth, + uint32_t guardfraction_percentage) +{ + double guardfraction_fraction; + + /* Turn the percentage into a fraction. */ + tor_assert(guardfraction_percentage <= 100); + guardfraction_fraction = guardfraction_percentage / 100.0; + + long guard_bw = tor_lround(guardfraction_fraction * orig_bandwidth); + tor_assert(guard_bw <= INT_MAX); + + guardfraction_bw->guard_bw = (int) guard_bw; + + guardfraction_bw->non_guard_bw = orig_bandwidth - (int) guard_bw; +} + /** A list of configured bridges. Whenever we actually get a descriptor * for one, we add it as an entry guard. Note that the order of bridges * in this list does not necessarily correspond to the order of bridges @@ -1607,6 +1804,11 @@ bridge_free(bridge_info_t *bridge) return; tor_free(bridge->transport_name); + if (bridge->socks_args) { + SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s)); + smartlist_free(bridge->socks_args); + } + tor_free(bridge); } @@ -1640,7 +1842,8 @@ get_configured_bridge_by_orports_digest(const char *digest, /** If we have a bridge configured whose digest matches <b>digest</b>, or a * bridge with no known digest whose address matches <b>addr</b>:<b>/port</b>, - * return that bridge. Else return NULL. */ + * return that bridge. Else return NULL. If <b>digest</b> is NULL, check for + * address/port matches only. */ static bridge_info_t * get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, uint16_t port, @@ -1650,7 +1853,7 @@ get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, return NULL; SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { - if (tor_digest_is_zero(bridge->identity) && + if ((tor_digest_is_zero(bridge->identity) || digest == NULL) && !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) && bridge->port == port) return bridge; @@ -1785,30 +1988,68 @@ bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port, } SMARTLIST_FOREACH_END(bridge); } -/** Remember a new bridge at <b>addr</b>:<b>port</b>. If <b>digest</b> - * is set, it tells us the identity key too. If we already had the - * bridge in our list, unmark it, and don't actually add anything new. - * If <b>transport_name</b> is non-NULL - the bridge is associated with a - * pluggable transport - we assign the transport to the bridge. */ +/** Return True if we have a bridge that uses a transport with name + * <b>transport_name</b>. */ +MOCK_IMPL(int, +transport_is_needed, (const char *transport_name)) +{ + if (!bridge_list) + return 0; + + SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { + if (bridge->transport_name && + !strcmp(bridge->transport_name, transport_name)) + return 1; + } SMARTLIST_FOREACH_END(bridge); + + return 0; +} + +/** Register the bridge information in <b>bridge_line</b> to the + * bridge subsystem. Steals reference of <b>bridge_line</b>. */ void -bridge_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *digest, const char *transport_name) +bridge_add_from_config(bridge_line_t *bridge_line) { bridge_info_t *b; - bridge_resolve_conflicts(addr, port, digest, transport_name); + { /* Log the bridge we are about to register: */ + log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)", + fmt_addrport(&bridge_line->addr, bridge_line->port), + bridge_line->transport_name ? + bridge_line->transport_name : "no transport", + tor_digest_is_zero(bridge_line->digest) ? + "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN)); + + if (bridge_line->socks_args) { /* print socks arguments */ + int i = 0; + + tor_assert(smartlist_len(bridge_line->socks_args) > 0); + + log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:", + smartlist_len(bridge_line->socks_args)); + SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg, + log_debug(LD_CONFIG, "%d: %s", ++i, arg)); + } + } + + bridge_resolve_conflicts(&bridge_line->addr, + bridge_line->port, + bridge_line->digest, + bridge_line->transport_name); b = tor_malloc_zero(sizeof(bridge_info_t)); - tor_addr_copy(&b->addr, addr); - b->port = port; - if (digest) - memcpy(b->identity, digest, DIGEST_LEN); - if (transport_name) - b->transport_name = tor_strdup(transport_name); + tor_addr_copy(&b->addr, &bridge_line->addr); + b->port = bridge_line->port; + memcpy(b->identity, bridge_line->digest, DIGEST_LEN); + if (bridge_line->transport_name) + b->transport_name = bridge_line->transport_name; b->fetch_status.schedule = DL_SCHED_BRIDGE; + b->socks_args = bridge_line->socks_args; if (!bridge_list) bridge_list = smartlist_new(); + tor_free(bridge_line); /* Deallocate bridge_line now. */ + smartlist_add(bridge_list, b); } @@ -1869,7 +2110,7 @@ find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) * transport, but the transport could not be found. */ int -find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, +get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, const transport_t **transport) { *transport = NULL; @@ -1896,11 +2137,21 @@ find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, return 0; } +/** Return a smartlist containing all the SOCKS arguments that we + * should pass to the SOCKS proxy. */ +const smartlist_t * +get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) +{ + bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr, + port, + NULL); + return bridge ? bridge->socks_args : NULL; +} + /** We need to ask <b>bridge</b> for its server descriptor. */ static void launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) { - char *address; const or_options_t *options = get_options(); if (connection_get_by_type_addr_port_purpose( @@ -1915,15 +2166,12 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) return; } - address = tor_dup_addr(&bridge->addr); - - directory_initiate_command(address, &bridge->addr, + directory_initiate_command(&bridge->addr, bridge->port, 0/*no dirport*/, bridge->identity, DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_BRIDGE, DIRIND_ONEHOP, "authority.z", NULL, 0, 0); - tor_free(address); } /** Fetching the bridge descriptor from the bridge authority returned a @@ -2041,13 +2289,11 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) } else { if (tor_addr_family(&bridge->addr) == AF_INET) { ri->addr = tor_addr_to_ipv4h(&bridge->addr); - tor_free(ri->address); - ri->address = tor_dup_ip(ri->addr); ri->or_port = bridge->port; log_info(LD_DIR, "Adjusted bridge routerinfo for '%s' to match configured " "address %s:%d.", - ri->nickname, ri->address, ri->or_port); + ri->nickname, fmt_addr32(ri->addr), ri->or_port); } else if (tor_addr_family(&bridge->addr) == AF_INET6) { tor_addr_copy(&ri->ipv6_addr, &bridge->addr); ri->ipv6_orport = bridge->port; @@ -2105,7 +2351,7 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) tor_assert(ri); tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); if (get_options()->UseBridges) { - int first = !any_bridge_descriptors_known(); + int first = num_bridges_usable() <= 1; bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); time_t now = time(NULL); router_set_status(ri->cache_info.identity_digest, 1); @@ -2119,6 +2365,13 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) node = node_get_mutable_by_id(ri->cache_info.identity_digest); tor_assert(node); rewrite_node_address_for_bridge(bridge, node); + if (tor_digest_is_zero(bridge->identity)) { + memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN); + log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d", + hex_str(bridge->identity, DIGEST_LEN), + fmt_and_decorate_addr(&bridge->addr), + (int) bridge->port); + } add_an_entry_guard(node, 1, 1, 0, 0); log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, @@ -2128,17 +2381,14 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) entry_guard_register_connect_status(ri->cache_info.identity_digest, 1, 0, now); if (first) { - /* XXXX apparently, this is never called. See bug #9229. */ routerlist_retry_directory_downloads(now); } - - update_networkstatus_downloads(now); } } } -/** Return 1 if any of our entry guards have descriptors that - * are marked with purpose 'bridge' and are running. Else return 0. +/** Return the number of bridges that have descriptors that + * are marked with purpose 'bridge' and are running. * * We use this function to decide if we're ready to start building * circuits through our bridges, or if we need to wait until the @@ -2150,25 +2400,16 @@ any_bridge_descriptors_known(void) return choose_random_entry(NULL) != NULL; } -/** Return 1 if there are any directory conns fetching bridge descriptors - * that aren't marked for close. We use this to guess if we should tell - * the controller that we have a problem. */ -int -any_pending_bridge_descriptor_fetches(void) -{ - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - if (conn->type == CONN_TYPE_DIR && - conn->purpose == DIR_PURPOSE_FETCH_SERVERDESC && - TO_DIR_CONN(conn)->router_purpose == ROUTER_PURPOSE_BRIDGE && - !conn->marked_for_close && - conn->linked && - conn->linked_conn && !conn->linked_conn->marked_for_close) { - log_debug(LD_DIR, "found one: %s", conn->address); - return 1; - } - } SMARTLIST_FOREACH_END(conn); - return 0; +/** Return the number of bridges that have descriptors that are marked with + * purpose 'bridge' and are running. + */ +static int +num_bridges_usable(void) +{ + int n_options = 0; + tor_assert(get_options()->UseBridges); + (void) choose_random_entry_impl(NULL, 0, 0, &n_options); + return n_options; } /** Return 1 if we have at least one descriptor for an entry guard @@ -2187,7 +2428,9 @@ entries_retry_helper(const or_options_t *options, int act) 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) { + node_is_bridge(node) == need_bridges && + (!need_bridges || (!e->bad_since && + node_is_a_configured_bridge(node)))) { any_known = 1; if (node->is_running) any_running = 1; /* some entry is both known and running */ @@ -2266,6 +2509,6 @@ entry_guards_free_all(void) clear_bridge_list(); smartlist_free(bridge_list); bridge_list = NULL; - circuit_build_times_free_timeouts(&circ_times); + circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 52b8dc00e4..107a562506 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -1,11 +1,11 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** - * \file guardnodes.h + * \file entrynodes.h * \brief Header file for circuitbuild.c. **/ @@ -77,6 +77,40 @@ int num_live_entry_guards(int for_directory); #endif +#ifdef ENTRYNODES_PRIVATE +STATIC const node_t *add_an_entry_guard(const node_t *chosen, + int reset_status, int prepend, + int for_discovery, int for_directory); + +STATIC int populate_live_entry_guards(smartlist_t *live_entry_guards, + const smartlist_t *all_entry_guards, + const node_t *chosen_exit, + dirinfo_type_t dirinfo_type, + int for_directory, + int need_uptime, int need_capacity); +STATIC int decide_num_guards(const or_options_t *options, int for_directory); + +STATIC void entry_guards_set_from_config(const or_options_t *options); + +/** Flags to be passed to entry_is_live() to indicate what kind of + * entry nodes we are looking for. */ +typedef enum { + ENTRY_NEED_UPTIME = 1<<0, + ENTRY_NEED_CAPACITY = 1<<1, + ENTRY_ASSUME_REACHABLE = 1<<2, + ENTRY_NEED_DESCRIPTOR = 1<<3, +} entry_is_live_flags_t; + +STATIC const node_t *entry_is_live(const entry_guard_t *e, + entry_is_live_flags_t flags, + const char **msg); + +STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now); + +#endif + +void remove_all_entry_guards(void); + void entry_guards_compute_status(const or_options_t *options, time_t now); int entry_guard_register_connect_status(const char *digest, int succeeded, int mark_relay_status, time_t now); @@ -97,31 +131,50 @@ int routerinfo_is_a_configured_bridge(const routerinfo_t *ri); int node_is_a_configured_bridge(const node_t *node); void learned_router_identity(const tor_addr_t *addr, uint16_t port, const char *digest); -void bridge_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *digest, - const char *transport_name); +struct bridge_line_t; +void bridge_add_from_config(struct bridge_line_t *bridge_line); void retry_bridge_descriptor_fetch_directly(const char *digest); void fetch_bridge_descriptors(const or_options_t *options, time_t now); void learned_bridge_descriptor(routerinfo_t *ri, int from_cache); int any_bridge_descriptors_known(void); -int any_pending_bridge_descriptor_fetches(void); int entries_known_but_down(const or_options_t *options); void entries_retry_all(const or_options_t *options); int any_bridge_supports_microdescriptors(void); +const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr, + uint16_t port); + +int any_bridges_dont_support_microdescriptors(void); void entry_guards_free_all(void); const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port); struct transport_t; -int find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, +int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, const struct transport_t **transport); +MOCK_DECL(int, transport_is_needed, (const char *transport_name)); int validate_pluggable_transports_config(void); double pathbias_get_close_success_count(entry_guard_t *guard); double pathbias_get_use_success_count(entry_guard_t *guard); +/** Contains the bandwidth of a relay as a guard and as a non-guard + * after the guardfraction has been considered. */ +typedef struct guardfraction_bandwidth_t { + /** Bandwidth as a guard after guardfraction has been considered. */ + int guard_bw; + /** Bandwidth as a non-guard after guardfraction has been considered. */ + int non_guard_bw; +} guardfraction_bandwidth_t; + +int should_apply_guardfraction(const networkstatus_t *ns); + +void +guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, + int orig_bandwidth, + uint32_t guardfraction_percentage); + #endif diff --git a/src/or/eventdns_tor.h b/src/or/eventdns_tor.h index 69662281bc..9d51f0960e 100644 --- a/src/or/eventdns_tor.h +++ b/src/or/eventdns_tor.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_EVENTDNS_TOR_H diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c new file mode 100644 index 0000000000..e8c8aa60a4 --- /dev/null +++ b/src/or/ext_orport.c @@ -0,0 +1,649 @@ +/* Copyright (c) 2012-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file ext_orport.c + * \brief Code implementing the Extended ORPort. +*/ + +#define EXT_ORPORT_PRIVATE +#include "or.h" +#include "connection.h" +#include "connection_or.h" +#include "ext_orport.h" +#include "control.h" +#include "config.h" +#include "util.h" +#include "main.h" + +/** Allocate and return a structure capable of holding an Extended + * ORPort message of body length <b>len</b>. */ +ext_or_cmd_t * +ext_or_cmd_new(uint16_t len) +{ + size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len; + ext_or_cmd_t *cmd = tor_malloc(size); + cmd->len = len; + return cmd; +} + +/** Deallocate the Extended ORPort message in <b>cmd</b>. */ +void +ext_or_cmd_free(ext_or_cmd_t *cmd) +{ + tor_free(cmd); +} + +/** Get an Extended ORPort message from <b>conn</b>, and place it in + * <b>out</b>. Return -1 on fail, 0 if we need more data, and 1 if we + * successfully extracted an Extended ORPort command from the + * buffer. */ +static int +connection_fetch_ext_or_cmd_from_buf(connection_t *conn, ext_or_cmd_t **out) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_ext_or_command_from_evbuffer(input, out); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_ext_or_command_from_buf(conn->inbuf, out); + } +} + +/** Write an Extended ORPort message to <b>conn</b>. Use + * <b>command</b> as the command type, <b>bodylen</b> as the body + * length, and <b>body</b>, if it's present, as the body of the + * message. */ +STATIC int +connection_write_ext_or_command(connection_t *conn, + uint16_t command, + const char *body, + size_t bodylen) +{ + char header[4]; + if (bodylen > UINT16_MAX) + return -1; + set_uint16(header, htons(command)); + set_uint16(header+2, htons(bodylen)); + connection_write_to_buf(header, 4, conn); + if (bodylen) { + tor_assert(body); + connection_write_to_buf(body, bodylen, conn); + } + return 0; +} + +/** Transition from an Extended ORPort which accepts Extended ORPort + * messages, to an Extended ORport which accepts OR traffic. */ +static void +connection_ext_or_transition(or_connection_t *conn) +{ + tor_assert(conn->base_.type == CONN_TYPE_EXT_OR); + + conn->base_.type = CONN_TYPE_OR; + TO_CONN(conn)->state = 0; // set the state to a neutral value + control_event_or_conn_status(conn, OR_CONN_EVENT_NEW, 0); + connection_tls_start_handshake(conn, 1); +} + +/** Length of authentication cookie. */ +#define EXT_OR_PORT_AUTH_COOKIE_LEN 32 +/** Length of the header of the cookie file. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN 32 +/** Static cookie file header. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER "! Extended ORPort Auth Cookie !\x0a" +/** Length of safe-cookie protocol hashes. */ +#define EXT_OR_PORT_AUTH_HASH_LEN DIGEST256_LEN +/** Length of safe-cookie protocol nonces. */ +#define EXT_OR_PORT_AUTH_NONCE_LEN 32 +/** Safe-cookie protocol constants. */ +#define EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST \ + "ExtORPort authentication server-to-client hash" +#define EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST \ + "ExtORPort authentication client-to-server hash" + +/* Code to indicate cookie authentication */ +#define EXT_OR_AUTHTYPE_SAFECOOKIE 0x01 + +/** If true, we've set ext_or_auth_cookie to a secret code and stored + * it to disk. */ +STATIC int ext_or_auth_cookie_is_set = 0; +/** If ext_or_auth_cookie_is_set, a secret cookie that we've stored to disk + * and which we're using to authenticate controllers. (If the controller can + * read it off disk, it has permission to connect.) */ +STATIC uint8_t *ext_or_auth_cookie = NULL; + +/** Helper: Return a newly allocated string containing a path to the + * file where we store our authentication cookie. */ +char * +get_ext_or_auth_cookie_file_name(void) +{ + const or_options_t *options = get_options(); + if (options->ExtORPortCookieAuthFile && + strlen(options->ExtORPortCookieAuthFile)) { + return tor_strdup(options->ExtORPortCookieAuthFile); + } else { + return get_datadir_fname("extended_orport_auth_cookie"); + } +} + +/* Initialize the cookie-based authentication system of the + * Extended ORPort. If <b>is_enabled</b> is 0, then disable the cookie + * authentication system. */ +int +init_ext_or_cookie_authentication(int is_enabled) +{ + char *fname = NULL; + int retval; + + if (!is_enabled) { + ext_or_auth_cookie_is_set = 0; + return 0; + } + + fname = get_ext_or_auth_cookie_file_name(); + retval = init_cookie_authentication(fname, EXT_OR_PORT_AUTH_COOKIE_HEADER, + EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN, + get_options()->ExtORPortCookieAuthFileGroupReadable, + &ext_or_auth_cookie, + &ext_or_auth_cookie_is_set); + tor_free(fname); + return retval; +} + +/** Read data from <b>conn</b> and see if the client sent us the + * authentication type that she prefers to use in this session. + * + * Return -1 if we received corrupted data or if we don't support the + * authentication type. Return 0 if we need more data in + * <b>conn</b>. Return 1 if the authentication type negotiation was + * successful. */ +static int +connection_ext_or_auth_neg_auth_type(connection_t *conn) +{ + char authtype[1] = {0}; + + if (connection_get_inbuf_len(conn) < 1) + return 0; + + if (connection_fetch_from_buf(authtype, 1, conn) < 0) + return -1; + + log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); + if (authtype[0] != EXT_OR_AUTHTYPE_SAFECOOKIE) { + /* '1' is the only auth type supported atm */ + return -1; + } + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE; + return 1; +} + +/** DOCDOC */ +STATIC int +handle_client_auth_nonce(const char *client_nonce, size_t client_nonce_len, + char **client_hash_out, + char **reply_out, size_t *reply_len_out) +{ + char server_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + char server_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; + char *reply; + size_t reply_len; + + if (client_nonce_len != EXT_OR_PORT_AUTH_NONCE_LEN) + return -1; + + /* Get our nonce */ + if (crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN) < 0) + return -1; + + { /* set up macs */ + size_t hmac_s_msg_len = strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + size_t hmac_c_msg_len = strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + + char *hmac_s_msg = tor_malloc_zero(hmac_s_msg_len); + char *hmac_c_msg = tor_malloc_zero(hmac_c_msg_len); + char *correct_client_hash = tor_malloc_zero(EXT_OR_PORT_AUTH_HASH_LEN); + + memcpy(hmac_s_msg, + EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST, + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST)); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + memcpy(hmac_c_msg, + EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST, + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST)); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + crypto_hmac_sha256(server_hash, + (char*)ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_s_msg, + hmac_s_msg_len); + + crypto_hmac_sha256(correct_client_hash, + (char*)ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_c_msg, + hmac_c_msg_len); + + /* Store the client hash we generated. We will need to compare it + with the hash sent by the client. */ + *client_hash_out = correct_client_hash; + + memwipe(hmac_s_msg, 0, hmac_s_msg_len); + memwipe(hmac_c_msg, 0, hmac_c_msg_len); + + tor_free(hmac_s_msg); + tor_free(hmac_c_msg); + } + + { /* debug logging */ /* XXX disable this codepath if not logging on debug?*/ + char server_hash_encoded[(2*EXT_OR_PORT_AUTH_HASH_LEN) + 1]; + char server_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + char client_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + + base16_encode(server_hash_encoded, sizeof(server_hash_encoded), + server_hash, sizeof(server_hash)); + base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), + server_nonce, sizeof(server_nonce)); + base16_encode(client_nonce_encoded, sizeof(client_nonce_encoded), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + log_debug(LD_GENERAL, + "server_hash: '%s'\nserver_nonce: '%s'\nclient_nonce: '%s'", + server_hash_encoded, server_nonce_encoded, client_nonce_encoded); + + memwipe(server_hash_encoded, 0, sizeof(server_hash_encoded)); + memwipe(server_nonce_encoded, 0, sizeof(server_nonce_encoded)); + memwipe(client_nonce_encoded, 0, sizeof(client_nonce_encoded)); + } + + { /* write reply: (server_hash, server_nonce) */ + + reply_len = EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_NONCE_LEN; + reply = tor_malloc_zero(reply_len); + memcpy(reply, server_hash, EXT_OR_PORT_AUTH_HASH_LEN); + memcpy(reply + EXT_OR_PORT_AUTH_HASH_LEN, server_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN); + } + + *reply_out = reply; + *reply_len_out = reply_len; + + return 0; +} + +/** Read the client's nonce out of <b>conn</b>, setup the safe-cookie + * crypto, and then send our own hash and nonce to the client + * + * Return -1 if there was an error; return 0 if we need more data in + * <b>conn</b>, and return 1 if we successfully retrieved the + * client's nonce and sent our own. */ +static int +connection_ext_or_auth_handle_client_nonce(connection_t *conn) +{ + char client_nonce[EXT_OR_PORT_AUTH_NONCE_LEN]; + char *reply=NULL; + size_t reply_len=0; + + if (!ext_or_auth_cookie_is_set) { /* this should not happen */ + log_warn(LD_BUG, "Extended ORPort authentication cookie was not set. " + "That's weird since we should have done that on startup. " + "This might be a Tor bug, please file a bug report. "); + return -1; + } + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) + return 0; + + if (connection_fetch_from_buf(client_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) + return -1; + + /* We extract the ClientNonce from the received data, and use it to + calculate ServerHash and ServerNonce according to proposal 217. + + We also calculate our own ClientHash value and save it in the + connection state. We validate it later against the ClientHash + sent by the client. */ + if (handle_client_auth_nonce(client_nonce, sizeof(client_nonce), + &TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, + &reply, &reply_len) < 0) + return -1; + + connection_write_to_buf(reply, reply_len, conn); + + memwipe(reply, 0, reply_len); + tor_free(reply); + + log_debug(LD_GENERAL, "Got client nonce, and sent our own nonce and hash."); + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH; + return 1; +} + +#define connection_ext_or_auth_send_result_success(c) \ + connection_ext_or_auth_send_result(c, 1) +#define connection_ext_or_auth_send_result_fail(c) \ + connection_ext_or_auth_send_result(c, 0) + +/** Send authentication results to <b>conn</b>. Successful results if + * <b>success</b> is set; failure results otherwise. */ +static void +connection_ext_or_auth_send_result(connection_t *conn, int success) +{ + if (success) + connection_write_to_buf("\x01", 1, conn); + else + connection_write_to_buf("\x00", 1, conn); +} + +/** Receive the client's hash from <b>conn</b>, validate that it's + * correct, and then send the authentication results to the client. + * + * Return -1 if there was an error during validation; return 0 if we + * need more data in <b>conn</b>, and return 1 if we successfully + * validated the client's hash and sent a happy authentication + * result. */ +static int +connection_ext_or_auth_handle_client_hash(connection_t *conn) +{ + char provided_client_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) + return 0; + + if (connection_fetch_from_buf(provided_client_hash, + EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) + return -1; + + if (tor_memneq(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, + provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN)) { + log_warn(LD_GENERAL, "Incorrect client hash. Authentication failed."); + connection_ext_or_auth_send_result_fail(conn); + return -1; + } + + log_debug(LD_GENERAL, "Got client's hash and it was legit."); + + /* send positive auth result */ + connection_ext_or_auth_send_result_success(conn); + conn->state = EXT_OR_CONN_STATE_OPEN; + return 1; +} + +/** Handle data from <b>or_conn</b> received on Extended ORPort. + * Return -1 on error. 0 on unsufficient data. 1 on correct. */ +static int +connection_ext_or_auth_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + + /* State transitions of the Extended ORPort authentication protocol: + + EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE (start state) -> + EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE -> + EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH -> + EXT_OR_CONN_STATE_OPEN + + During EXT_OR_CONN_STATE_OPEN, data is handled by + connection_ext_or_process_inbuf(). + */ + + switch (conn->state) { /* Functionify */ + case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: + return connection_ext_or_auth_neg_auth_type(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: + return connection_ext_or_auth_handle_client_nonce(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: + return connection_ext_or_auth_handle_client_hash(conn); + + default: + log_warn(LD_BUG, "Encountered unexpected connection state %d while trying " + "to process Extended ORPort authentication data.", conn->state); + return -1; + } +} + +/** Extended ORPort commands (Transport-to-Bridge) */ +#define EXT_OR_CMD_TB_DONE 0x0000 +#define EXT_OR_CMD_TB_USERADDR 0x0001 +#define EXT_OR_CMD_TB_TRANSPORT 0x0002 + +/** Extended ORPort commands (Bridge-to-Transport) */ +#define EXT_OR_CMD_BT_OKAY 0x1000 +#define EXT_OR_CMD_BT_DENY 0x1001 +#define EXT_OR_CMD_BT_CONTROL 0x1002 + +/** Process a USERADDR command from the Extended + * ORPort. <b>payload</b> is a payload of size <b>len</b>. + * + * If the USERADDR command was well formed, change the address of + * <b>conn</b> to the address on the USERADDR command. + * + * Return 0 on success and -1 on error. */ +static int +connection_ext_or_handle_cmd_useraddr(connection_t *conn, + const char *payload, uint16_t len) +{ + /* Copy address string. */ + tor_addr_t addr; + uint16_t port; + char *addr_str; + char *address_part=NULL; + int res; + if (memchr(payload, '\0', len)) { + log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort UserAddr"); + return -1; + } + + addr_str = tor_memdup_nulterm(payload, len); + + res = tor_addr_port_split(LOG_INFO, addr_str, &address_part, &port); + tor_free(addr_str); + if (res<0) + return -1; + + res = tor_addr_parse(&addr, address_part); + tor_free(address_part); + if (res<0) + return -1; + + { /* do some logging */ + char *old_address = tor_dup_addr(&conn->addr); + char *new_address = tor_dup_addr(&addr); + + log_debug(LD_NET, "Received USERADDR." + "We rewrite our address from '%s:%u' to '%s:%u'.", + safe_str(old_address), conn->port, safe_str(new_address), port); + + tor_free(old_address); + tor_free(new_address); + } + + /* record the address */ + tor_addr_copy(&conn->addr, &addr); + conn->port = port; + if (conn->address) { + tor_free(conn->address); + } + conn->address = tor_dup_addr(&addr); + + return 0; +} + +/** Process a TRANSPORT command from the Extended + * ORPort. <b>payload</b> is a payload of size <b>len</b>. + * + * If the TRANSPORT command was well formed, register the name of the + * transport on <b>conn</b>. + * + * Return 0 on success and -1 on error. */ +static int +connection_ext_or_handle_cmd_transport(or_connection_t *conn, + const char *payload, uint16_t len) +{ + char *transport_str; + if (memchr(payload, '\0', len)) { + log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort Transport"); + return -1; + } + + transport_str = tor_memdup_nulterm(payload, len); + + /* Transport names MUST be C-identifiers. */ + if (!string_is_C_identifier(transport_str)) { + tor_free(transport_str); + return -1; + } + + /* If ext_or_transport is already occupied (because the PT sent two + * TRANSPORT commands), deallocate the old name and keep the new + * one */ + if (conn->ext_or_transport) + tor_free(conn->ext_or_transport); + + conn->ext_or_transport = transport_str; + return 0; +} + +#define EXT_OR_CONN_STATE_IS_AUTHENTICATING(st) \ + ((st) <= EXT_OR_CONN_STATE_AUTH_MAX) + +/** Process Extended ORPort messages from <b>or_conn</b>. */ +int +connection_ext_or_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + ext_or_cmd_t *command; + int r; + + /* DOCDOC Document the state machine and transitions in this function */ + + /* If we are still in the authentication stage, process traffic as + authentication data: */ + while (EXT_OR_CONN_STATE_IS_AUTHENTICATING(conn->state)) { + log_debug(LD_GENERAL, "Got Extended ORPort authentication data (%u).", + (unsigned int) connection_get_inbuf_len(conn)); + r = connection_ext_or_auth_process_inbuf(or_conn); + if (r < 0) { + connection_mark_for_close(conn); + return -1; + } else if (r == 0) { + return 0; + } + /* if r > 0, loop and process more data (if any). */ + } + + while (1) { + log_debug(LD_GENERAL, "Got Extended ORPort data."); + command = NULL; + r = connection_fetch_ext_or_cmd_from_buf(conn, &command); + if (r < 0) + goto err; + else if (r == 0) + return 0; /* need to wait for more data */ + + /* Got a command! */ + tor_assert(command); + + if (command->cmd == EXT_OR_CMD_TB_DONE) { + if (connection_get_inbuf_len(conn)) { + /* The inbuf isn't empty; the client is misbehaving. */ + goto err; + } + + log_debug(LD_NET, "Received DONE."); + + /* If the transport proxy did not use the TRANSPORT command to + * specify the transport name, mark this as unknown transport. */ + if (!or_conn->ext_or_transport) { + /* We write this string this way to avoid ??>, which is a C + * trigraph. */ + or_conn->ext_or_transport = tor_strdup("<?" "?>"); + } + + connection_write_ext_or_command(conn, EXT_OR_CMD_BT_OKAY, NULL, 0); + + /* can't transition immediately; need to flush first. */ + conn->state = EXT_OR_CONN_STATE_FLUSHING; + connection_stop_reading(conn); + } else if (command->cmd == EXT_OR_CMD_TB_USERADDR) { + if (connection_ext_or_handle_cmd_useraddr(conn, + command->body, command->len) < 0) + goto err; + } else if (command->cmd == EXT_OR_CMD_TB_TRANSPORT) { + if (connection_ext_or_handle_cmd_transport(or_conn, + command->body, command->len) < 0) + goto err; + } else { + log_notice(LD_NET,"Got Extended ORPort command we don't regognize (%u).", + command->cmd); + } + + ext_or_cmd_free(command); + } + + return 0; + + err: + ext_or_cmd_free(command); + connection_mark_for_close(conn); + return -1; +} + +/** <b>conn</b> finished flushing Extended ORPort messages to the + * network, and is now ready to accept OR traffic. This function + * does the transition. */ +int +connection_ext_or_finished_flushing(or_connection_t *conn) +{ + if (conn->base_.state == EXT_OR_CONN_STATE_FLUSHING) { + connection_start_reading(TO_CONN(conn)); + connection_ext_or_transition(conn); + } + return 0; +} + +/** Initiate Extended ORPort authentication, by sending the list of + * supported authentication types to the client. */ +int +connection_ext_or_start_auth(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + const uint8_t authtypes[] = { + /* We only support authtype '1' for now. */ + EXT_OR_AUTHTYPE_SAFECOOKIE, + /* Marks the end of the list. */ + 0 + }; + + log_debug(LD_GENERAL, + "ExtORPort authentication: Sending supported authentication types"); + + connection_write_to_buf((const char *)authtypes, sizeof(authtypes), conn); + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; + + return 0; +} + +/** Free any leftover allocated memory of the ext_orport.c subsystem. */ +void +ext_orport_free_all(void) +{ + if (ext_or_auth_cookie) /* Free the auth cookie */ + tor_free(ext_or_auth_cookie); +} + diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h new file mode 100644 index 0000000000..8b2542f937 --- /dev/null +++ b/src/or/ext_orport.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef EXT_ORPORT_H +#define EXT_ORPORT_H + +int connection_ext_or_start_auth(or_connection_t *or_conn); + +ext_or_cmd_t *ext_or_cmd_new(uint16_t len); +void ext_or_cmd_free(ext_or_cmd_t *cmd); +void connection_or_set_ext_or_identifier(or_connection_t *conn); +void connection_or_remove_from_ext_or_id_map(or_connection_t *conn); +void connection_or_clear_ext_or_id_map(void); +or_connection_t *connection_or_get_by_ext_or_id(const char *id); + +int connection_ext_or_finished_flushing(or_connection_t *conn); +int connection_ext_or_process_inbuf(or_connection_t *or_conn); + +int init_ext_or_cookie_authentication(int is_enabled); +char *get_ext_or_auth_cookie_file_name(void); +void ext_orport_free_all(void); + +#ifdef EXT_ORPORT_PRIVATE +STATIC int connection_write_ext_or_command(connection_t *conn, + uint16_t command, + const char *body, + size_t bodylen); +STATIC int handle_client_auth_nonce(const char *client_nonce, + size_t client_nonce_len, + char **client_hash_out, + char **reply_out, size_t *reply_len_out); +#ifdef TOR_UNIT_TESTS +extern uint8_t *ext_or_auth_cookie; +extern int ext_or_auth_cookie_is_set; +#endif +#endif + +#endif + diff --git a/src/or/fp_pair.c b/src/or/fp_pair.c index 4d8a835c83..42bebcd847 100644 --- a/src/or/fp_pair.c +++ b/src/or/fp_pair.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013, The Tor Project, Inc. */ +/* Copyright (c) 2013-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" @@ -32,17 +32,8 @@ fp_pair_map_entries_eq(const fp_pair_map_entry_t *a, static INLINE unsigned int fp_pair_map_entry_hash(const fp_pair_map_entry_t *a) { - const uint32_t *p; - unsigned int hash; - - p = (const uint32_t *)(a->key.first); - /* Hashes are 20 bytes long, so 5 times uint32_t */ - hash = p[0] ^ p[1] ^ p[2] ^ p[3] ^ p[4]; - /* Now XOR in the second fingerprint */ - p = (const uint32_t *)(a->key.second); - hash ^= p[0] ^ p[1] ^ p[2] ^ p[3] ^ p[4]; - - return hash; + tor_assert(sizeof(a->key) == DIGEST_LEN*2); + return (unsigned) siphash24g(&a->key, DIGEST_LEN*2); } /* @@ -51,9 +42,9 @@ fp_pair_map_entry_hash(const fp_pair_map_entry_t *a) HT_PROTOTYPE(fp_pair_map_impl, fp_pair_map_entry_s, node, fp_pair_map_entry_hash, fp_pair_map_entries_eq) -HT_GENERATE(fp_pair_map_impl, fp_pair_map_entry_s, node, - fp_pair_map_entry_hash, fp_pair_map_entries_eq, - 0.6, tor_malloc, tor_realloc, tor_free) +HT_GENERATE2(fp_pair_map_impl, fp_pair_map_entry_s, node, + fp_pair_map_entry_hash, fp_pair_map_entries_eq, + 0.6, tor_reallocarray_, tor_free_) /** Constructor to create a new empty map from fp_pair_t to void * */ diff --git a/src/or/fp_pair.h b/src/or/fp_pair.h index 89f664a813..0830ab1f36 100644 --- a/src/or/fp_pair.h +++ b/src/or/fp_pair.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013, The Tor Project, Inc. */ +/* Copyright (c) 2013-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/geoip.c b/src/or/geoip.c index e2e98e8ec4..120ce479cc 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2013, The Tor Project, Inc. */ +/* Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -58,8 +58,8 @@ static char geoip6_digest[DIGEST_LEN]; /** Return the index of the <b>country</b>'s entry in the GeoIP * country list if it is a valid 2-letter country code, otherwise * return -1. */ -country_t -geoip_get_country(const char *country) +MOCK_IMPL(country_t, +geoip_get_country,(const char *country)) { void *idxplus1_; intptr_t idx; @@ -120,7 +120,7 @@ geoip_add_entry(const tor_addr_t *low, const tor_addr_t *high, /** Add an entry to the GeoIP table indicated by <b>family</b>, * parsing it from <b>line</b>. The format is as for geoip_load_file(). */ -/*private*/ int +STATIC int geoip_parse_entry(const char *line, sa_family_t family) { tor_addr_t low_addr, high_addr; @@ -363,7 +363,7 @@ geoip_load_file(sa_family_t family, const char *filename) * be less than geoip_get_n_countries(). To decode it, call * geoip_get_country_name(). */ -int +STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr) { geoip_ipv4_entry_t *ent; @@ -379,7 +379,7 @@ geoip_get_country_by_ipv4(uint32_t ipaddr) * 0 for the 'unknown country'. The return value will always be less than * geoip_get_n_countries(). To decode it, call geoip_get_country_name(). */ -int +STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr) { geoip_ipv6_entry_t *ent; @@ -396,8 +396,8 @@ geoip_get_country_by_ipv6(const struct in6_addr *addr) * the 'unknown country'. The return value will always be less than * geoip_get_n_countries(). To decode it, call geoip_get_country_name(). */ -int -geoip_get_country_by_addr(const tor_addr_t *addr) +MOCK_IMPL(int, +geoip_get_country_by_addr,(const tor_addr_t *addr)) { if (tor_addr_family(addr) == AF_INET) { return geoip_get_country_by_ipv4(tor_addr_to_ipv4h(addr)); @@ -409,8 +409,8 @@ geoip_get_country_by_addr(const tor_addr_t *addr) } /** Return the number of countries recognized by the GeoIP country list. */ -int -geoip_get_n_countries(void) +MOCK_IMPL(int, +geoip_get_n_countries,(void)) { if (!geoip_countries) init_geoip_countries(); @@ -430,8 +430,8 @@ geoip_get_country_name(country_t num) } /** Return true iff we have loaded a GeoIP database.*/ -int -geoip_is_loaded(sa_family_t family) +MOCK_IMPL(int, +geoip_is_loaded,(sa_family_t family)) { tor_assert(family == AF_INET || family == AF_INET6); if (geoip_countries == NULL) @@ -461,6 +461,10 @@ geoip_db_digest(sa_family_t family) typedef struct clientmap_entry_t { HT_ENTRY(clientmap_entry_t) node; tor_addr_t addr; + /* Name of pluggable transport used by this client. NULL if no + pluggable transport was used. */ + char *transport_name; + /** Time when we last saw this IP address, in MINUTES since the epoch. * * (This will run out of space around 4011 CE. If Tor is still in use around @@ -482,20 +486,39 @@ static HT_HEAD(clientmap, clientmap_entry_t) client_history = static INLINE unsigned clientmap_entry_hash(const clientmap_entry_t *a) { - return ht_improve_hash(tor_addr_hash(&a->addr)); + unsigned h = (unsigned) tor_addr_hash(&a->addr); + + if (a->transport_name) + h += (unsigned) siphash24g(a->transport_name, strlen(a->transport_name)); + + return h; } /** Hashtable helper: compare two clientmap_entry_t values for equality. */ static INLINE int clientmap_entries_eq(const clientmap_entry_t *a, const clientmap_entry_t *b) { + if (strcmp_opt(a->transport_name, b->transport_name)) + return 0; + return !tor_addr_compare(&a->addr, &b->addr, CMP_EXACT) && a->action == b->action; } HT_PROTOTYPE(clientmap, clientmap_entry_t, node, clientmap_entry_hash, clientmap_entries_eq); -HT_GENERATE(clientmap, clientmap_entry_t, node, clientmap_entry_hash, - clientmap_entries_eq, 0.6, malloc, realloc, free); +HT_GENERATE2(clientmap, clientmap_entry_t, node, clientmap_entry_hash, + clientmap_entries_eq, 0.6, tor_reallocarray_, tor_free_) + +/** Free all storage held by <b>ent</b>. */ +static void +clientmap_entry_free(clientmap_entry_t *ent) +{ + if (!ent) + return; + + tor_free(ent->transport_name); + tor_free(ent); +} /** Clear history of connecting clients used by entry and bridge stats. */ static void @@ -507,7 +530,7 @@ client_history_clear(void) if ((*ent)->action == GEOIP_CLIENT_CONNECT) { this = *ent; next = HT_NEXT_RMV(clientmap, &client_history, ent); - tor_free(this); + clientmap_entry_free(this); } else { next = HT_NEXT(clientmap, &client_history, ent); } @@ -519,27 +542,40 @@ client_history_clear(void) * configured accordingly. */ void geoip_note_client_seen(geoip_client_action_t action, - const tor_addr_t *addr, time_t now) + const tor_addr_t *addr, + const char *transport_name, + time_t now) { const or_options_t *options = get_options(); clientmap_entry_t lookup, *ent; + memset(&lookup, 0, sizeof(clientmap_entry_t)); + if (action == GEOIP_CLIENT_CONNECT) { /* Only remember statistics as entry guard or as bridge. */ if (!options->EntryStatistics && (!(options->BridgeRelay && options->BridgeRecordUsageByCountry))) return; } else { - if (options->BridgeRelay || options->BridgeAuthoritativeDir || - !options->DirReqStatistics) + /* Only gather directory-request statistics if configured, and + * forcibly disable them on bridge authorities. */ + if (!options->DirReqStatistics || options->BridgeAuthoritativeDir) return; } + log_debug(LD_GENERAL, "Seen client from '%s' with transport '%s'.", + safe_str_client(fmt_addr((addr))), + transport_name ? transport_name : "<no transport>"); + tor_addr_copy(&lookup.addr, addr); lookup.action = (int)action; + lookup.transport_name = (char*) transport_name; ent = HT_FIND(clientmap, &client_history, &lookup); + if (! ent) { ent = tor_malloc_zero(sizeof(clientmap_entry_t)); tor_addr_copy(&ent->addr, addr); + if (transport_name) + ent->transport_name = tor_strdup(transport_name); ent->action = (int)action; HT_INSERT(clientmap, &client_history, ent); } @@ -566,7 +602,7 @@ remove_old_client_helper_(struct clientmap_entry_t *ent, void *_cutoff) { time_t cutoff = *(time_t*)_cutoff / 60; if (ent->last_seen_in_minutes < cutoff) { - tor_free(ent); + clientmap_entry_free(ent); return 1; } else { return 0; @@ -684,8 +720,8 @@ dirreq_map_ent_hash(const dirreq_map_entry_t *entry) HT_PROTOTYPE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash, dirreq_map_ent_eq); -HT_GENERATE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash, - dirreq_map_ent_eq, 0.6, malloc, realloc, free); +HT_GENERATE2(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash, + dirreq_map_ent_eq, 0.6, tor_reallocarray_, tor_free_) /** Helper: Put <b>entry</b> into map of directory requests using * <b>type</b> and <b>dirreq_id</b> as key parts. If there is @@ -769,6 +805,106 @@ geoip_change_dirreq_state(uint64_t dirreq_id, dirreq_type_t type, } } +/** Return the bridge-ip-transports string that should be inserted in + * our extra-info descriptor. Return NULL if the bridge-ip-transports + * line should be empty. */ +char * +geoip_get_transport_history(void) +{ + unsigned granularity = IP_GRANULARITY; + /** String hash table (name of transport) -> (number of users). */ + strmap_t *transport_counts = strmap_new(); + + /** Smartlist that contains copies of the names of the transports + that have been used. */ + smartlist_t *transports_used = smartlist_new(); + + /* Special string to signify that no transport was used for this + connection. Pluggable transport names can't have symbols in their + names, so this string will never collide with a real transport. */ + static const char* no_transport_str = "<OR>"; + + clientmap_entry_t **ent; + const char *transport_name = NULL; + smartlist_t *string_chunks = smartlist_new(); + char *the_string = NULL; + + /* If we haven't seen any clients yet, return NULL. */ + if (HT_EMPTY(&client_history)) + goto done; + + /** We do the following steps to form the transport history string: + * a) Foreach client that uses a pluggable transport, we increase the + * times that transport was used by one. If the client did not use + * a transport, we increase the number of times someone connected + * without obfuscation. + * b) Foreach transport we observed, we write its transport history + * string and push it to string_chunks. So, for example, if we've + * seen 665 obfs2 clients, we write "obfs2=665". + * c) We concatenate string_chunks to form the final string. + */ + + log_debug(LD_GENERAL,"Starting iteration for transport history. %d clients.", + HT_SIZE(&client_history)); + + /* Loop through all clients. */ + HT_FOREACH(ent, clientmap, &client_history) { + uintptr_t val; + void *ptr; + transport_name = (*ent)->transport_name; + if (!transport_name) + transport_name = no_transport_str; + + /* Increase the count for this transport name. */ + ptr = strmap_get(transport_counts, transport_name); + val = (uintptr_t)ptr; + val++; + ptr = (void*)val; + strmap_set(transport_counts, transport_name, ptr); + + /* If it's the first time we see this transport, note it. */ + if (val == 1) + smartlist_add(transports_used, tor_strdup(transport_name)); + + log_debug(LD_GENERAL, "Client from '%s' with transport '%s'. " + "I've now seen %d clients.", + safe_str_client(fmt_addr(&(*ent)->addr)), + transport_name ? transport_name : "<no transport>", + (int)val); + } + + /* Sort the transport names (helps with unit testing). */ + smartlist_sort_strings(transports_used); + + /* Loop through all seen transports. */ + SMARTLIST_FOREACH_BEGIN(transports_used, const char *, transport_name) { + void *transport_count_ptr = strmap_get(transport_counts, transport_name); + uintptr_t transport_count = (uintptr_t) transport_count_ptr; + + log_debug(LD_GENERAL, "We got "U64_FORMAT" clients with transport '%s'.", + U64_PRINTF_ARG((uint64_t)transport_count), transport_name); + + smartlist_add_asprintf(string_chunks, "%s="U64_FORMAT, + transport_name, + U64_PRINTF_ARG(round_uint64_to_next_multiple_of( + (uint64_t)transport_count, + granularity))); + } SMARTLIST_FOREACH_END(transport_name); + + the_string = smartlist_join_strings(string_chunks, ",", 0, NULL); + + log_debug(LD_GENERAL, "Final bridge-ip-transports string: '%s'", the_string); + + done: + strmap_free(transport_counts, NULL); + SMARTLIST_FOREACH(transports_used, char *, s, tor_free(s)); + smartlist_free(transports_used); + SMARTLIST_FOREACH(string_chunks, char *, s, tor_free(s)); + smartlist_free(string_chunks); + + return the_string; +} + /** Return a newly allocated comma-separated string containing statistics * on network status downloads. The string contains the number of completed * requests, timeouts, and still running requests as well as the download @@ -827,7 +963,7 @@ geoip_get_dirreq_history(dirreq_type_t type) /* We may have rounded 'completed' up. Here we want to use the * real value. */ complete = smartlist_len(dirreq_completed); - dltimes = tor_malloc_zero(sizeof(uint32_t) * complete); + dltimes = tor_calloc(complete, sizeof(uint32_t)); SMARTLIST_FOREACH_BEGIN(dirreq_completed, dirreq_map_entry_t *, ent) { uint32_t bytes_per_second; uint32_t time_diff = (uint32_t) tv_mdiff(&ent->request_time, @@ -897,7 +1033,7 @@ geoip_get_client_history(geoip_client_action_t action, if (!geoip_is_loaded(AF_INET) && !geoip_is_loaded(AF_INET6)) return -1; - counts = tor_malloc_zero(sizeof(unsigned)*n_countries); + counts = tor_calloc(n_countries, sizeof(unsigned)); HT_FOREACH(ent, clientmap, &client_history) { int country; if ((*ent)->action != (int)action) @@ -1037,7 +1173,7 @@ geoip_reset_dirreq_stats(time_t now) if ((*ent)->action == GEOIP_CLIENT_NETWORKSTATUS) { this = *ent; next = HT_NEXT_RMV(clientmap, &client_history, ent); - tor_free(this); + clientmap_entry_free(this); } else { next = HT_NEXT(clientmap, &client_history, ent); } @@ -1132,7 +1268,7 @@ geoip_format_dirreq_stats(time_t now) time_t geoip_dirreq_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_dirreq_stats_interval) return 0; /* Not initialized. */ @@ -1146,21 +1282,13 @@ geoip_dirreq_stats_write(time_t now) str = geoip_format_dirreq_stats(now); /* Write dirreq-stats string to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "dirreq-stats", str, "dirreq statistics"); + /* Reset measurement interval start. */ + geoip_reset_dirreq_stats(now); } - filename = get_datadir_fname2("stats", "dirreq-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write dirreq statistics to disk!"); - - /* Reset measurement interval start. */ - geoip_reset_dirreq_stats(now); done: - tor_free(statsdir); - tor_free(filename); tor_free(str); return start_of_dirreq_stats_interval + WRITE_STATS_INTERVAL; } @@ -1197,6 +1325,8 @@ validate_bridge_stats(const char *stats_str, time_t now) const char *BRIDGE_STATS_END = "bridge-stats-end "; const char *BRIDGE_IPS = "bridge-ips "; const char *BRIDGE_IPS_EMPTY_LINE = "bridge-ips\n"; + const char *BRIDGE_TRANSPORTS = "bridge-ip-transports "; + const char *BRIDGE_TRANSPORTS_EMPTY_LINE = "bridge-ip-transports\n"; const char *tmp; time_t stats_end_time; int seconds; @@ -1231,6 +1361,15 @@ validate_bridge_stats(const char *stats_str, time_t now) return 0; } + /* Parse: "bridge-ip-transports PT=N,PT=N,..." */ + tmp = find_str_at_start_of_line(stats_str, BRIDGE_TRANSPORTS); + if (!tmp) { + /* Look if there is an empty "bridge-ip-transports" line */ + tmp = find_str_at_start_of_line(stats_str, BRIDGE_TRANSPORTS_EMPTY_LINE); + if (!tmp) + return 0; + } + return 1; } @@ -1244,7 +1383,8 @@ static char *bridge_stats_extrainfo = NULL; char * geoip_format_bridge_stats(time_t now) { - char *out = NULL, *country_data = NULL, *ipver_data = NULL; + char *out = NULL; + char *country_data = NULL, *ipver_data = NULL, *transport_data = NULL; long duration = now - start_of_bridge_stats_interval; char written[ISO_TIME_LEN+1]; @@ -1255,16 +1395,20 @@ geoip_format_bridge_stats(time_t now) format_iso_time(written, now); geoip_get_client_history(GEOIP_CLIENT_CONNECT, &country_data, &ipver_data); + transport_data = geoip_get_transport_history(); tor_asprintf(&out, "bridge-stats-end %s (%ld s)\n" "bridge-ips %s\n" - "bridge-ip-versions %s\n", + "bridge-ip-versions %s\n" + "bridge-ip-transports %s\n", written, duration, country_data ? country_data : "", - ipver_data ? ipver_data : ""); + ipver_data ? ipver_data : "", + transport_data ? transport_data : ""); tor_free(country_data); tor_free(ipver_data); + tor_free(transport_data); return out; } @@ -1292,12 +1436,45 @@ format_bridge_stats_controller(time_t now) return out; } +/** Return a newly allocated string holding our bridge usage stats by + * country in a format suitable for inclusion in our heartbeat + * message. Return NULL on failure. */ +char * +format_client_stats_heartbeat(time_t now) +{ + const int n_hours = 6; + char *out = NULL; + int n_clients = 0; + clientmap_entry_t **ent; + unsigned cutoff = (unsigned)( (now-n_hours*3600)/60 ); + + if (!start_of_bridge_stats_interval) + return NULL; /* Not initialized. */ + + /* count unique IPs */ + HT_FOREACH(ent, clientmap, &client_history) { + /* only count directly connecting clients */ + if ((*ent)->action != GEOIP_CLIENT_CONNECT) + continue; + if ((*ent)->last_seen_in_minutes < cutoff) + continue; + n_clients++; + } + + tor_asprintf(&out, "Heartbeat: " + "In the last %d hours, I have seen %d unique clients.", + n_hours, + n_clients); + + return out; +} + /** Write bridge statistics to $DATADIR/stats/bridge-stats and return * when we should next try to write statistics. */ time_t geoip_bridge_stats_write(time_t now) { - char *filename = NULL, *val = NULL, *statsdir = NULL; + char *val = NULL; /* Check if 24 hours have passed since starting measurements. */ if (now < start_of_bridge_stats_interval + WRITE_STATS_INTERVAL) @@ -1317,24 +1494,20 @@ geoip_bridge_stats_write(time_t now) start_of_bridge_stats_interval = now; /* Write it to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) - goto done; - filename = get_datadir_fname2("stats", "bridge-stats"); - - write_str_to_file(filename, bridge_stats_extrainfo, 0); - - /* Tell the controller, "hey, there are clients!" */ - { - char *controller_str = format_bridge_stats_controller(now); - if (controller_str) - control_event_clients_seen(controller_str); - tor_free(controller_str); + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "bridge-stats", + bridge_stats_extrainfo, "bridge statistics"); + + /* Tell the controller, "hey, there are clients!" */ + { + char *controller_str = format_bridge_stats_controller(now); + if (controller_str) + control_event_clients_seen(controller_str); + tor_free(controller_str); + } } - done: - tor_free(filename); - tor_free(statsdir); + done: return start_of_bridge_stats_interval + WRITE_STATS_INTERVAL; } @@ -1436,7 +1609,7 @@ geoip_format_entry_stats(time_t now) time_t geoip_entry_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_entry_stats_interval) return 0; /* Not initialized. */ @@ -1450,21 +1623,14 @@ geoip_entry_stats_write(time_t now) str = geoip_format_entry_stats(now); /* Write entry-stats string to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; - } - filename = get_datadir_fname2("stats", "entry-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write entry statistics to disk!"); + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "entry-stats", str, "entry statistics"); - /* Reset measurement interval start. */ - geoip_reset_entry_stats(now); + /* Reset measurement interval start. */ + geoip_reset_entry_stats(now); + } done: - tor_free(statsdir); - tor_free(filename); tor_free(str); return start_of_entry_stats_interval + WRITE_STATS_INTERVAL; } @@ -1534,7 +1700,7 @@ geoip_free_all(void) for (ent = HT_START(clientmap, &client_history); ent != NULL; ent = next) { this = *ent; next = HT_NEXT_RMV(clientmap, &client_history, ent); - tor_free(this); + clientmap_entry_free(this); } HT_CLEAR(clientmap, &client_history); } @@ -1549,5 +1715,6 @@ geoip_free_all(void) } clear_geoip_db(); + tor_free(bridge_stats_extrainfo); } diff --git a/src/or/geoip.h b/src/or/geoip.h index ebefee5f4e..8a3486c7ac 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,25 +12,29 @@ #ifndef TOR_GEOIP_H #define TOR_GEOIP_H +#include "testsupport.h" + #ifdef GEOIP_PRIVATE -int geoip_parse_entry(const char *line, sa_family_t family); -int geoip_get_country_by_ipv4(uint32_t ipaddr); -int geoip_get_country_by_ipv6(const struct in6_addr *addr); +STATIC int geoip_parse_entry(const char *line, sa_family_t family); +STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr); +STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr); #endif int should_record_bridge_info(const or_options_t *options); int geoip_load_file(sa_family_t family, const char *filename); -int geoip_get_country_by_addr(const tor_addr_t *addr); -int geoip_get_n_countries(void); +MOCK_DECL(int, geoip_get_country_by_addr, (const tor_addr_t *addr)); +MOCK_DECL(int, geoip_get_n_countries, (void)); const char *geoip_get_country_name(country_t num); -int geoip_is_loaded(sa_family_t family); +MOCK_DECL(int, geoip_is_loaded, (sa_family_t family)); const char *geoip_db_digest(sa_family_t family); -country_t geoip_get_country(const char *countrycode); +MOCK_DECL(country_t, geoip_get_country, (const char *countrycode)); void geoip_note_client_seen(geoip_client_action_t action, - const tor_addr_t *addr, time_t now); + const tor_addr_t *addr, const char *transport_name, + time_t now); void geoip_remove_old_clients(time_t cutoff); void geoip_note_ns_response(geoip_ns_response_t response); +char *geoip_get_transport_history(void); int geoip_get_client_history(geoip_client_action_t action, char **country_str, char **ipver_str); char *geoip_get_request_history(void); @@ -60,6 +64,7 @@ time_t geoip_bridge_stats_write(time_t now); void geoip_bridge_stats_term(void); const char *geoip_get_bridge_stats_extrainfo(time_t); char *geoip_get_bridge_stats_controller(time_t); +char *format_client_stats_heartbeat(time_t now); #endif diff --git a/src/or/hibernate.c b/src/or/hibernate.c index a412571331..356e11f6ec 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -239,8 +239,8 @@ accounting_parse_options(const or_options_t *options, int validate_only) /** If we want to manage the accounting system and potentially * hibernate, return 1, else return 0. */ -int -accounting_is_enabled(const or_options_t *options) +MOCK_IMPL(int, +accounting_is_enabled,(const or_options_t *options)) { if (options->AccountingMax) return 1; @@ -255,6 +255,13 @@ accounting_get_interval_length(void) return (int)(interval_end_time - interval_start_time); } +/** Return the time at which the current accounting interval will end. */ +MOCK_IMPL(time_t, +accounting_get_end_time,(void)) +{ + return interval_end_time; +} + /** Called from main.c to tell us that <b>seconds</b> seconds have * passed, <b>n_read</b> bytes have been read, and <b>n_written</b> * bytes have been written. */ @@ -403,6 +410,17 @@ configure_accounting(time_t now) accounting_set_wakeup_time(); } +/** Return the relevant number of bytes sent/received this interval + * based on the set AccountingRule */ +static uint64_t +get_accounting_bytes(void) +{ + if (get_options()->AccountingRule == ACCT_SUM) + return n_bytes_read_in_interval+n_bytes_written_in_interval; + else + return MAX(n_bytes_read_in_interval, n_bytes_written_in_interval); +} + /** Set expected_bandwidth_usage based on how much we sent/received * per minute last interval (if we were up for at least 30 minutes), * or based on our declared bandwidth otherwise. */ @@ -414,6 +432,11 @@ update_expected_bandwidth(void) uint64_t max_configured = (options->RelayBandwidthRate > 0 ? options->RelayBandwidthRate : options->BandwidthRate) * 60; + /* max_configured is the larger of bytes read and bytes written + * If we are accounting based on sum, worst case is both are + * at max, doubling the expected sum of bandwidth */ + if (get_options()->AccountingRule == ACCT_SUM) + max_configured *= 2; #define MIN_TIME_FOR_MEASUREMENT (1800) @@ -432,8 +455,7 @@ update_expected_bandwidth(void) * doesn't know to store soft-limit info. Just take rate at which * we were reading/writing in the last interval as our expected rate. */ - uint64_t used = MAX(n_bytes_written_in_interval, - n_bytes_read_in_interval); + uint64_t used = get_accounting_bytes(); expected = used / (n_seconds_active_in_interval / 60); } else { /* If we haven't gotten enough data last interval, set 'expected' @@ -641,7 +663,15 @@ read_bandwidth_usage(void) { char *fname = get_datadir_fname("bw_accounting"); - unlink(fname); + int res; + + res = unlink(fname); + if (res != 0) { + log_warn(LD_FS, + "Failed to unlink %s: %s", + fname, strerror(errno)); + } + tor_free(fname); } @@ -700,8 +730,7 @@ hibernate_hard_limit_reached(void) uint64_t hard_limit = get_options()->AccountingMax; if (!hard_limit) return 0; - return n_bytes_read_in_interval >= hard_limit - || n_bytes_written_in_interval >= hard_limit; + return get_accounting_bytes() >= hard_limit; } /** Return true iff we have sent/received almost all the bytes we are willing @@ -732,8 +761,7 @@ hibernate_soft_limit_reached(void) if (!soft_limit) return 0; - return n_bytes_read_in_interval >= soft_limit - || n_bytes_written_in_interval >= soft_limit; + return get_accounting_bytes() >= soft_limit; } /** Called when we get a SIGINT, or when bandwidth soft limit is @@ -757,8 +785,7 @@ hibernate_begin(hibernate_state_t new_state, time_t now) hibernate_state == HIBERNATE_STATE_LIVE) { soft_limit_hit_at = now; n_seconds_to_hit_soft_limit = n_seconds_active_in_interval; - n_bytes_at_soft_limit = MAX(n_bytes_read_in_interval, - n_bytes_written_in_interval); + n_bytes_at_soft_limit = get_accounting_bytes(); } /* close listeners. leave control listener(s). */ @@ -808,8 +835,8 @@ hibernate_begin_shutdown(void) } /** Return true iff we are currently hibernating. */ -int -we_are_hibernating(void) +MOCK_IMPL(int, +we_are_hibernating,(void)) { return hibernate_state != HIBERNATE_STATE_LIVE; } @@ -988,13 +1015,22 @@ getinfo_helper_accounting(control_connection_t *conn, U64_PRINTF_ARG(n_bytes_written_in_interval)); } else if (!strcmp(question, "accounting/bytes-left")) { uint64_t limit = get_options()->AccountingMax; - uint64_t read_left = 0, write_left = 0; - if (n_bytes_read_in_interval < limit) - read_left = limit - n_bytes_read_in_interval; - if (n_bytes_written_in_interval < limit) - write_left = limit - n_bytes_written_in_interval; - tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, - U64_PRINTF_ARG(read_left), U64_PRINTF_ARG(write_left)); + if (get_options()->AccountingRule == ACCT_SUM) { + uint64_t total_left = 0; + uint64_t total_bytes = get_accounting_bytes(); + if (total_bytes < limit) + total_left = limit - total_bytes; + tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, + U64_PRINTF_ARG(total_left), U64_PRINTF_ARG(total_left)); + } else { + uint64_t read_left = 0, write_left = 0; + if (n_bytes_read_in_interval < limit) + read_left = limit - n_bytes_read_in_interval; + if (n_bytes_written_in_interval < limit) + write_left = limit - n_bytes_written_in_interval; + tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, + U64_PRINTF_ARG(read_left), U64_PRINTF_ARG(write_left)); + } } else if (!strcmp(question, "accounting/interval-start")) { *answer = tor_malloc(ISO_TIME_LEN+1); format_iso_time(*answer, interval_start_time); @@ -1010,6 +1046,7 @@ getinfo_helper_accounting(control_connection_t *conn, return 0; } +#ifdef TOR_UNIT_TESTS /** * Manually change the hibernation state. Private; used only by the unit * tests. @@ -1019,4 +1056,5 @@ hibernate_set_state_for_testing_(hibernate_state_t newstate) { hibernate_state = newstate; } +#endif diff --git a/src/or/hibernate.h b/src/or/hibernate.h index d2d6989e10..b9e619c5ad 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,19 +12,23 @@ #ifndef TOR_HIBERNATE_H #define TOR_HIBERNATE_H +#include "testsupport.h" + int accounting_parse_options(const or_options_t *options, int validate_only); -int accounting_is_enabled(const or_options_t *options); +MOCK_DECL(int, accounting_is_enabled, (const or_options_t *options)); int accounting_get_interval_length(void); +MOCK_DECL(time_t, accounting_get_end_time, (void)); void configure_accounting(time_t now); void accounting_run_housekeeping(time_t now); void accounting_add_bytes(size_t n_read, size_t n_written, int seconds); int accounting_record_bandwidth_usage(time_t now, or_state_t *state); void hibernate_begin_shutdown(void); -int we_are_hibernating(void); +MOCK_DECL(int, we_are_hibernating, (void)); void consider_hibernation(time_t now); int getinfo_helper_accounting(control_connection_t *conn, const char *question, char **answer, const char **errmsg); +uint64_t get_accounting_max_total(void); #ifdef HIBERNATE_PRIVATE /** Possible values of hibernate_state */ @@ -45,8 +49,10 @@ typedef enum { HIBERNATE_STATE_INITIAL=5 } hibernate_state_t; +#ifdef TOR_UNIT_TESTS void hibernate_set_state_for_testing_(hibernate_state_t newstate); #endif +#endif #endif diff --git a/src/or/include.am b/src/or/include.am index 65dbeff53e..b44e1099dc 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -1,5 +1,13 @@ bin_PROGRAMS+= src/or/tor -noinst_LIBRARIES+= src/or/libtor.a +noinst_LIBRARIES += \ + src/or/libtor.a +if UNITTESTS_ENABLED +noinst_LIBRARIES += \ + src/or/libtor-testing.a +endif +if COVERAGE_ENABLED +noinst_PROGRAMS+= src/or/tor-cov +endif if BUILD_NT_SERVICES tor_platform_source=src/or/ntmain.c @@ -15,17 +23,12 @@ else evdns_source=src/ext/eventdns.c endif -if CURVE25519_ENABLED -onion_ntor_source=src/or/onion_ntor.c -else -onion_ntor_source= -endif - -src_or_libtor_a_SOURCES = \ +LIBTOR_A_SOURCES = \ src/or/addressmap.c \ src/or/buffers.c \ src/or/channel.c \ src/or/channeltls.c \ + src/or/circpathbias.c \ src/or/circuitbuild.c \ src/or/circuitlist.c \ src/or/circuitmux.c \ @@ -48,6 +51,7 @@ src_or_libtor_a_SOURCES = \ src/or/fp_pair.c \ src/or/geoip.c \ src/or/entrynodes.c \ + src/or/ext_orport.c \ src/or/hibernate.c \ src/or/main.c \ src/or/microdesc.c \ @@ -70,13 +74,17 @@ src_or_libtor_a_SOURCES = \ src/or/routerlist.c \ src/or/routerparse.c \ src/or/routerset.c \ + src/or/scheduler.c \ src/or/statefile.c \ src/or/status.c \ + src/or/onion_ntor.c \ $(evdns_source) \ $(tor_platform_source) \ - $(onion_ntor_source) \ src/or/config_codedigest.c +src_or_libtor_a_SOURCES = $(LIBTOR_A_SOURCES) +src_or_libtor_testing_a_SOURCES = $(LIBTOR_A_SOURCES) + #libtor_a_LIBADD = ../common/libor.a ../common/libor-crypto.a \ # ../common/libor-event.a @@ -90,6 +98,9 @@ AM_CPPFLAGS += -DSHARE_DATADIR="\"$(datadir)\"" \ -DLOCALSTATEDIR="\"$(localstatedir)\"" \ -DBINDIR="\"$(bindir)\"" +src_or_libtor_testing_a_CPPFLAGS = -DTOR_UNIT_TESTS $(AM_CPPFLAGS) +src_or_libtor_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + # -L flags need to go in LDFLAGS. -l flags need to go in LDADD. # This seems to matter nowhere but on windows, but I assure you that it # matters a lot there, and is quite hard to debug if you forget to do it. @@ -100,13 +111,29 @@ src_or_tor_LDADD = src/or/libtor.a src/common/libor.a \ src/common/libor-crypto.a $(LIBDONNA) \ src/common/libor-event.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ + +if COVERAGE_ENABLED +src_or_tor_cov_SOURCES = src/or/tor_main.c +src_or_tor_cov_CPPFLAGS = -DTOR_UNIT_TESTS $(AM_CPPFLAGS) +src_or_tor_cov_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_or_tor_cov_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@ +src_or_tor_cov_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \ + src/common/libor-crypto-testing.a $(LIBDONNA) \ + src/common/libor-event-testing.a \ + @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ +TESTING_TOR_BINARY = ./src/or/tor-cov +else +TESTING_TOR_BINARY = ./src/or/tor +endif ORHEADERS = \ src/or/addressmap.h \ src/or/buffers.h \ src/or/channel.h \ src/or/channeltls.h \ + src/or/circpathbias.h \ src/or/circuitbuild.h \ src/or/circuitlist.h \ src/or/circuitmux.h \ @@ -127,6 +154,7 @@ ORHEADERS = \ src/or/dns.h \ src/or/dnsserv.h \ src/or/eventdns_tor.h \ + src/or/ext_orport.h \ src/or/fp_pair.h \ src/or/geoip.h \ src/or/entrynodes.h \ @@ -155,6 +183,7 @@ ORHEADERS = \ src/or/routerlist.h \ src/or/routerset.h \ src/or/routerparse.h \ + src/or/scheduler.h \ src/or/statefile.h \ src/or/status.h diff --git a/src/or/main.c b/src/or/main.c index bd23141b97..df0cd1bee3 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,6 +13,7 @@ #define MAIN_PRIVATE #include "or.h" #include "addressmap.h" +#include "backtrace.h" #include "buffers.h" #include "channel.h" #include "channeltls.h" @@ -21,11 +22,13 @@ #include "circuituse.h" #include "command.h" #include "config.h" +#include "confparse.h" #include "connection.h" #include "connection_edge.h" #include "connection_or.h" #include "control.h" #include "cpuworker.h" +#include "crypto_s2k.h" #include "directory.h" #include "dirserv.h" #include "dirvote.h" @@ -50,13 +53,17 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "scheduler.h" #include "statefile.h" #include "status.h" +#include "util_process.h" +#include "ext_orport.h" #ifdef USE_DMALLOC #include <dmalloc.h> #include <openssl/crypto.h> #endif #include "memarea.h" +#include "../common/sandbox.h" #ifdef HAVE_EVENT2_EVENT_H #include <event2/event.h> @@ -68,6 +75,16 @@ #include <event2/bufferevent.h> #endif +#ifdef HAVE_SYSTEMD +# if defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) +/* Systemd's use of gcc's __INCLUDE_LEVEL__ extension macro appears to confuse + * Coverity. Here's a kludge to unconfuse it. + */ +# define __INCLUDE_LEVEL__ 2 +# endif +#include <systemd/sd-daemon.h> +#endif + void evdns_shutdown(int); /********* PROTOTYPES **********/ @@ -80,6 +97,7 @@ static void second_elapsed_callback(periodic_timer_t *timer, void *args); static int conn_close_if_marked(int i); static void connection_start_reading_from_linked_conn(connection_t *conn); static int connection_should_read_from_linked_conn(connection_t *conn); +static int run_main_loop_until_done(void); /********* START VARIABLES **********/ @@ -144,7 +162,7 @@ static int called_loop_once = 0; * any longer (a big time jump happened, when we notice our directory is * heinously out-of-date, etc. */ -int can_complete_circuit=0; +static int can_complete_circuits = 0; /** How often do we check for router descriptors that we should download * when we have too little directory info? */ @@ -155,8 +173,6 @@ int can_complete_circuit=0; /** How often do we 'forgive' undownloadable router descriptors and attempt * to download them again? */ #define DESCRIPTOR_FAILURE_RESET_INTERVAL (60*60) -/** How long do we let a directory connection stall before expiring it? */ -#define DIR_CONN_MAX_STALL (5*60) /** Decides our behavior when no logs are configured/before any * logs have been configured. For 0, we log notice to stdout as normal. @@ -167,11 +183,11 @@ int quiet_level = 0; /********* END VARIABLES ************/ /**************************************************************************** -* -* This section contains accessors and other methods on the connection_array -* variables (which are global within this file and unavailable outside it). -* -****************************************************************************/ + * + * This section contains accessors and other methods on the connection_array + * variables (which are global within this file and unavailable outside it). + * + ****************************************************************************/ #if 0 && defined(USE_BUFFEREVENTS) static void @@ -219,6 +235,31 @@ set_buffer_lengths_to_zero(tor_socket_t s) } #endif +/** Return 1 if we have successfully built a circuit, and nothing has changed + * to make us think that maybe we can't. + */ +int +have_completed_a_circuit(void) +{ + return can_complete_circuits; +} + +/** Note that we have successfully built a circuit, so that reachability + * testing and introduction points and so on may be attempted. */ +void +note_that_we_completed_a_circuit(void) +{ + can_complete_circuits = 1; +} + +/** Note that something has happened (like a clock jump, or DisableNetwork) to + * make us think that maybe we can't complete circuits. */ +void +note_that_we_maybe_cant_complete_circuits(void) +{ + can_complete_circuits = 0; +} + /** 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. @@ -351,6 +392,12 @@ connection_remove(connection_t *conn) (int)conn->s, conn_type_to_string(conn->type), smartlist_len(connection_array)); + if (conn->type == CONN_TYPE_AP && conn->socket_family == AF_UNIX) { + log_info(LD_NET, "Closing SOCKS SocksSocket connection"); + } + + control_event_conn_bandwidth(conn); + tor_assert(conn->conn_array_index >= 0); current_index = conn->conn_array_index; connection_unregister_events(conn); /* This is redundant, but cheap. */ @@ -414,6 +461,19 @@ connection_unlink(connection_t *conn) connection_free(conn); } +/** Initialize the global connection list, closeable connection list, + * and active connection list. */ +STATIC void +init_connection_lists(void) +{ + if (!connection_array) + connection_array = smartlist_new(); + if (!closeable_connection_lst) + closeable_connection_lst = smartlist_new(); + if (!active_linked_connection_lst) + active_linked_connection_lst = smartlist_new(); +} + /** Schedule <b>conn</b> to be closed. **/ void add_connection_to_closeable_list(connection_t *conn) @@ -452,15 +512,15 @@ get_connection_array(void) /** Provides the traffic read and written over the life of the process. */ -uint64_t -get_bytes_read(void) +MOCK_IMPL(uint64_t, +get_bytes_read,(void)) { return stats_n_bytes_read; } /* DOCDOC get_bytes_written */ -uint64_t -get_bytes_written(void) +MOCK_IMPL(uint64_t, +get_bytes_written,(void)) { return stats_n_bytes_written; } @@ -507,8 +567,8 @@ connection_is_reading(connection_t *conn) } /** Tell the main loop to stop notifying <b>conn</b> of any read events. */ -void -connection_stop_reading(connection_t *conn) +MOCK_IMPL(void, +connection_stop_reading,(connection_t *conn)) { tor_assert(conn); @@ -532,8 +592,8 @@ connection_stop_reading(connection_t *conn) } /** Tell the main loop to start notifying <b>conn</b> of any read events. */ -void -connection_start_reading(connection_t *conn) +MOCK_IMPL(void, +connection_start_reading,(connection_t *conn)) { tor_assert(conn); @@ -572,8 +632,8 @@ connection_is_writing(connection_t *conn) } /** Tell the main loop to stop notifying <b>conn</b> of any write events. */ -void -connection_stop_writing(connection_t *conn) +MOCK_IMPL(void, +connection_stop_writing,(connection_t *conn)) { tor_assert(conn); @@ -598,8 +658,8 @@ connection_stop_writing(connection_t *conn) } /** Tell the main loop to start notifying <b>conn</b> of any write events. */ -void -connection_start_writing(connection_t *conn) +MOCK_IMPL(void, +connection_start_writing,(connection_t *conn)) { tor_assert(conn); @@ -687,7 +747,7 @@ connection_stop_reading_from_linked_conn(connection_t *conn) } /** Close all connections that have been scheduled to get closed. */ -static void +STATIC void close_closeable_connections(void) { int i; @@ -902,16 +962,7 @@ conn_close_if_marked(int i) return 0; } if (connection_wants_to_flush(conn)) { - int severity; - if (conn->type == CONN_TYPE_EXIT || - (conn->type == CONN_TYPE_OR && server_mode(get_options())) || - (conn->type == CONN_TYPE_DIR && conn->purpose == DIR_PURPOSE_SERVER)) - severity = LOG_INFO; - else - severity = LOG_NOTICE; - /* XXXX Maybe allow this to happen a certain amount per hour; it usually - * is meaningless. */ - log_fn(severity, LD_NET, "We stalled too much while trying to write %d " + log_fn(LOG_INFO, LD_NET, "We stalled too much while trying to write %d " "bytes to address %s. If this happens a lot, either " "something is wrong with your network connection, or " "something is wrong with theirs. " @@ -989,19 +1040,10 @@ directory_info_has_arrived(time_t now, int from_cache) } if (server_mode(options) && !net_is_disabled() && !from_cache && - (can_complete_circuit || !any_predicted_circuits(now))) + (have_completed_a_circuit() || !any_predicted_circuits(now))) consider_testing_reachability(1, 1); } -/** How long do we wait before killing OR connections with no circuits? - * In Tor versions up to 0.2.1.25 and 0.2.2.12-alpha, we waited 15 minutes - * before cancelling these connections, which caused fast relays to accrue - * many many idle connections. Hopefully 3 minutes is low enough that - * it kills most idle connections, without being so low that we cause - * clients to bounce on and off. - */ -#define IDLE_OR_CONN_TIMEOUT 180 - /** Perform regular maintenance tasks for a single connection. This * function gets run once per second per connection by run_scheduled_events. */ @@ -1012,6 +1054,8 @@ run_connection_housekeeping(int i, time_t now) connection_t *conn = smartlist_get(connection_array, i); const or_options_t *options = get_options(); or_connection_t *or_conn; + channel_t *chan = NULL; + int have_any_circuits; int past_keepalive = now >= conn->timestamp_lastwritten + options->KeepalivePeriod; @@ -1028,9 +1072,11 @@ run_connection_housekeeping(int i, time_t now) * if a server or received if a client) for 5 min */ if (conn->type == CONN_TYPE_DIR && ((DIR_CONN_IS_SERVER(conn) && - conn->timestamp_lastwritten + DIR_CONN_MAX_STALL < now) || + conn->timestamp_lastwritten + + options->TestingDirConnectionMaxStall < now) || (!DIR_CONN_IS_SERVER(conn) && - conn->timestamp_lastread + DIR_CONN_MAX_STALL < now))) { + conn->timestamp_lastread + + options->TestingDirConnectionMaxStall < now))) { log_info(LD_DIR,"Expiring wedged directory conn (fd %d, purpose %d)", (int)conn->s, conn->purpose); /* This check is temporary; it's to let us know whether we should consider @@ -1059,8 +1105,18 @@ run_connection_housekeeping(int i, time_t now) tor_assert(conn->outbuf); #endif + chan = TLS_CHAN_TO_BASE(or_conn->chan); + tor_assert(chan); + + if (channel_num_circuits(chan) != 0) { + have_any_circuits = 1; + chan->timestamp_last_had_circuits = now; + } else { + have_any_circuits = 0; + } + if (channel_is_bad_for_new_circs(TLS_CHAN_TO_BASE(or_conn->chan)) && - !connection_or_get_num_circuits(or_conn)) { + ! have_any_circuits) { /* It's bad for new circuits, and has no unmarked circuits on it: * mark it now. */ log_info(LD_OR, @@ -1079,19 +1135,22 @@ run_connection_housekeeping(int i, time_t now) connection_or_close_normally(TO_OR_CONN(conn), 0); } } else if (we_are_hibernating() && - !connection_or_get_num_circuits(or_conn) && + ! have_any_circuits && !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].", (int)conn->s,conn->address, conn->port); connection_or_close_normally(TO_OR_CONN(conn), 1); - } else if (!connection_or_get_num_circuits(or_conn) && - now >= or_conn->timestamp_last_added_nonpadding + - IDLE_OR_CONN_TIMEOUT) { + } else if (!have_any_circuits && + now - or_conn->idle_timeout >= + chan->timestamp_last_had_circuits) { log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) " - "[idle %d].", (int)conn->s,conn->address, conn->port, - (int)(now - or_conn->timestamp_last_added_nonpadding)); + "[no circuits for %d; timeout %d; %scanonical].", + (int)conn->s, conn->address, conn->port, + (int)(now - chan->timestamp_last_had_circuits), + or_conn->idle_timeout, + or_conn->is_canonical ? "" : "non"); connection_or_close_normally(TO_OR_CONN(conn), 0); } else if ( now >= or_conn->timestamp_lastempty + options->KeepalivePeriod*10 && @@ -1143,6 +1202,18 @@ get_signewnym_epoch(void) return newnym_epoch; } +static time_t time_to_check_descriptor = 0; +/** + * Update our schedule so that we'll check whether we need to update our + * descriptor immediately, rather than after up to CHECK_DESCRIPTOR_INTERVAL + * seconds. + */ +void +reschedule_descriptor_update_check(void) +{ + time_to_check_descriptor = 0; +} + /** Perform regular maintenance tasks. This function gets run once per * second by second_elapsed_callback(). */ @@ -1152,8 +1223,7 @@ run_scheduled_events(time_t now) static time_t last_rotated_x509_certificate = 0; static time_t time_to_check_v3_certificate = 0; static time_t time_to_check_listeners = 0; - static time_t time_to_check_descriptor = 0; - static time_t time_to_shrink_memory = 0; + static time_t time_to_download_networkstatus = 0; static time_t time_to_try_getting_descriptors = 0; static time_t time_to_reset_descriptor_failures = 0; static time_t time_to_add_entropy = 0; @@ -1176,22 +1246,12 @@ run_scheduled_events(time_t now) int i; int have_dir_info; - /** 0. See if we've been asked to shut down and our timeout has + /* 0. See if we've been asked to shut down and our timeout has * expired; or if our bandwidth limits are exhausted and we * should hibernate; or if it's time to wake up from hibernation. */ 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 && @@ -1203,7 +1263,7 @@ run_scheduled_events(time_t now) /* 0c. If we've deferred log messages for the controller, handle them now */ flush_pending_log_callbacks(); - /** 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys, + /* 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys, * shut down and restart all cpuworkers, and update the directory if * necessary. */ @@ -1211,7 +1271,7 @@ run_scheduled_events(time_t now) get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME < now) { log_info(LD_GENERAL,"Rotating onion key."); rotate_onion_key(); - cpuworkers_rotate(); + cpuworkers_rotate_keyinfo(); if (router_rebuild_descriptor(1)<0) { log_info(LD_CONFIG, "Couldn't rebuild router descriptor"); } @@ -1219,7 +1279,8 @@ run_scheduled_events(time_t now) router_upload_dir_desc_to_dirservers(0); } - if (!options->DisableNetwork && time_to_try_getting_descriptors < now) { + if (!should_delay_dir_fetches(options, NULL) && + time_to_try_getting_descriptors < now) { update_all_descriptor_downloads(now); update_extrainfo_downloads(now); if (router_have_minimum_dir_info()) @@ -1234,10 +1295,10 @@ run_scheduled_events(time_t now) now + DESCRIPTOR_FAILURE_RESET_INTERVAL; } - if (options->UseBridges) + if (options->UseBridges && !options->DisableNetwork) fetch_bridge_descriptors(options, now); - /** 1b. Every MAX_SSL_KEY_LIFETIME_INTERNAL seconds, we change our + /* 1b. Every MAX_SSL_KEY_LIFETIME_INTERNAL seconds, we change our * TLS context. */ if (!last_rotated_x509_certificate) last_rotated_x509_certificate = now; @@ -1263,7 +1324,7 @@ run_scheduled_events(time_t now) time_to_add_entropy = now + ENTROPY_INTERVAL; } - /** 1c. If we have to change the accounting interval or record + /* 1c. If we have to change the accounting interval or record * bandwidth used in this accounting interval, do so. */ if (accounting_is_enabled(options)) accounting_run_housekeeping(now); @@ -1276,7 +1337,7 @@ run_scheduled_events(time_t now) dirserv_test_reachability(now); } - /** 1d. Periodically, we discount older stability information so that new + /* 1d. Periodically, we discount older stability information so that new * stability info counts more, and save the stability information to disk as * appropriate. */ if (time_to_downrate_stability < now) @@ -1337,6 +1398,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->HiddenServiceStatistics) { + time_t next_write = rep_hist_hs_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; + } if (options->ExitPortStatistics) { time_t next_write = rep_hist_exit_stats_write(time_to_write_stats_files); if (next_write && next_write < next_time_to_write_stats_files) @@ -1381,7 +1447,7 @@ run_scheduled_events(time_t now) if (time_to_clean_caches < now) { rep_history_clean(now - options->RephistTrackTime); rend_cache_clean(now); - rend_cache_clean_v2_descs_as_dir(now); + rend_cache_clean_v2_descs_as_dir(now, 0); microdesc_cache_rebuild(NULL, 0); #define CLEAN_CACHES_INTERVAL (30*60) time_to_clean_caches = now + CLEAN_CACHES_INTERVAL; @@ -1395,7 +1461,7 @@ run_scheduled_events(time_t now) dns_init(); } - /** 2. Periodically, we consider force-uploading our descriptor + /* 2. Periodically, we consider force-uploading our descriptor * (if we've passed our internal checks). */ /** How often do we check whether part of our router info has changed in a @@ -1415,7 +1481,7 @@ run_scheduled_events(time_t now) /* also, check religiously for reachability, if it's within the first * 20 minutes of our uptime. */ if (is_server && - (can_complete_circuit || !any_predicted_circuits(now)) && + (have_completed_a_circuit() || !any_predicted_circuits(now)) && !we_are_hibernating()) { if (stats_n_seconds_working < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { consider_testing_reachability(1, dirport_reachability_count==0); @@ -1438,22 +1504,29 @@ run_scheduled_events(time_t now) /* If any networkstatus documents are no longer recent, we need to * update all the descriptors' running status. */ - /* purge obsolete entries */ - networkstatus_v2_list_clean(now); /* Remove dead routers. */ routerlist_remove_old_routers(); + } - /* Also, once per minute, check whether we want to download any - * networkstatus documents. - */ + /* 2c. Every minute (or every second if TestingTorNetwork), check + * whether we want to download any networkstatus documents. */ + +/* How often do we check whether we should download network status + * documents? */ +#define networkstatus_dl_check_interval(o) ((o)->TestingTorNetwork ? 1 : 60) + + if (!should_delay_dir_fetches(options, NULL) && + time_to_download_networkstatus < now) { + time_to_download_networkstatus = + now + networkstatus_dl_check_interval(options); update_networkstatus_downloads(now); } - /** 2c. Let directory voting happen. */ + /* 2c. Let directory voting happen. */ if (authdir_mode_v3(options)) dirvote_act(options, now); - /** 3a. Every second, we examine pending circuits and prune the + /* 3a. Every second, we examine pending circuits and prune the * ones which have been pending for more than a few seconds. * We do this before step 4, so it can try building more if * it's not comfortable with the number of available circuits. @@ -1462,83 +1535,74 @@ run_scheduled_events(time_t now) * it can't, currently), we should do this more often.) */ circuit_expire_building(); - /** 3b. Also look at pending streams and prune the ones that 'began' + /* 3b. Also look at pending streams and prune the ones that 'began' * a long time ago but haven't gotten a 'connected' yet. * Do this before step 4, so we can put them back into pending * state to be picked up by the new circuit. */ connection_ap_expire_beginning(); - /** 3c. And expire connections that we've held open for too long. + /* 3c. And expire connections that we've held open for too long. */ connection_expire_held_open(); - /** 3d. And every 60 seconds, we relaunch listeners if any died. */ + /* 3d. And every 60 seconds, we relaunch listeners if any died. */ if (!net_is_disabled() && time_to_check_listeners < now) { retry_all_listeners(NULL, NULL, 0); time_to_check_listeners = now+60; } - /** 4. Every second, we try a new circuit if there are no valid + /* 4. Every second, we try a new circuit if there are no valid * circuits. Every NewCircuitPeriod seconds, we expire circuits * that became dirty more than MaxCircuitDirtiness seconds ago, * and we make a new circ if there are no clean circuits. */ have_dir_info = router_have_minimum_dir_info(); - if (have_dir_info && !net_is_disabled()) + if (have_dir_info && !net_is_disabled()) { circuit_build_needed_circs(now); + } else { + circuit_expire_old_circs_as_needed(now); + } /* every 10 seconds, but not at the same second as other such events */ if (now % 10 == 5) circuit_expire_old_circuits_serverside(now); - /** 5. We do housekeeping for each connection... */ + /* 5. We do housekeeping for each connection... */ connection_or_set_bad_connections(NULL, 0); for (i=0;i<smartlist_len(connection_array);i++) { run_connection_housekeeping(i, now); } - if (time_to_shrink_memory < now) { - SMARTLIST_FOREACH(connection_array, connection_t *, conn, { - if (conn->outbuf) - buf_shrink(conn->outbuf); - if (conn->inbuf) - buf_shrink(conn->inbuf); - }); - clean_cell_pool(); - buf_shrink_freelists(0); -/** How often do we check buffers and pools for empty space that can be - * deallocated? */ -#define MEM_SHRINK_INTERVAL (60) - time_to_shrink_memory = now + MEM_SHRINK_INTERVAL; - } - - /** 6. And remove any marked circuits... */ + + /* 6. And remove any marked circuits... */ circuit_close_all_marked(); - /** 7. And upload service descriptors if necessary. */ - if (can_complete_circuit && !net_is_disabled()) { + /* 7. And upload service descriptors if necessary. */ + if (have_completed_a_circuit() && !net_is_disabled()) { rend_consider_services_upload(now); rend_consider_descriptor_republication(); } - /** 8. and blow away any connections that need to die. have to do this now, + /* 8. and blow away any connections that need to die. have to do this now, * because if we marked a conn for close and left its socket -1, then * we'll pass it to poll/select and bad things will happen. */ close_closeable_connections(); - /** 8b. And if anything in our state is ready to get flushed to disk, we + /* 8b. And if anything in our state is ready to get flushed to disk, we * flush it. */ or_state_save(now); - /** 8c. Do channel cleanup just like for connections */ + /* 8c. Do channel cleanup just like for connections */ channel_run_cleanup(); channel_listener_run_cleanup(); - /** 9. and if we're a server, check whether our DNS is telling stories to - * us. */ + /* 9. and if we're an exit node, check whether our DNS is telling stories + * to us. */ if (!net_is_disabled() && - public_server_mode(options) && time_to_check_for_correct_dns < now) { + public_server_mode(options) && + time_to_check_for_correct_dns < now && + ! router_my_exit_policy_is_reject_star()) { if (!time_to_check_for_correct_dns) { time_to_check_for_correct_dns = now + 60 + crypto_rand_int(120); } else { @@ -1548,7 +1612,7 @@ run_scheduled_events(time_t now) } } - /** 10. write bridge networkstatus file to disk */ + /* 10. write bridge networkstatus file to disk */ if (options->BridgeAuthoritativeDir && time_to_write_bridge_status_file < now) { networkstatus_dump_bridge_status_to_file(now); @@ -1556,7 +1620,7 @@ run_scheduled_events(time_t now) time_to_write_bridge_status_file = now+BRIDGE_STATUSFILE_INTERVAL; } - /** 11. check the port forwarding app */ + /* 11. check the port forwarding app */ if (!net_is_disabled() && time_to_check_port_forwarding < now && options->PortForwarding && @@ -1574,11 +1638,11 @@ run_scheduled_events(time_t now) time_to_check_port_forwarding = now+PORT_FORWARDING_CHECK_INTERVAL; } - /** 11b. check pending unconfigured managed proxies */ + /* 11b. check pending unconfigured managed proxies */ if (!net_is_disabled() && pt_proxies_configuration_pending()) pt_configure_remaining_proxies(); - /** 12. write the heartbeat message */ + /* 12. write the heartbeat message */ if (options->HeartbeatPeriod && time_to_next_heartbeat <= now) { if (time_to_next_heartbeat) /* don't log the first heartbeat */ @@ -1638,35 +1702,42 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written); control_event_stream_bandwidth_used(); + control_event_conn_bandwidth_used(); + control_event_circ_bandwidth_used(); + control_event_circuit_cell_stats(); if (server_mode(options) && !net_is_disabled() && seconds_elapsed > 0 && - can_complete_circuit && + have_completed_a_circuit() && stats_n_seconds_working / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT != (stats_n_seconds_working+seconds_elapsed) / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { /* every 20 minutes, check and complain if necessary */ const routerinfo_t *me = router_get_my_routerinfo(); if (me && !check_whether_orport_reachable()) { + char *address = tor_dup_ip(me->addr); log_warn(LD_CONFIG,"Your server (%s:%d) has not managed to confirm that " "its ORPort is reachable. Please check your firewalls, ports, " "address, /etc/hosts file, etc.", - me->address, me->or_port); + address, me->or_port); control_event_server_status(LOG_WARN, "REACHABILITY_FAILED ORADDRESS=%s:%d", - me->address, me->or_port); + address, me->or_port); + tor_free(address); } if (me && !check_whether_dirport_reachable()) { + char *address = tor_dup_ip(me->addr); log_warn(LD_CONFIG, "Your server (%s:%d) has not managed to confirm that its " "DirPort is reachable. Please check your firewalls, ports, " "address, /etc/hosts file, etc.", - me->address, me->dir_port); + address, me->dir_port); control_event_server_status(LOG_WARN, "REACHABILITY_FAILED DIRADDRESS=%s:%d", - me->address, me->dir_port); + address, me->dir_port); + tor_free(address); } } @@ -1686,6 +1757,19 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) current_second = now; /* remember which second it is, for next time */ } +#ifdef HAVE_SYSTEMD_209 +static periodic_timer_t *systemd_watchdog_timer = NULL; + +/** Libevent callback: invoked to reset systemd watchdog. */ +static void +systemd_watchdog_callback(periodic_timer_t *timer, void *arg) +{ + (void)timer; + (void)arg; + sd_notify(0, "WATCHDOG=1"); +} +#endif + #ifndef USE_BUFFEREVENTS /** Timer: used to invoke refill_callback(). */ static periodic_timer_t *refill_timer = NULL; @@ -1728,7 +1812,7 @@ refill_callback(periodic_timer_t *timer, void *arg) accounting_add_bytes(bytes_read, bytes_written, seconds_rolled_over); if (milliseconds_elapsed > 0) - connection_bucket_refill(milliseconds_elapsed, now.tv_sec); + connection_bucket_refill(milliseconds_elapsed, (time_t)now.tv_sec); stats_prev_global_read_bucket = global_read_bucket; stats_prev_global_write_bucket = global_write_bucket; @@ -1820,6 +1904,10 @@ do_hup(void) return -1; } options = get_options(); /* they have changed now */ + /* Logs are only truncated the first time they are opened, but were + probably intended to be cleaned up on signal. */ + if (options->TruncateLogFile) + truncate_logs(); } else { char *msg = NULL; log_notice(LD_GENERAL, "Not reloading config file: the controller told " @@ -1856,19 +1944,18 @@ do_hup(void) * force a retry there. */ if (server_mode(options)) { - /* Restart cpuworker and dnsworker processes, so they get up-to-date + /* Update cpuworker and dnsworker processes, so they get up-to-date * configuration options. */ - cpuworkers_rotate(); + cpuworkers_rotate_keyinfo(); dns_reset(); } return 0; } /** Tor main loop. */ -/* static */ int +int do_main_loop(void) { - int loop_result; time_t now; /* initialize dns resolve map, spawn workers if needed */ @@ -1900,9 +1987,6 @@ do_main_loop(void) } } - /* Set up the packed_cell_t memory pool. */ - init_cell_pool(); - /* Set up our buckets */ connection_bucket_init(); #ifndef USE_BUFFEREVENTS @@ -1917,9 +2001,6 @@ do_main_loop(void) log_warn(LD_DIR, "Couldn't load all cached v3 certificates. Starting anyway."); } - if (router_reload_v2_networkstatus()) { - return -1; - } if (router_reload_consensus_networkstatus()) { return -1; } @@ -1951,6 +2032,28 @@ do_main_loop(void) tor_assert(second_timer); } +#ifdef HAVE_SYSTEMD_209 + uint64_t watchdog_delay; + /* set up systemd watchdog notification. */ + if (sd_watchdog_enabled(1, &watchdog_delay) > 0) { + if (! systemd_watchdog_timer) { + struct timeval watchdog; + /* The manager will "act on" us if we don't send them a notification + * every 'watchdog_delay' microseconds. So, send notifications twice + * that often. */ + watchdog_delay /= 2; + watchdog.tv_sec = watchdog_delay / 1000000; + watchdog.tv_usec = watchdog_delay % 1000000; + + systemd_watchdog_timer = periodic_timer_new(tor_libevent_get_base(), + &watchdog, + systemd_watchdog_callback, + NULL); + tor_assert(systemd_watchdog_timer); + } + } +#endif + #ifndef USE_BUFFEREVENTS if (!refill_timer) { struct timeval refill_interval; @@ -1967,51 +2070,92 @@ do_main_loop(void) } #endif - for (;;) { - if (nt_service_is_stopping()) - return 0; +#ifdef HAVE_SYSTEMD + { + const int r = sd_notify(0, "READY=1"); + if (r < 0) { + log_warn(LD_GENERAL, "Unable to send readiness to systemd: %s", + strerror(r)); + } else if (r > 0) { + log_notice(LD_GENERAL, "Signaled readiness to systemd"); + } else { + log_info(LD_GENERAL, "Systemd NOTIFY_SOCKET not present."); + } + } +#endif + + return run_main_loop_until_done(); +} + +/** + * Run the main loop a single time. Return 0 for "exit"; -1 for "exit with + * error", and 1 for "run this again." + */ +static int +run_main_loop_once(void) +{ + int loop_result; + + if (nt_service_is_stopping()) + return 0; #ifndef _WIN32 - /* Make it easier to tell whether libevent failure is our fault or not. */ - errno = 0; + /* Make it easier to tell whether libevent failure is our fault or not. */ + errno = 0; #endif - /* All active linked conns should get their read events activated. */ - SMARTLIST_FOREACH(active_linked_connection_lst, connection_t *, conn, - event_active(conn->read_event, EV_READ, 1)); - called_loop_once = smartlist_len(active_linked_connection_lst) ? 1 : 0; - - update_approx_time(time(NULL)); - - /* poll until we have an event, or the second ends, or until we have - * some active linked connections to trigger events for. */ - loop_result = event_base_loop(tor_libevent_get_base(), - called_loop_once ? EVLOOP_ONCE : 0); - - /* let catch() handle things like ^c, and otherwise don't worry about it */ - if (loop_result < 0) { - int e = tor_socket_errno(-1); - /* let the program survive things like ^z */ - if (e != EINTR && !ERRNO_IS_EINPROGRESS(e)) { - log_err(LD_NET,"libevent call with %s failed: %s [%d]", - tor_libevent_get_method(), tor_socket_strerror(e), e); - return -1; + /* All active linked conns should get their read events activated. */ + SMARTLIST_FOREACH(active_linked_connection_lst, connection_t *, conn, + event_active(conn->read_event, EV_READ, 1)); + called_loop_once = smartlist_len(active_linked_connection_lst) ? 1 : 0; + + update_approx_time(time(NULL)); + + /* poll until we have an event, or the second ends, or until we have + * some active linked connections to trigger events for. */ + loop_result = event_base_loop(tor_libevent_get_base(), + called_loop_once ? EVLOOP_ONCE : 0); + + /* let catch() handle things like ^c, and otherwise don't worry about it */ + if (loop_result < 0) { + int e = tor_socket_errno(-1); + /* let the program survive things like ^z */ + if (e != EINTR && !ERRNO_IS_EINPROGRESS(e)) { + log_err(LD_NET,"libevent call with %s failed: %s [%d]", + tor_libevent_get_method(), tor_socket_strerror(e), e); + return -1; #ifndef _WIN32 - } else if (e == EINVAL) { - log_warn(LD_NET, "EINVAL from libevent: should you upgrade libevent?"); - if (got_libevent_error()) - return -1; + } else if (e == EINVAL) { + log_warn(LD_NET, "EINVAL from libevent: should you upgrade libevent?"); + if (got_libevent_error()) + return -1; #endif - } else { - if (ERRNO_IS_EINPROGRESS(e)) - log_warn(LD_BUG, - "libevent call returned EINPROGRESS? Please report."); - log_debug(LD_NET,"libevent call interrupted."); - /* You can't trust the results of this poll(). Go back to the - * top of the big for loop. */ - continue; - } + } else { + if (ERRNO_IS_EINPROGRESS(e)) + log_warn(LD_BUG, + "libevent call returned EINPROGRESS? Please report."); + log_debug(LD_NET,"libevent call interrupted."); + /* You can't trust the results of this poll(). Go back to the + * top of the big for loop. */ + return 1; } } + + return 1; +} + +/** Run the run_main_loop_once() function until it declares itself done, + * and return its final return value. + * + * Shadow won't invoke this function, so don't fill it up with things. + */ +static int +run_main_loop_until_done(void) +{ + int loop_result = 1; + do { + loop_result = run_main_loop_once(); + } while (loop_result == 1); + return loop_result; } #ifndef _WIN32 /* Only called when we're willing to use signals */ @@ -2045,6 +2189,9 @@ process_signal(uintptr_t sig) tor_cleanup(); exit(0); } +#ifdef HAVE_SYSTEMD + sd_notify(0, "STOPPING=1"); +#endif hibernate_begin_shutdown(); break; #ifdef SIGPIPE @@ -2064,17 +2211,22 @@ process_signal(uintptr_t sig) control_event_signal(sig); break; case SIGHUP: +#ifdef HAVE_SYSTEMD + sd_notify(0, "RELOADING=1"); +#endif if (do_hup() < 0) { log_warn(LD_CONFIG,"Restart failed (config error?). Exiting."); tor_cleanup(); exit(1); } +#ifdef HAVE_SYSTEMD + sd_notify(0, "READY=1"); +#endif control_event_signal(sig); break; #ifdef SIGCHLD case SIGCHLD: - while (waitpid(-1,NULL,WNOHANG) > 0) ; /* keep reaping until no more - zombies */ + notify_pending_waitpid_callbacks(); break; #endif case SIGNEWNYM: { @@ -2093,12 +2245,16 @@ process_signal(uintptr_t sig) addressmap_clear_transient(); control_event_signal(sig); break; + case SIGHEARTBEAT: + log_heartbeat(time(NULL)); + control_event_signal(sig); + break; } } /** Returns Tor's uptime. */ -long -get_uptime(void) +MOCK_IMPL(long, +get_uptime,(void)) { return stats_n_seconds_working; } @@ -2118,7 +2274,6 @@ dumpmemusage(int severity) dump_routerlist_mem_usage(severity); dump_cell_pool_usage(severity); dump_dns_mem_usage(severity); - buf_dump_freelist_sizes(severity); tor_log_mallinfo(severity); } @@ -2292,21 +2447,24 @@ handle_signals(int is_parent) /** Main entry point for the Tor command-line client. */ -/* static */ int +int tor_init(int argc, char *argv[]) { - char buf[256]; - int i, quiet = 0; + char progname[256]; + int quiet = 0; + time_of_process_start = time(NULL); - if (!connection_array) - connection_array = smartlist_new(); - if (!closeable_connection_lst) - closeable_connection_lst = smartlist_new(); - if (!active_linked_connection_lst) - active_linked_connection_lst = smartlist_new(); + init_connection_lists(); /* Have the log set up with our application name. */ - tor_snprintf(buf, sizeof(buf), "Tor %s", get_version()); - log_set_application_name(buf); + tor_snprintf(progname, sizeof(progname), "Tor %s", get_version()); + log_set_application_name(progname); + + /* Set up the crypto nice and early */ + if (crypto_early_init() < 0) { + log_err(LD_GENERAL, "Unable to initialize the crypto subsystem!"); + return -1; + } + /* Initialize the history structures. */ rep_hist_init(); /* Initialize the service cache. */ @@ -2314,17 +2472,31 @@ tor_init(int argc, char *argv[]) addressmap_init(); /* Init the client dns cache. Do it always, since it's * cheap. */ + { /* We search for the "quiet" option first, since it decides whether we * will log anything at all to the command line. */ - for (i=1;i<argc;++i) { - if (!strcmp(argv[i], "--hush")) - quiet = 1; - if (!strcmp(argv[i], "--quiet")) - quiet = 2; - /* --version implies --quiet */ - if (!strcmp(argv[i], "--version")) - quiet = 2; + config_line_t *opts = NULL, *cmdline_opts = NULL; + const config_line_t *cl; + (void) config_parse_commandline(argc, argv, 1, &opts, &cmdline_opts); + for (cl = cmdline_opts; cl; cl = cl->next) { + if (!strcmp(cl->key, "--hush")) + quiet = 1; + if (!strcmp(cl->key, "--quiet") || + !strcmp(cl->key, "--dump-config")) + quiet = 2; + /* --version, --digests, and --help imply --hush */ + if (!strcmp(cl->key, "--version") || !strcmp(cl->key, "--digests") || + !strcmp(cl->key, "--list-torrc-options") || + !strcmp(cl->key, "--library-versions") || + !strcmp(cl->key, "-h") || !strcmp(cl->key, "--help")) { + if (quiet < 1) + quiet = 1; + } + } + config_free_lines(opts); + config_free_lines(cmdline_opts); } + /* give it somewhere to log to initially */ switch (quiet) { case 2: @@ -2346,11 +2518,12 @@ tor_init(int argc, char *argv[]) #else ""; #endif - log_notice(LD_GENERAL, "Tor v%s %srunning on %s with Libevent %s " - "and OpenSSL %s.", version, bev_str, + log_notice(LD_GENERAL, "Tor v%s %srunning on %s with Libevent %s, " + "OpenSSL %s and Zlib %s.", version, bev_str, get_uname(), tor_libevent_get_version_str(), - crypto_openssl_get_version_str()); + crypto_openssl_get_version_str(), + tor_zlib_get_version_str()); log_notice(LD_GENERAL, "Tor can't help you if you use it wrong! " "Learn how to be safe at " @@ -2390,6 +2563,9 @@ tor_init(int argc, char *argv[]) return -1; } stream_choice_seed_weak_rng(); + if (tor_init_libevent_rng() < 0) { + log_warn(LD_NET, "Problem initializing libevent RNG."); + } return 0; } @@ -2488,19 +2664,24 @@ tor_free_all(int postfork) channel_tls_free_all(); channel_free_all(); connection_free_all(); - buf_shrink_freelists(1); + scheduler_free_all(); memarea_clear_freelist(); nodelist_free_all(); microdesc_free_all(); + ext_orport_free_all(); + control_free_all(); + sandbox_free_getaddrinfo_cache(); if (!postfork) { config_free_all(); or_state_free_all(); router_free_all(); policies_free_all(); } - free_cell_pool(); if (!postfork) { tor_tls_free_all(); +#ifndef _WIN32 + tor_getpwnam(NULL); +#endif } /* stuff in main.c */ @@ -2532,10 +2713,19 @@ tor_cleanup(void) time_t now = time(NULL); /* Remove our pid file. We don't care if there was an error when we * unlink, nothing we could do about it anyways. */ - if (options->PidFile) - unlink(options->PidFile); - if (options->ControlPortWriteToFile) - unlink(options->ControlPortWriteToFile); + if (options->PidFile) { + if (unlink(options->PidFile) != 0) { + log_warn(LD_FS, "Couldn't unlink pid file %s: %s", + options->PidFile, strerror(errno)); + } + } + if (options->ControlPortWriteToFile) { + if (unlink(options->ControlPortWriteToFile) != 0) { + log_warn(LD_FS, "Couldn't unlink control port file %s: %s", + options->ControlPortWriteToFile, + strerror(errno)); + } + } if (accounting_is_enabled(options)) accounting_record_bandwidth_usage(now, get_or_state()); or_state_mark_dirty(get_or_state(), 0); /* force an immediate save. */ @@ -2558,7 +2748,7 @@ tor_cleanup(void) } /** Read/create keys as needed, and echo our fingerprint to stdout. */ -/* static */ int +static int do_list_fingerprint(void) { char buf[FINGERPRINT_LEN+1]; @@ -2566,7 +2756,7 @@ do_list_fingerprint(void) const char *nickname = get_options()->Nickname; if (!server_mode(get_options())) { log_err(LD_GENERAL, - "Clients don't have long-term identity keys. Exiting.\n"); + "Clients don't have long-term identity keys. Exiting."); return -1; } tor_assert(nickname); @@ -2588,46 +2778,266 @@ do_list_fingerprint(void) /** Entry point for password hashing: take the desired password from * the command line, and print its salted hash to stdout. **/ -/* static */ void +static void do_hash_password(void) { char output[256]; - char key[S2K_SPECIFIER_LEN+DIGEST_LEN]; + char key[S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN]; - crypto_rand(key, S2K_SPECIFIER_LEN-1); - key[S2K_SPECIFIER_LEN-1] = (uint8_t)96; /* Hash 64 K of data. */ - secret_to_key(key+S2K_SPECIFIER_LEN, DIGEST_LEN, + crypto_rand(key, S2K_RFC2440_SPECIFIER_LEN-1); + key[S2K_RFC2440_SPECIFIER_LEN-1] = (uint8_t)96; /* Hash 64 K of data. */ + secret_to_key_rfc2440(key+S2K_RFC2440_SPECIFIER_LEN, DIGEST_LEN, get_options()->command_arg, strlen(get_options()->command_arg), key); base16_encode(output, sizeof(output), key, sizeof(key)); printf("16:%s\n",output); } -#if defined (WINCE) -int -find_flashcard_path(PWCHAR path, size_t size) +/** Entry point for configuration dumping: write the configuration to + * stdout. */ +static int +do_dump_config(void) { - WIN32_FIND_DATA d = {0}; - HANDLE h = NULL; - - if (!path) + const or_options_t *options = get_options(); + const char *arg = options->command_arg; + int how; + char *opts; + if (!strcmp(arg, "short")) { + how = OPTIONS_DUMP_MINIMAL; + } else if (!strcmp(arg, "non-builtin")) { + how = OPTIONS_DUMP_DEFAULTS; + } else if (!strcmp(arg, "full")) { + how = OPTIONS_DUMP_ALL; + } else { + printf("%s is not a recognized argument to --dump-config. " + "Please select 'short', 'non-builtin', or 'full'", arg); return -1; + } - h = FindFirstFlashCard(&d); - if (h == INVALID_HANDLE_VALUE) - return -1; + opts = options_dump(options, how); + printf("%s", opts); + tor_free(opts); - if (wcslen(d.cFileName) == 0) { - FindClose(h); - return -1; + return 0; +} + +static void +init_addrinfo(void) +{ + char hname[256]; + + // host name to sandbox + gethostname(hname, sizeof(hname)); + sandbox_add_addrinfo(hname); +} + +static sandbox_cfg_t* +sandbox_init_filter(void) +{ + const or_options_t *options = get_options(); + sandbox_cfg_t *cfg = sandbox_cfg_new(); + int i; + + sandbox_cfg_allow_openat_filename(&cfg, + get_datadir_fname("cached-status")); + +#define OPEN(name) \ + sandbox_cfg_allow_open_filename(&cfg, tor_strdup(name)) + +#define OPEN_DATADIR(name) \ + sandbox_cfg_allow_open_filename(&cfg, get_datadir_fname(name)) + +#define OPEN_DATADIR2(name, name2) \ + sandbox_cfg_allow_open_filename(&cfg, get_datadir_fname2((name), (name2))) + +#define OPEN_DATADIR_SUFFIX(name, suffix) do { \ + OPEN_DATADIR(name); \ + OPEN_DATADIR(name suffix); \ + } while (0) + +#define OPEN_DATADIR2_SUFFIX(name, name2, suffix) do { \ + OPEN_DATADIR2(name, name2); \ + OPEN_DATADIR2(name, name2 suffix); \ + } while (0) + + OPEN_DATADIR_SUFFIX("cached-certs", ".tmp"); + OPEN_DATADIR_SUFFIX("cached-consensus", ".tmp"); + OPEN_DATADIR_SUFFIX("unverified-consensus", ".tmp"); + OPEN_DATADIR_SUFFIX("unverified-microdesc-consensus", ".tmp"); + OPEN_DATADIR_SUFFIX("cached-microdesc-consensus", ".tmp"); + OPEN_DATADIR_SUFFIX("cached-microdescs", ".tmp"); + OPEN_DATADIR_SUFFIX("cached-microdescs.new", ".tmp"); + OPEN_DATADIR_SUFFIX("cached-descriptors", ".tmp"); + OPEN_DATADIR_SUFFIX("cached-descriptors.new", ".tmp"); + OPEN_DATADIR("cached-descriptors.tmp.tmp"); + OPEN_DATADIR_SUFFIX("cached-extrainfo", ".tmp"); + OPEN_DATADIR_SUFFIX("cached-extrainfo.new", ".tmp"); + OPEN_DATADIR("cached-extrainfo.tmp.tmp"); + OPEN_DATADIR_SUFFIX("state", ".tmp"); + OPEN_DATADIR_SUFFIX("unparseable-desc", ".tmp"); + OPEN_DATADIR_SUFFIX("v3-status-votes", ".tmp"); + OPEN("/dev/srandom"); + OPEN("/dev/urandom"); + OPEN("/dev/random"); + OPEN("/etc/hosts"); + OPEN("/proc/meminfo"); + + if (options->ServerDNSResolvConfFile) + sandbox_cfg_allow_open_filename(&cfg, + tor_strdup(options->ServerDNSResolvConfFile)); + else + sandbox_cfg_allow_open_filename(&cfg, tor_strdup("/etc/resolv.conf")); + + for (i = 0; i < 2; ++i) { + if (get_torrc_fname(i)) { + sandbox_cfg_allow_open_filename(&cfg, tor_strdup(get_torrc_fname(i))); + } } - wcsncpy(path,d.cFileName,size); - FindClose(h); - return 0; +#define RENAME_SUFFIX(name, suffix) \ + sandbox_cfg_allow_rename(&cfg, \ + get_datadir_fname(name suffix), \ + get_datadir_fname(name)) + +#define RENAME_SUFFIX2(prefix, name, suffix) \ + sandbox_cfg_allow_rename(&cfg, \ + get_datadir_fname2(prefix, name suffix), \ + get_datadir_fname2(prefix, name)) + + RENAME_SUFFIX("cached-certs", ".tmp"); + RENAME_SUFFIX("cached-consensus", ".tmp"); + RENAME_SUFFIX("unverified-consensus", ".tmp"); + RENAME_SUFFIX("unverified-microdesc-consensus", ".tmp"); + RENAME_SUFFIX("cached-microdesc-consensus", ".tmp"); + RENAME_SUFFIX("cached-microdescs", ".tmp"); + RENAME_SUFFIX("cached-microdescs", ".new"); + RENAME_SUFFIX("cached-microdescs.new", ".tmp"); + RENAME_SUFFIX("cached-descriptors", ".tmp"); + RENAME_SUFFIX("cached-descriptors", ".new"); + RENAME_SUFFIX("cached-descriptors.new", ".tmp"); + RENAME_SUFFIX("cached-extrainfo", ".tmp"); + RENAME_SUFFIX("cached-extrainfo", ".new"); + RENAME_SUFFIX("cached-extrainfo.new", ".tmp"); + RENAME_SUFFIX("state", ".tmp"); + RENAME_SUFFIX("unparseable-desc", ".tmp"); + RENAME_SUFFIX("v3-status-votes", ".tmp"); + +#define STAT_DATADIR(name) \ + sandbox_cfg_allow_stat_filename(&cfg, get_datadir_fname(name)) + +#define STAT_DATADIR2(name, name2) \ + sandbox_cfg_allow_stat_filename(&cfg, get_datadir_fname2((name), (name2))) + + STAT_DATADIR(NULL); + STAT_DATADIR("lock"); + STAT_DATADIR("state"); + STAT_DATADIR("router-stability"); + STAT_DATADIR("cached-extrainfo.new"); + + { + smartlist_t *files = smartlist_new(); + tor_log_get_logfile_names(files); + SMARTLIST_FOREACH(files, char *, file_name, { + /* steals reference */ + sandbox_cfg_allow_open_filename(&cfg, file_name); + }); + smartlist_free(files); + } + + { + smartlist_t *files = smartlist_new(); + smartlist_t *dirs = smartlist_new(); + rend_services_add_filenames_to_lists(files, dirs); + SMARTLIST_FOREACH(files, char *, file_name, { + char *tmp_name = NULL; + tor_asprintf(&tmp_name, "%s.tmp", file_name); + sandbox_cfg_allow_rename(&cfg, + tor_strdup(tmp_name), tor_strdup(file_name)); + /* steals references */ + sandbox_cfg_allow_open_filename(&cfg, file_name); + sandbox_cfg_allow_open_filename(&cfg, tmp_name); + }); + SMARTLIST_FOREACH(dirs, char *, dir, { + /* steals reference */ + sandbox_cfg_allow_stat_filename(&cfg, dir); + }); + smartlist_free(files); + smartlist_free(dirs); + } + + { + char *fname; + if ((fname = get_controller_cookie_file_name())) { + sandbox_cfg_allow_open_filename(&cfg, fname); + } + if ((fname = get_ext_or_auth_cookie_file_name())) { + sandbox_cfg_allow_open_filename(&cfg, fname); + } + } + + if (options->DirPortFrontPage) { + sandbox_cfg_allow_open_filename(&cfg, + tor_strdup(options->DirPortFrontPage)); + } + + // orport + if (server_mode(get_options())) { + + OPEN_DATADIR2_SUFFIX("keys", "secret_id_key", ".tmp"); + OPEN_DATADIR2_SUFFIX("keys", "secret_onion_key", ".tmp"); + OPEN_DATADIR2_SUFFIX("keys", "secret_onion_key_ntor", ".tmp"); + OPEN_DATADIR2("keys", "secret_id_key.old"); + OPEN_DATADIR2("keys", "secret_onion_key.old"); + OPEN_DATADIR2("keys", "secret_onion_key_ntor.old"); + + OPEN_DATADIR2_SUFFIX("stats", "bridge-stats", ".tmp"); + OPEN_DATADIR2_SUFFIX("stats", "dirreq-stats", ".tmp"); + + OPEN_DATADIR2_SUFFIX("stats", "entry-stats", ".tmp"); + OPEN_DATADIR2_SUFFIX("stats", "exit-stats", ".tmp"); + OPEN_DATADIR2_SUFFIX("stats", "buffer-stats", ".tmp"); + OPEN_DATADIR2_SUFFIX("stats", "conn-stats", ".tmp"); + + OPEN_DATADIR("approved-routers"); + OPEN_DATADIR_SUFFIX("fingerprint", ".tmp"); + OPEN_DATADIR_SUFFIX("hashed-fingerprint", ".tmp"); + OPEN_DATADIR_SUFFIX("router-stability", ".tmp"); + + OPEN("/etc/resolv.conf"); + + RENAME_SUFFIX("fingerprint", ".tmp"); + RENAME_SUFFIX2("keys", "secret_onion_key_ntor", ".tmp"); + RENAME_SUFFIX2("keys", "secret_id_key", ".tmp"); + RENAME_SUFFIX2("keys", "secret_id_key.old", ".tmp"); + RENAME_SUFFIX2("keys", "secret_onion_key", ".tmp"); + RENAME_SUFFIX2("keys", "secret_onion_key.old", ".tmp"); + RENAME_SUFFIX2("stats", "bridge-stats", ".tmp"); + RENAME_SUFFIX2("stats", "dirreq-stats", ".tmp"); + RENAME_SUFFIX2("stats", "entry-stats", ".tmp"); + RENAME_SUFFIX2("stats", "exit-stats", ".tmp"); + RENAME_SUFFIX2("stats", "buffer-stats", ".tmp"); + RENAME_SUFFIX2("stats", "conn-stats", ".tmp"); + RENAME_SUFFIX2("stats", "hidserv-stats", ".tmp"); + RENAME_SUFFIX("hashed-fingerprint", ".tmp"); + RENAME_SUFFIX("router-stability", ".tmp"); + + sandbox_cfg_allow_rename(&cfg, + get_datadir_fname2("keys", "secret_onion_key"), + get_datadir_fname2("keys", "secret_onion_key.old")); + sandbox_cfg_allow_rename(&cfg, + get_datadir_fname2("keys", "secret_onion_key_ntor"), + get_datadir_fname2("keys", "secret_onion_key_ntor.old")); + + STAT_DATADIR("keys"); + STAT_DATADIR("stats"); + STAT_DATADIR2("stats", "dirreq-stats"); + } + + init_addrinfo(); + + return cfg; } -#endif /** Main entry point for the Tor process. Called from main(). */ /* This function is distinct from main() only so we can link main.c into @@ -2636,31 +3046,6 @@ int tor_main(int argc, char *argv[]) { int result = 0; -#if defined (WINCE) - WCHAR path [MAX_PATH] = {0}; - WCHAR fullpath [MAX_PATH] = {0}; - PWCHAR p = NULL; - FILE* redir = NULL; - FILE* redirdbg = NULL; - - // this is to facilitate debugging by opening - // a file on a folder shared by the wm emulator. - // if no flashcard (real or emulated) is present, - // log files will be written in the root folder - if (find_flashcard_path(path,MAX_PATH) == -1) { - redir = _wfreopen( L"\\stdout.log", L"w", stdout ); - redirdbg = _wfreopen( L"\\stderr.log", L"w", stderr ); - } else { - swprintf(fullpath,L"\\%s\\tor",path); - CreateDirectory(fullpath,NULL); - - swprintf(fullpath,L"\\%s\\tor\\stdout.log",path); - redir = _wfreopen( fullpath, L"w", stdout ); - - swprintf(fullpath,L"\\%s\\tor\\stderr.log",path); - redirdbg = _wfreopen( fullpath, L"w", stderr ); - } -#endif #ifdef _WIN32 /* Call SetProcessDEPPolicy to permanently enable DEP. @@ -2675,9 +3060,11 @@ tor_main(int argc, char *argv[]) } #endif + configure_backtrace_handler(get_version()); + update_approx_time(time(NULL)); tor_threads_init(); - init_logging(); + init_logging(0); #ifdef USE_DMALLOC { /* Instruct OpenSSL to use our internal wrappers for malloc, @@ -2695,6 +3082,22 @@ tor_main(int argc, char *argv[]) #endif if (tor_init(argc, argv)<0) return -1; + + if (get_options()->Sandbox && get_options()->command == CMD_RUN_TOR) { + sandbox_cfg_t* cfg = sandbox_init_filter(); + + if (sandbox_init(cfg)) { + log_err(LD_BUG,"Failed to create syscall sandbox filter"); + return -1; + } + + // registering libevent rng +#ifdef HAVE_EVUTIL_SECURE_RNG_SET_URANDOM_DEVICE_FILE + evutil_secure_rng_set_urandom_device_file( + (char*) sandbox_intern_string("/dev/urandom")); +#endif + } + switch (get_options()->command) { case CMD_RUN_TOR: #ifdef NT_SERVICE @@ -2713,6 +3116,9 @@ tor_main(int argc, char *argv[]) printf("Configuration was valid\n"); result = 0; break; + case CMD_DUMP_CONFIG: + result = do_dump_config(); + break; case CMD_RUN_UNITTESTS: /* only set by test.c */ default: log_warn(LD_BUG,"Illegal command number %d: internal error.", diff --git a/src/or/main.h b/src/or/main.h index 338449b6a6..f77b4711c5 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,7 +12,9 @@ #ifndef TOR_MAIN_H #define TOR_MAIN_H -extern int can_complete_circuit; +int have_completed_a_circuit(void); +void note_that_we_completed_a_circuit(void); +void note_that_we_maybe_cant_complete_circuits(void); int connection_add_impl(connection_t *conn, int is_connecting); #define connection_add(conn) connection_add_impl((conn), 0) @@ -24,8 +26,8 @@ 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); +MOCK_DECL(uint64_t,get_bytes_read,(void)); +MOCK_DECL(uint64_t,get_bytes_written,(void)); /** Bitmask for events that we can turn on and off with * connection_watch_events. */ @@ -36,12 +38,12 @@ typedef enum watchable_events { } watchable_events_t; void connection_watch_events(connection_t *conn, watchable_events_t events); int connection_is_reading(connection_t *conn); -void connection_stop_reading(connection_t *conn); -void connection_start_reading(connection_t *conn); +MOCK_DECL(void,connection_stop_reading,(connection_t *conn)); +MOCK_DECL(void,connection_start_reading,(connection_t *conn)); int connection_is_writing(connection_t *conn); -void connection_stop_writing(connection_t *conn); -void connection_start_writing(connection_t *conn); +MOCK_DECL(void,connection_stop_writing,(connection_t *conn)); +MOCK_DECL(void,connection_start_writing,(connection_t *conn)); void connection_stop_reading_from_linked_conn(connection_t *conn); @@ -50,8 +52,10 @@ void directory_info_has_arrived(time_t now, int from_cache); void ip_address_changed(int at_interface); void dns_servers_relaunch_checks(void); +void reschedule_descriptor_update_check(void); + +MOCK_DECL(long,get_uptime,(void)); -long get_uptime(void); unsigned get_signewnym_epoch(void); void handle_signals(int is_parent); @@ -66,11 +70,12 @@ void tor_free_all(int postfork); int tor_main(int argc, char *argv[]); -#ifdef MAIN_PRIVATE int do_main_loop(void); -int do_list_fingerprint(void); -void do_hash_password(void); int tor_init(int argc, char **argv); + +#ifdef MAIN_PRIVATE +STATIC void init_connection_lists(void); +STATIC void close_closeable_connections(void); #endif #endif diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 0e72c0b89b..0511e870d1 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2013, The Tor Project, Inc. */ +/* Copyright (c) 2009-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" @@ -45,12 +45,7 @@ struct microdesc_cache_t { static INLINE unsigned int microdesc_hash_(microdesc_t *md) { - unsigned *d = (unsigned*)md->digest; -#if SIZEOF_INT == 4 - return d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6] ^ d[7]; -#else - return d[0] ^ d[1] ^ d[2] ^ d[3]; -#endif + return (unsigned) siphash24g(md->digest, sizeof(md->digest)); } /** Helper: compares <b>a</b> and </b> for equality for hash-table purposes. */ @@ -62,9 +57,9 @@ microdesc_eq_(microdesc_t *a, microdesc_t *b) HT_PROTOTYPE(microdesc_map, microdesc_t, node, microdesc_hash_, microdesc_eq_); -HT_GENERATE(microdesc_map, microdesc_t, node, +HT_GENERATE2(microdesc_map, microdesc_t, node, microdesc_hash_, microdesc_eq_, 0.6, - malloc, realloc, free); + tor_reallocarray_, tor_free_) /** Write the body of <b>md</b> into <b>f</b>, with appropriate annotations. * On success, return the total number of bytes written, and set @@ -139,7 +134,7 @@ get_microdesc_cache(void) * ending at <b>eos</b>, and store them in <b>cache</b>. If <b>no_save</b>, * mark them as non-writable to disk. If <b>where</b> is SAVED_IN_CACHE, * leave their bodies as pointers to the mmap'd cache. If where is - * <b>SAVED_NOWHERE</b>, do not allow annotations. If listed_at is positive, + * <b>SAVED_NOWHERE</b>, do not allow annotations. If listed_at is not -1, * set the last_listed field of every microdesc to listed_at. If * requested_digests is non-null, then it contains a list of digests we mean * to allow, so we should reject any non-requested microdesc with a different @@ -152,39 +147,81 @@ microdescs_add_to_cache(microdesc_cache_t *cache, int no_save, time_t listed_at, smartlist_t *requested_digests256) { + void * const DIGEST_REQUESTED = (void*)1; + void * const DIGEST_RECEIVED = (void*)2; + void * const DIGEST_INVALID = (void*)3; + smartlist_t *descriptors, *added; const int allow_annotations = (where != SAVED_NOWHERE); + smartlist_t *invalid_digests = smartlist_new(); descriptors = microdescs_parse_from_string(s, eos, allow_annotations, - where); - if (listed_at > 0) { + where, invalid_digests); + if (listed_at != (time_t)-1) { SMARTLIST_FOREACH(descriptors, microdesc_t *, md, md->last_listed = listed_at); } if (requested_digests256) { - digestmap_t *requested; /* XXXX actually we should just use a - digest256map */ - requested = digestmap_new(); - SMARTLIST_FOREACH(requested_digests256, const char *, cp, - digestmap_set(requested, cp, (void*)1)); + digest256map_t *requested; + requested = digest256map_new(); + /* Set requested[d] to DIGEST_REQUESTED for every md we requested. */ + SMARTLIST_FOREACH(requested_digests256, const uint8_t *, cp, + digest256map_set(requested, cp, DIGEST_REQUESTED)); + /* Set requested[d] to DIGEST_INVALID for every md we requested which we + * will never be able to parse. Remove the ones we didn't request from + * invalid_digests. + */ + SMARTLIST_FOREACH_BEGIN(invalid_digests, uint8_t *, cp) { + if (digest256map_get(requested, cp)) { + digest256map_set(requested, cp, DIGEST_INVALID); + } else { + tor_free(cp); + SMARTLIST_DEL_CURRENT(invalid_digests, cp); + } + } SMARTLIST_FOREACH_END(cp); + /* Update requested[d] to 2 for the mds we asked for and got. Delete the + * ones we never requested from the 'descriptors' smartlist. + */ SMARTLIST_FOREACH_BEGIN(descriptors, microdesc_t *, md) { - if (digestmap_get(requested, md->digest)) { - digestmap_set(requested, md->digest, (void*)2); + if (digest256map_get(requested, (const uint8_t*)md->digest)) { + digest256map_set(requested, (const uint8_t*)md->digest, + DIGEST_RECEIVED); } else { log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Received non-requested microdesc"); 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) { + /* Remove the ones we got or the invalid ones from requested_digests256. + */ + SMARTLIST_FOREACH_BEGIN(requested_digests256, uint8_t *, cp) { + void *status = digest256map_get(requested, cp); + if (status == DIGEST_RECEIVED || status == DIGEST_INVALID) { tor_free(cp); SMARTLIST_DEL_CURRENT(requested_digests256, cp); } } SMARTLIST_FOREACH_END(cp); - digestmap_free(requested, NULL); + digest256map_free(requested, NULL); + } + + /* For every requested microdescriptor that was unparseable, mark it + * as not to be retried. */ + if (smartlist_len(invalid_digests)) { + networkstatus_t *ns = + networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); + if (ns) { + SMARTLIST_FOREACH_BEGIN(invalid_digests, char *, d) { + routerstatus_t *rs = + router_get_mutable_consensus_status_by_descriptor_digest(ns, d); + if (rs && tor_memeq(d, rs->descriptor_digest, DIGEST256_LEN)) { + download_status_mark_impossible(&rs->dl_status); + } + } SMARTLIST_FOREACH_END(d); + } } + SMARTLIST_FOREACH(invalid_digests, uint8_t *, d, tor_free(d)); + smartlist_free(invalid_digests); added = microdescs_add_list_to_cache(cache, descriptors, where, no_save); smartlist_free(descriptors); @@ -280,6 +317,7 @@ void microdesc_cache_clear(microdesc_cache_t *cache) { microdesc_t **entry, **next; + for (entry = HT_START(microdesc_map, &cache->map); entry; entry = next) { microdesc_t *md = *entry; next = HT_NEXT_RMV(microdesc_map, &cache->map, entry); @@ -288,7 +326,13 @@ microdesc_cache_clear(microdesc_cache_t *cache) } HT_CLEAR(microdesc_map, &cache->map); if (cache->cache_content) { - tor_munmap_file(cache->cache_content); + int res = tor_munmap_file(cache->cache_content); + if (res != 0) { + log_warn(LD_FS, + "tor_munmap_file() failed clearing microdesc cache; " + "we are probably about to leak memory."); + /* TODO something smarter? */ + } cache->cache_content = NULL; } cache->total_len_seen = 0; @@ -368,7 +412,9 @@ microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force) cutoff = now - TOLERATE_MICRODESC_AGE; for (mdp = HT_START(microdesc_map, &cache->map); mdp != NULL; ) { - if ((*mdp)->last_listed < cutoff) { + const int is_old = (*mdp)->last_listed < cutoff; + const unsigned held_by_nodes = (*mdp)->held_by_nodes; + if (is_old && !held_by_nodes) { ++dropped; victim = *mdp; mdp = HT_NEXT_RMV(microdesc_map, &cache->map, mdp); @@ -376,6 +422,57 @@ microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force) bytes_dropped += victim->bodylen; microdesc_free(victim); } else { + if (is_old) { + /* It's old, but it has held_by_nodes set. That's not okay. */ + /* Let's try to diagnose and fix #7164 . */ + smartlist_t *nodes = nodelist_find_nodes_with_microdesc(*mdp); + const networkstatus_t *ns = networkstatus_get_latest_consensus(); + long networkstatus_age = -1; + const int ht_badness = HT_REP_IS_BAD_(microdesc_map, &cache->map); + if (ns) { + networkstatus_age = now - ns->valid_after; + } + log_warn(LD_BUG, "Microdescriptor seemed very old " + "(last listed %d hours ago vs %d hour cutoff), but is still " + "marked as being held by %d node(s). I found %d node(s) " + "holding it. Current networkstatus is %ld hours old. " + "Hashtable badness is %d.", + (int)((now - (*mdp)->last_listed) / 3600), + (int)((now - cutoff) / 3600), + held_by_nodes, + smartlist_len(nodes), + networkstatus_age / 3600, + ht_badness); + + SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) { + const char *rs_match = "No RS"; + const char *rs_present = ""; + if (node->rs) { + if (tor_memeq(node->rs->descriptor_digest, + (*mdp)->digest, DIGEST256_LEN)) { + rs_match = "Microdesc digest in RS matches"; + } else { + rs_match = "Microdesc digest in RS does match"; + } + if (ns) { + /* This should be impossible, but let's see! */ + rs_present = " RS not present in networkstatus."; + SMARTLIST_FOREACH(ns->routerstatus_list, routerstatus_t *,rs, { + if (rs == node->rs) { + rs_present = " RS okay in networkstatus."; + } + }); + } + } + log_warn(LD_BUG, " [%d]: ID=%s. md=%p, rs=%p, ri=%p. %s.%s", + node_sl_idx, + hex_str(node->identity, DIGEST_LEN), + node->md, node->rs, node->ri, rs_match, rs_present); + } SMARTLIST_FOREACH_END(node); + smartlist_free(nodes); + (*mdp)->last_listed = now; + } + ++kept; mdp = HT_NEXT(microdesc_map, &cache->map, mdp); } @@ -434,7 +531,7 @@ int microdesc_cache_rebuild(microdesc_cache_t *cache, int force) { open_file_t *open_file; - int fd = -1; + int fd = -1, res; microdesc_t **mdp; smartlist_t *wrote; ssize_t size; @@ -489,7 +586,8 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) "By my count, I'm at "I64_FORMAT ", but I should be at "I64_FORMAT, I64_PRINTF_ARG(off), I64_PRINTF_ARG(off_real)); - off = off_real; + if (off_real >= 0) + off = off_real; } if (md->saved_location != SAVED_IN_CACHE) { tor_free(md->body); @@ -500,8 +598,14 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) /* We must do this unmap _before_ we call finish_writing_to_file(), or * windows will not actually replace the file. */ - if (cache->cache_content) - tor_munmap_file(cache->cache_content); + if (cache->cache_content) { + res = tor_munmap_file(cache->cache_content); + if (res != 0) { + log_warn(LD_FS, + "Failed to unmap old microdescriptor cache while rebuilding"); + } + cache->cache_content = NULL; + } if (finish_writing_to_file(open_file) < 0) { log_warn(LD_DIR, "Error rebuilding microdescriptor cache: %s", @@ -514,6 +618,7 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) microdesc_wipe_body(md); } } + smartlist_free(wrote); return -1; } @@ -605,8 +710,10 @@ microdesc_free_(microdesc_t *md, const char *fname, int lineno) tor_fragile_assert(); } if (md->held_by_nodes) { + microdesc_cache_t *cache = get_microdesc_cache(); int found=0; const smartlist_t *nodes = nodelist_get_list(); + const int ht_badness = HT_REP_IS_BAD_(microdesc_map, &cache->map); SMARTLIST_FOREACH(nodes, node_t *, node, { if (node->md == md) { ++found; @@ -614,13 +721,14 @@ microdesc_free_(microdesc_t *md, const char *fname, int lineno) } }); if (found) { - log_info(LD_BUG, "microdesc_free() called from %s:%d, but md was still " - "referenced %d node(s); held_by_nodes == %u", - fname, lineno, found, md->held_by_nodes); + log_warn(LD_BUG, "microdesc_free() called from %s:%d, but md was still " + "referenced %d node(s); held_by_nodes == %u, ht_badness == %d", + fname, lineno, found, md->held_by_nodes, ht_badness); } else { log_warn(LD_BUG, "microdesc_free() called from %s:%d with held_by_nodes " - "set to %u, but md was not referenced by any nodes", - fname, lineno, md->held_by_nodes); + "set to %u, but md was not referenced by any nodes. " + "ht_badness == %d", + fname, lineno, md->held_by_nodes, ht_badness); } tor_fragile_assert(); } @@ -686,7 +794,7 @@ microdesc_average_size(microdesc_cache_t *cache) * 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) + int downloadable_only, digest256map_t *skip) { smartlist_t *result = smartlist_new(); time_t now = time(NULL); @@ -696,9 +804,9 @@ microdesc_list_missing_digest256(networkstatus_t *ns, microdesc_cache_t *cache, continue; if (downloadable_only && !download_status_is_ready(&rs->dl_status, now, - MAX_MICRODESC_DOWNLOAD_FAILURES)) + get_options()->TestingMicrodescMaxDownloadTries)) continue; - if (skip && digestmap_get(skip, rs->descriptor_digest)) + if (skip && digest256map_get(skip, (const uint8_t*)rs->descriptor_digest)) continue; if (tor_mem_is_zero(rs->descriptor_digest, DIGEST256_LEN)) continue; @@ -723,9 +831,9 @@ update_microdesc_downloads(time_t now) const or_options_t *options = get_options(); networkstatus_t *consensus; smartlist_t *missing; - digestmap_t *pending; + digest256map_t *pending; - if (should_delay_dir_fetches(options)) + if (should_delay_dir_fetches(options, NULL)) return; if (directory_too_idle_to_fetch_descriptors(options, now)) return; @@ -737,14 +845,14 @@ update_microdesc_downloads(time_t now) if (!we_fetch_microdescriptors(options)) return; - pending = digestmap_new(); + pending = digest256map_new(); list_pending_microdesc_downloads(pending); missing = microdesc_list_missing_digest256(consensus, get_microdesc_cache(), 1, pending); - digestmap_free(pending, NULL); + digest256map_free(pending, NULL); launch_descriptor_downloads(DIR_PURPOSE_FETCH_MICRODESC, missing, NULL, now); diff --git a/src/or/microdesc.h b/src/or/microdesc.h index 7adb8c68af..08571e4bd5 100644 --- a/src/or/microdesc.h +++ b/src/or/microdesc.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -37,7 +37,7 @@ 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); + digest256map_t *skip); void microdesc_free_(microdesc_t *md, const char *fname, int line); #define microdesc_free(md) \ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 23b7304b39..da110fdff6 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,6 +10,7 @@ * client or cache. */ +#define NETWORKSTATUS_PRIVATE #include "or.h" #include "channel.h" #include "circuitmux.h" @@ -31,18 +32,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" - -/* For tracking v2 networkstatus documents. Only caches do this now. */ - -/** Map from descriptor digest of routers listed in the v2 networkstatus - * documents to download_status_t* */ -static digestmap_t *v2_download_status_map = NULL; -/** Global list of all of the current v2 network_status documents that we know - * about. This list is kept sorted by published_on. */ -static smartlist_t *networkstatus_v2_list = NULL; -/** True iff any member of networkstatus_v2_list has changed since the last - * time we called download_status_map_update_from_v2_networkstatus() */ -static int networkstatus_v2_list_has_changed = 0; +#include "transports.h" /** Map from lowercase nickname to identity digest of named server, if any. */ static strmap_t *named_server_map = NULL; @@ -88,17 +78,16 @@ typedef struct consensus_waiting_for_certs_t { static consensus_waiting_for_certs_t consensus_waiting_for_certs[N_CONSENSUS_FLAVORS]; -/** The last time we tried to download a networkstatus, or 0 for "never". We - * use this to rate-limit download attempts for directory caches (including - * mirrors). Clients don't use this now. */ -static time_t last_networkstatus_download_attempted = 0; - /** A time before which we shouldn't try to replace the current consensus: * this will be at some point after the next consensus becomes valid, but * before the current consensus becomes invalid. */ static time_t time_to_download_next_consensus[N_CONSENSUS_FLAVORS]; /** Download status for the current consensus networkstatus. */ -static download_status_t consensus_dl_status[N_CONSENSUS_FLAVORS]; +static download_status_t consensus_dl_status[N_CONSENSUS_FLAVORS] = + { + { 0, 0, DL_SCHED_CONSENSUS }, + { 0, 0, DL_SCHED_CONSENSUS }, + }; /** True iff we have logged a warning about this OR's version being older than * listed by the authorities. */ @@ -107,7 +96,6 @@ static int have_warned_about_old_version = 0; * listed by the authorities. */ static int have_warned_about_new_version = 0; -static void download_status_map_update_from_v2_networkstatus(void); static void routerstatus_list_update_named_server_map(void); /** Forget that we've warned about anything networkstatus-related, so we will @@ -131,86 +119,9 @@ void networkstatus_reset_download_failures(void) { int i; - const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); - SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) { - if (!router_get_by_descriptor_digest(rs->descriptor_digest)) - rs->need_to_mirror = 1; - } SMARTLIST_FOREACH_END(rs); - } SMARTLIST_FOREACH_END(ns); for (i=0; i < N_CONSENSUS_FLAVORS; ++i) download_status_reset(&consensus_dl_status[i]); - if (v2_download_status_map) { - digestmap_iter_t *iter; - digestmap_t *map = v2_download_status_map; - const char *key; - void *val; - download_status_t *dls; - for (iter = digestmap_iter_init(map); !digestmap_iter_done(iter); - iter = digestmap_iter_next(map, iter) ) { - digestmap_iter_get(iter, &key, &val); - dls = val; - download_status_reset(dls); - } - } -} - -/** Repopulate our list of network_status_t objects from the list cached on - * disk. Return 0 on success, -1 on failure. */ -int -router_reload_v2_networkstatus(void) -{ - smartlist_t *entries; - struct stat st; - char *s; - char *filename = get_datadir_fname("cached-status"); - int maybe_delete = !directory_caches_v2_dir_info(get_options()); - time_t now = time(NULL); - if (!networkstatus_v2_list) - networkstatus_v2_list = smartlist_new(); - - entries = tor_listdir(filename); - if (!entries) { /* dir doesn't exist */ - tor_free(filename); - return 0; - } else if (!smartlist_len(entries) && maybe_delete) { - rmdir(filename); - tor_free(filename); - smartlist_free(entries); - return 0; - } - tor_free(filename); - SMARTLIST_FOREACH_BEGIN(entries, const char *, fn) { - char buf[DIGEST_LEN]; - if (maybe_delete) { - filename = get_datadir_fname2("cached-status", fn); - remove_file_if_very_old(filename, now); - tor_free(filename); - continue; - } - if (strlen(fn) != HEX_DIGEST_LEN || - base16_decode(buf, sizeof(buf), fn, strlen(fn))) { - log_info(LD_DIR, - "Skipping cached-status file with unexpected name \"%s\"",fn); - continue; - } - filename = get_datadir_fname2("cached-status", fn); - s = read_file_to_str(filename, 0, &st); - if (s) { - if (router_set_networkstatus_v2(s, st.st_mtime, NS_FROM_CACHE, - NULL)<0) { - log_warn(LD_FS, "Couldn't load networkstatus from \"%s\"",filename); - } - tor_free(s); - } - tor_free(filename); - } SMARTLIST_FOREACH_END(fn); - SMARTLIST_FOREACH(entries, char *, fn, tor_free(fn)); - smartlist_free(entries); - networkstatus_v2_list_clean(time(NULL)); - routers_update_all_from_networkstatus(time(NULL), 2); - return 0; } /** Read every cached v3 consensus networkstatus from the disk. */ @@ -277,7 +188,7 @@ router_reload_consensus_networkstatus(void) } /** Free all storage held by the vote_routerstatus object <b>rs</b>. */ -static void +STATIC void vote_routerstatus_free(vote_routerstatus_t *rs) { vote_microdesc_hash_t *h, *next; @@ -303,26 +214,6 @@ routerstatus_free(routerstatus_t *rs) tor_free(rs); } -/** Free all storage held by the networkstatus object <b>ns</b>. */ -void -networkstatus_v2_free(networkstatus_v2_t *ns) -{ - if (!ns) - return; - tor_free(ns->source_address); - tor_free(ns->contact); - if (ns->signing_key) - crypto_pk_free(ns->signing_key); - tor_free(ns->client_versions); - tor_free(ns->server_versions); - if (ns->entries) { - SMARTLIST_FOREACH(ns->entries, routerstatus_t *, rs, - routerstatus_free(rs)); - smartlist_free(ns->entries); - } - tor_free(ns); -} - /** Free all storage held in <b>sig</b> */ void document_signature_free(document_signature_t *sig) @@ -366,6 +257,10 @@ networkstatus_vote_free(networkstatus_t *ns) SMARTLIST_FOREACH(ns->supported_methods, char *, c, tor_free(c)); smartlist_free(ns->supported_methods); } + if (ns->package_lines) { + SMARTLIST_FOREACH(ns->package_lines, char *, c, tor_free(c)); + smartlist_free(ns->package_lines); + } if (ns->voters) { SMARTLIST_FOREACH_BEGIN(ns->voters, networkstatus_voter_info_t *, voter) { tor_free(voter->nickname); @@ -648,296 +543,10 @@ networkstatus_check_consensus_signature(networkstatus_t *consensus, return -2; } -/** Helper: return a newly allocated string containing the name of the filename - * where we plan to cache the network status with the given identity digest. */ -char * -networkstatus_get_cache_filename(const char *identity_digest) -{ - char fp[HEX_DIGEST_LEN+1]; - base16_encode(fp, HEX_DIGEST_LEN+1, identity_digest, DIGEST_LEN); - return get_datadir_fname2("cached-status", fp); -} - -/** Helper for smartlist_sort: Compare two networkstatus objects by - * publication date. */ -static int -compare_networkstatus_v2_published_on_(const void **_a, const void **_b) -{ - const networkstatus_v2_t *a = *_a, *b = *_b; - if (a->published_on < b->published_on) - return -1; - else if (a->published_on > b->published_on) - return 1; - else - return 0; -} - -/** Add the parsed v2 networkstatus in <b>ns</b> (with original document in - * <b>s</b>) to the disk cache (and the in-memory directory server cache) as - * appropriate. */ -static int -add_networkstatus_to_cache(const char *s, - v2_networkstatus_source_t source, - networkstatus_v2_t *ns) -{ - if (source != NS_FROM_CACHE) { - char *fn = networkstatus_get_cache_filename(ns->identity_digest); - if (write_str_to_file(fn, s, 0)<0) { - log_notice(LD_FS, "Couldn't write cached network status to \"%s\"", fn); - } - tor_free(fn); - } - - if (directory_caches_v2_dir_info(get_options())) - dirserv_set_cached_networkstatus_v2(s, - ns->identity_digest, - ns->published_on); - - return 0; -} - /** How far in the future do we allow a network-status to get before removing * it? (seconds) */ #define NETWORKSTATUS_ALLOW_SKEW (24*60*60) -/** Given a string <b>s</b> containing a network status that we received at - * <b>arrived_at</b> from <b>source</b>, try to parse it, see if we want to - * store it, and put it into our cache as necessary. - * - * If <b>source</b> is NS_FROM_DIR or NS_FROM_CACHE, do not replace our - * own networkstatus_t (if we're an authoritative directory server). - * - * If <b>source</b> is NS_FROM_CACHE, do not write our networkstatus_t to the - * cache. - * - * If <b>requested_fingerprints</b> is provided, it must contain a list of - * uppercased identity fingerprints. Do not update any networkstatus whose - * fingerprint is not on the list; after updating a networkstatus, remove its - * fingerprint from the list. - * - * Return 0 on success, -1 on failure. - * - * Callers should make sure that routers_update_all_from_networkstatus() is - * invoked after this function succeeds. - */ -int -router_set_networkstatus_v2(const char *s, time_t arrived_at, - v2_networkstatus_source_t source, - smartlist_t *requested_fingerprints) -{ - networkstatus_v2_t *ns; - int i, found; - time_t now; - int skewed = 0; - dir_server_t *trusted_dir = NULL; - const char *source_desc = NULL; - char fp[HEX_DIGEST_LEN+1]; - char published[ISO_TIME_LEN+1]; - - if (!directory_caches_v2_dir_info(get_options())) - return 0; /* Don't bother storing it. */ - - ns = networkstatus_v2_parse_from_string(s); - if (!ns) { - log_warn(LD_DIR, "Couldn't parse network status."); - return -1; - } - base16_encode(fp, HEX_DIGEST_LEN+1, ns->identity_digest, DIGEST_LEN); - if (!(trusted_dir = - router_get_trusteddirserver_by_digest(ns->identity_digest)) || - !(trusted_dir->type & V2_DIRINFO)) { - log_info(LD_DIR, "Network status was signed, but not by an authoritative " - "directory we recognize."); - source_desc = fp; - } else { - source_desc = trusted_dir->description; - } - now = time(NULL); - if (arrived_at > now) - arrived_at = now; - - ns->received_on = arrived_at; - - format_iso_time(published, ns->published_on); - - if (ns->published_on > now + NETWORKSTATUS_ALLOW_SKEW) { - char dbuf[64]; - long delta = now - ns->published_on; - format_time_interval(dbuf, sizeof(dbuf), delta); - log_warn(LD_GENERAL, "Network status from %s was published %s in the " - "future (%s UTC). Check your time and date settings! " - "Not caching.", - source_desc, dbuf, published); - control_event_general_status(LOG_WARN, - "CLOCK_SKEW MIN_SKEW=%ld SOURCE=NETWORKSTATUS:%s:%d", - delta, ns->source_address, ns->source_dirport); - skewed = 1; - } - - if (!networkstatus_v2_list) - networkstatus_v2_list = smartlist_new(); - - if ( (source == NS_FROM_DIR_BY_FP || source == NS_FROM_DIR_ALL) && - router_digest_is_me(ns->identity_digest)) { - /* Don't replace our own networkstatus when we get it from somebody else.*/ - networkstatus_v2_free(ns); - return 0; - } - - if (requested_fingerprints) { - if (smartlist_contains_string(requested_fingerprints, fp)) { - smartlist_string_remove(requested_fingerprints, fp); - } else { - if (source != NS_FROM_DIR_ALL) { - char *requested = - smartlist_join_strings(requested_fingerprints," ",0,NULL); - log_warn(LD_DIR, - "We received a network status with a fingerprint (%s) that we " - "never requested. (We asked for: %s.) Dropping.", - fp, requested); - tor_free(requested); - return 0; - } - } - } - - if (!trusted_dir) { - if (!skewed) { - /* We got a non-trusted networkstatus, and we're a directory cache. - * This means that we asked an authority, and it told us about another - * authority we didn't recognize. */ - log_info(LD_DIR, - "We do not recognize authority (%s) but we are willing " - "to cache it.", fp); - add_networkstatus_to_cache(s, source, ns); - networkstatus_v2_free(ns); - } - return 0; - } - - found = 0; - for (i=0; i < smartlist_len(networkstatus_v2_list); ++i) { - networkstatus_v2_t *old_ns = smartlist_get(networkstatus_v2_list, i); - - if (tor_memeq(old_ns->identity_digest, ns->identity_digest, DIGEST_LEN)) { - if (tor_memeq(old_ns->networkstatus_digest, - ns->networkstatus_digest, DIGEST_LEN)) { - /* Same one we had before. */ - networkstatus_v2_free(ns); - tor_assert(trusted_dir); - log_info(LD_DIR, - "Not replacing network-status from %s (published %s); " - "we already have it.", - trusted_dir->description, published); - if (old_ns->received_on < arrived_at) { - if (source != NS_FROM_CACHE) { - char *fn; - fn = networkstatus_get_cache_filename(old_ns->identity_digest); - /* We use mtime to tell when it arrived, so update that. */ - touch_file(fn); - tor_free(fn); - } - old_ns->received_on = arrived_at; - } - download_status_failed(&trusted_dir->v2_ns_dl_status, 0); - return 0; - } else if (old_ns->published_on >= ns->published_on) { - char old_published[ISO_TIME_LEN+1]; - format_iso_time(old_published, old_ns->published_on); - tor_assert(trusted_dir); - log_info(LD_DIR, - "Not replacing network-status from %s (published %s);" - " we have a newer one (published %s) for this authority.", - trusted_dir->description, published, - old_published); - networkstatus_v2_free(ns); - download_status_failed(&trusted_dir->v2_ns_dl_status, 0); - return 0; - } else { - networkstatus_v2_free(old_ns); - smartlist_set(networkstatus_v2_list, i, ns); - found = 1; - break; - } - } - } - - if (source != NS_FROM_CACHE && trusted_dir) { - download_status_reset(&trusted_dir->v2_ns_dl_status); - } - - if (!found) - smartlist_add(networkstatus_v2_list, ns); - -/** Retain any routerinfo mentioned in a V2 networkstatus for at least this - * long. */ -#define V2_NETWORKSTATUS_ROUTER_LIFETIME (3*60*60) - - { - time_t live_until = ns->published_on + V2_NETWORKSTATUS_ROUTER_LIFETIME; - SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) { - signed_descriptor_t *sd = - router_get_by_descriptor_digest(rs->descriptor_digest); - if (sd) { - if (sd->last_listed_as_valid_until < live_until) - sd->last_listed_as_valid_until = live_until; - } else { - rs->need_to_mirror = 1; - } - } SMARTLIST_FOREACH_END(rs); - } - - log_info(LD_DIR, "Setting networkstatus %s %s (published %s)", - source == NS_FROM_CACHE?"cached from": - ((source == NS_FROM_DIR_BY_FP || source == NS_FROM_DIR_ALL) ? - "downloaded from":"generated for"), - trusted_dir->description, published); - networkstatus_v2_list_has_changed = 1; - - smartlist_sort(networkstatus_v2_list, - compare_networkstatus_v2_published_on_); - - if (!skewed) - add_networkstatus_to_cache(s, source, ns); - - return 0; -} - -/** Remove all very-old network_status_t objects from memory and from the - * disk cache. */ -void -networkstatus_v2_list_clean(time_t now) -{ - int i; - if (!networkstatus_v2_list) - return; - - for (i = 0; i < smartlist_len(networkstatus_v2_list); ++i) { - networkstatus_v2_t *ns = smartlist_get(networkstatus_v2_list, i); - char *fname = NULL; - if (ns->published_on + MAX_NETWORKSTATUS_AGE > now) - continue; - /* Okay, this one is too old. Remove it from the list, and delete it - * from the cache. */ - smartlist_del(networkstatus_v2_list, i--); - fname = networkstatus_get_cache_filename(ns->identity_digest); - if (file_status(fname) == FN_FILE) { - log_info(LD_DIR, "Removing too-old networkstatus in %s", fname); - unlink(fname); - } - tor_free(fname); - if (directory_caches_v2_dir_info(get_options())) { - dirserv_set_cached_networkstatus_v2(NULL, ns->identity_digest, 0); - } - networkstatus_v2_free(ns); - } - - /* And now go through the directory cache for any cached untrusted - * networkstatuses and other network info. */ - dirserv_clear_old_networkstatuses(now - MAX_NETWORKSTATUS_AGE); - dirserv_clear_old_v1_info(now); -} - /** Helper for bsearching a list of routerstatus_t pointers: compare a * digest in the key to the identity digest of a routerstatus_t. */ int @@ -959,22 +568,6 @@ compare_digest_to_vote_routerstatus_entry(const void *_key, return tor_memcmp(key, vrs->status.identity_digest, DIGEST_LEN); } -/** As networkstatus_v2_find_entry, but do not return a const pointer */ -routerstatus_t * -networkstatus_v2_find_mutable_entry(networkstatus_v2_t *ns, const char *digest) -{ - return smartlist_bsearch(ns->entries, digest, - compare_digest_to_routerstatus_entry); -} - -/** 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_mutable_entry(networkstatus_t *ns, const char *digest) @@ -1004,21 +597,12 @@ networkstatus_vote_find_entry_idx(networkstatus_t *ns, found_out); } -/** Return a list of the v2 networkstatus documents. */ -const smartlist_t * -networkstatus_get_v2_list(void) -{ - if (!networkstatus_v2_list) - networkstatus_v2_list = smartlist_new(); - return networkstatus_v2_list; -} - /** As router_get_consensus_status_by_descriptor_digest, but does not return * a const pointer. */ -routerstatus_t * -router_get_mutable_consensus_status_by_descriptor_digest( +MOCK_IMPL(routerstatus_t *, +router_get_mutable_consensus_status_by_descriptor_digest,( networkstatus_t *consensus, - const char *digest) + const char *digest)) { if (!consensus) consensus = current_consensus; @@ -1048,8 +632,8 @@ router_get_consensus_status_by_descriptor_digest(networkstatus_t *consensus, /** Given the digest of a router descriptor, return its current download * status, or NULL if the digest is unrecognized. */ -download_status_t * -router_get_dl_status_by_descriptor_digest(const char *d) +MOCK_IMPL(download_status_t *, +router_get_dl_status_by_descriptor_digest,(const char *d)) { routerstatus_t *rs; if (!current_ns_consensus) @@ -1057,8 +641,6 @@ router_get_dl_status_by_descriptor_digest(const char *d) 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); return NULL; } @@ -1124,72 +706,6 @@ networkstatus_nickname_is_unnamed(const char *nickname) * networkstatus documents? */ #define NONAUTHORITY_NS_CACHE_INTERVAL (60*60) -/** We are a directory server, and so cache network_status documents. - * Initiate downloads as needed to update them. For v2 authorities, - * this means asking each trusted directory for its network-status. - * For caches, this means asking a random v2 authority for all - * network-statuses. - */ -static void -update_v2_networkstatus_cache_downloads(time_t now) -{ - int authority = authdir_mode_v2(get_options()); - int interval = - authority ? AUTHORITY_NS_CACHE_INTERVAL : NONAUTHORITY_NS_CACHE_INTERVAL; - const smartlist_t *trusted_dir_servers = router_get_trusted_dir_servers(); - - if (last_networkstatus_download_attempted + interval >= now) - return; - - last_networkstatus_download_attempted = now; - - if (authority) { - /* An authority launches a separate connection for everybody. */ - SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, dir_server_t *, ds) - { - char resource[HEX_DIGEST_LEN+6]; /* fp/hexdigit.z\0 */ - tor_addr_t addr; - if (!(ds->type & V2_DIRINFO)) - continue; - if (router_digest_is_me(ds->digest)) - continue; - tor_addr_from_ipv4h(&addr, ds->addr); - /* Is this quite sensible with IPv6 or multiple addresses? */ - if (connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &addr, ds->dir_port, - DIR_PURPOSE_FETCH_V2_NETWORKSTATUS)) { - /* XXX the above dir_port won't be accurate if we're - * doing a tunneled conn. In that case it should be or_port. - * How to guess from here? Maybe make the function less general - * and have it know that it's looking for dir conns. -RD */ - /* Only directory caches download v2 networkstatuses, and they - * don't use tunneled connections. I think it's okay to ignore - * this. */ - continue; - } - strlcpy(resource, "fp/", sizeof(resource)); - base16_encode(resource+3, sizeof(resource)-3, ds->digest, DIGEST_LEN); - strlcat(resource, ".z", sizeof(resource)); - directory_initiate_command_routerstatus( - &ds->fake_status, DIR_PURPOSE_FETCH_V2_NETWORKSTATUS, - ROUTER_PURPOSE_GENERAL, - DIRIND_ONEHOP, - resource, - NULL, 0 /* No payload. */, - 0 /* No I-M-S. */); - } - SMARTLIST_FOREACH_END(ds); - } else { - /* A non-authority cache launches one connection to a random authority. */ - /* (Check whether we're currently fetching network-status objects.) */ - if (!connection_get_by_type_purpose(CONN_TYPE_DIR, - DIR_PURPOSE_FETCH_V2_NETWORKSTATUS)) - directory_get_from_dirserver(DIR_PURPOSE_FETCH_V2_NETWORKSTATUS, - ROUTER_PURPOSE_GENERAL, "all.z", - PDS_RETRY_IF_NO_SERVERS); - } -} - /** Return true iff, given the options listed in <b>options</b>, <b>flavor</b> * is the flavor of a consensus networkstatus that we would like to fetch. */ static int @@ -1214,8 +730,6 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor) return flavor == usable_consensus_flavor(); } -/** How many times will we try to fetch a consensus before we give up? */ -#define CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES 8 /** How long will we hang onto a possibly live consensus for which we're * fetching certs before we check whether there is a better one? */ #define DELAY_WHILE_FETCHING_CERTS (20*60) @@ -1248,8 +762,11 @@ update_consensus_networkstatus_downloads(time_t now) resource = networkstatus_get_flavor_name(i); + /* Let's make sure we remembered to update consensus_dl_status */ + tor_assert(consensus_dl_status[i].schedule == DL_SCHED_CONSENSUS); + if (!download_status_is_ready(&consensus_dl_status[i], now, - CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES)) + options->TestingConsensusMaxDownloadTries)) continue; /* We failed downloading a consensus too recently. */ if (connection_dir_get_by_purpose_and_resource( DIR_PURPOSE_FETCH_CONSENSUS, resource)) @@ -1319,12 +836,16 @@ update_consensus_networkstatus_fetch_time_impl(time_t now, int flav) a crazy-fast voting interval, though, 2 minutes may be too much. */ min_sec_before_caching = interval/16; + /* make sure we always delay by at least a second before caching */ + if (min_sec_before_caching == 0) { + min_sec_before_caching = 1; + } } if (directory_fetches_dir_info_early(options)) { /* We want to cache the next one at some point after this one * is no longer fresh... */ - start = c->fresh_until + min_sec_before_caching; + start = (time_t)(c->fresh_until + min_sec_before_caching); /* Some clients may need the consensus sooner than others. */ if (options->FetchDirInfoExtraEarly || authdir_mode_v3(options)) { dl_interval = 60; @@ -1337,7 +858,7 @@ update_consensus_networkstatus_fetch_time_impl(time_t now, int flav) } else { /* We're an ordinary client or a bridge. Give all the caches enough * time to download the consensus. */ - start = c->fresh_until + (interval*3)/4; + start = (time_t)(c->fresh_until + (interval*3)/4); /* But download the next one well before this one is expired. */ dl_interval = ((c->valid_until - start) * 7 )/ 8; @@ -1345,13 +866,22 @@ update_consensus_networkstatus_fetch_time_impl(time_t now, int flav) * to choose the rest of the interval *after* them. */ if (directory_fetches_dir_info_later(options)) { /* Give all the *clients* enough time to download the consensus. */ - start = start + dl_interval + min_sec_before_caching; + start = (time_t)(start + dl_interval + min_sec_before_caching); /* But try to get it before ours actually expires. */ dl_interval = (c->valid_until - start) - min_sec_before_caching; } } + /* catch low dl_interval in crazy-fast networks */ if (dl_interval < 1) dl_interval = 1; + /* catch late start in crazy-fast networks */ + if (start+dl_interval >= c->valid_until) + start = c->valid_until - dl_interval - 1; + log_debug(LD_DIR, + "fresh_until: %ld start: %ld " + "dl_interval: %ld valid_until: %ld ", + (long)c->fresh_until, (long)start, dl_interval, + (long)c->valid_until); /* We must not try to replace c while it's still fresh: */ tor_assert(c->fresh_until < start); /* We must download the next one before c is invalid: */ @@ -1391,14 +921,45 @@ update_consensus_networkstatus_fetch_time(time_t now) /** Return 1 if there's a reason we shouldn't try any directory * fetches yet (e.g. we demand bridges and none are yet known). - * Else return 0. */ + * Else return 0. + + * If we return 1 and <b>msg_out</b> is provided, set <b>msg_out</b> + * to an explanation of why directory fetches are delayed. (If we + * return 0, we set msg_out to NULL.) + */ int -should_delay_dir_fetches(const or_options_t *options) +should_delay_dir_fetches(const or_options_t *options, const char **msg_out) { - if (options->UseBridges && !any_bridge_descriptors_known()) { - log_info(LD_DIR, "delaying dir fetches (no running bridges known)"); + if (msg_out) { + *msg_out = NULL; + } + + if (options->DisableNetwork) { + if (msg_out) { + *msg_out = "DisableNetwork is set."; + } + log_info(LD_DIR, "Delaying dir fetches (DisableNetwork is set)"); return 1; } + + if (options->UseBridges) { + if (!any_bridge_descriptors_known()) { + if (msg_out) { + *msg_out = "No running bridges"; + } + log_info(LD_DIR, "Delaying dir fetches (no running bridges known)"); + return 1; + } + + if (pt_proxies_configuration_pending()) { + if (msg_out) { + *msg_out = "Pluggable transport proxies still configuring"; + } + log_info(LD_DIR, "Delaying dir fetches (pt proxies still configuring)"); + return 1; + } + } + return 0; } @@ -1408,10 +969,8 @@ void update_networkstatus_downloads(time_t now) { const or_options_t *options = get_options(); - if (should_delay_dir_fetches(options)) + if (should_delay_dir_fetches(options, NULL)) return; - if (authdir_mode_any_main(options) || options->FetchV2Networkstatus) - update_v2_networkstatus_cache_downloads(now); update_consensus_networkstatus_downloads(now); update_certificate_downloads(now); } @@ -1453,8 +1012,8 @@ networkstatus_get_latest_consensus(void) /** Return the latest consensus we have whose flavor matches <b>f</b>, or NULL * if we don't have one. */ -networkstatus_t * -networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) +MOCK_IMPL(networkstatus_t *, +networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) { if (f == FLAV_NS) return current_ns_consensus; @@ -1518,10 +1077,8 @@ routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b) a->is_named != b->is_named || a->is_unnamed != b->is_unnamed || a->is_valid != b->is_valid || - a->is_v2_dir != b->is_v2_dir || a->is_possible_guard != b->is_possible_guard || a->is_bad_exit != b->is_bad_exit || - a->is_bad_directory != b->is_bad_directory || a->is_hs_dir != b->is_hs_dir || a->version_known != b->version_known; } @@ -1583,7 +1140,7 @@ networkstatus_copy_old_consensus_info(networkstatus_t *new_c, rs_new->last_dir_503_at = rs_old->last_dir_503_at; if (tor_memeq(rs_old->descriptor_digest, rs_new->descriptor_digest, - DIGEST_LEN)) { + DIGEST256_LEN)) { /* And the same descriptor too! */ memcpy(&rs_new->dl_status, &rs_old->dl_status,sizeof(download_status_t)); } @@ -1740,7 +1297,11 @@ networkstatus_set_current_consensus(const char *consensus, /* Even if we had enough signatures, we'd never use this as the * latest consensus. */ if (was_waiting_for_certs && from_cache) - unlink(unverified_fname); + if (unlink(unverified_fname) != 0) { + log_warn(LD_FS, + "Failed to unlink %s: %s", + unverified_fname, strerror(errno)); + } } goto done; } else { @@ -1750,8 +1311,13 @@ networkstatus_set_current_consensus(const char *consensus, "consensus"); result = -2; } - if (was_waiting_for_certs && (r < -1) && from_cache) - unlink(unverified_fname); + if (was_waiting_for_certs && (r < -1) && from_cache) { + if (unlink(unverified_fname) != 0) { + log_warn(LD_FS, + "Failed to unlink %s: %s", + unverified_fname, strerror(errno)); + } + } goto done; } } @@ -1799,7 +1365,11 @@ networkstatus_set_current_consensus(const char *consensus, waiting->body = NULL; waiting->set_at = 0; waiting->dl_failed = 0; - unlink(unverified_fname); + if (unlink(unverified_fname) != 0) { + log_warn(LD_FS, + "Failed to unlink %s: %s", + unverified_fname, strerror(errno)); + } } /* Reset the failure count only if this consensus is actually valid. */ @@ -1835,7 +1405,8 @@ networkstatus_set_current_consensus(const char *consensus, * current consensus really alter our view of any OR's rate limits? */ connection_or_update_token_buckets(get_connection_array(), options); - circuit_build_times_new_consensus_params(&circ_times, current_consensus); + circuit_build_times_new_consensus_params(get_circuit_build_times_mutable(), + current_consensus); } if (directory_caches_dir_info(options)) { @@ -1912,9 +1483,6 @@ routers_update_all_from_networkstatus(time_t now, int dir_version) networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now, FLAV_NS); - if (networkstatus_v2_list_has_changed) - download_status_map_update_from_v2_networkstatus(); - if (!consensus || dir_version < 3) /* nothing more we should do */ return; @@ -1969,35 +1537,6 @@ routers_update_all_from_networkstatus(time_t now, int dir_version) } } -/** Update v2_download_status_map to contain an entry for every router - * descriptor listed in the v2 networkstatuses. */ -static void -download_status_map_update_from_v2_networkstatus(void) -{ - digestmap_t *dl_status; - if (!networkstatus_v2_list) - return; - if (!v2_download_status_map) - v2_download_status_map = digestmap_new(); - - dl_status = digestmap_new(); - SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - 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)) - continue; - if (!(s = digestmap_remove(v2_download_status_map, d))) { - s = tor_malloc_zero(sizeof(download_status_t)); - } - digestmap_set(dl_status, d, s); - } SMARTLIST_FOREACH_END(rs); - } SMARTLIST_FOREACH_END(ns); - digestmap_free(v2_download_status_map, tor_free_); - v2_download_status_map = dl_status; - networkstatus_v2_list_has_changed = 0; -} - /** Update our view of the list of named servers from the most recently * retrieved networkstatus consensus. */ static void @@ -2029,14 +1568,11 @@ void routers_update_status_from_consensus_networkstatus(smartlist_t *routers, int reset_failures) { - dir_server_t *ds; const or_options_t *options = get_options(); - int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); + int authdir = authdir_mode_v3(options); networkstatus_t *ns = current_consensus; if (!ns || !smartlist_len(ns->routerstatus_list)) return; - if (!networkstatus_v2_list) - networkstatus_v2_list = smartlist_new(); routers_sort_by_identity(routers); @@ -2046,11 +1582,6 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, router->cache_info.identity_digest, DIGEST_LEN), { }) { - /* We have a routerstatus for this router. */ - const char *digest = router->cache_info.identity_digest; - - ds = router_get_fallback_dirserver_by_digest(digest); - /* Is it the same descriptor, or only the same identity? */ if (tor_memeq(router->cache_info.signed_descriptor_digest, rs->descriptor_digest, DIGEST_LEN)) { @@ -2068,30 +1599,11 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, dirserv_should_launch_reachability_test(router, old_router); } } - if (rs->is_flagged_running && ds) { - download_status_reset(&ds->v2_ns_dl_status); - } if (reset_failures) { download_status_reset(&rs->dl_status); } } SMARTLIST_FOREACH_JOIN_END(rs, router); - /* Now update last_listed_as_valid_until from v2 networkstatuses. */ - SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - time_t live_until = ns->published_on + V2_NETWORKSTATUS_ROUTER_LIFETIME; - SMARTLIST_FOREACH_JOIN(ns->entries, const routerstatus_t *, rs, - routers, routerinfo_t *, ri, - tor_memcmp(rs->identity_digest, - ri->cache_info.identity_digest, DIGEST_LEN), - STMT_NIL) { - if (tor_memeq(ri->cache_info.signed_descriptor_digest, - rs->descriptor_digest, DIGEST_LEN)) { - if (live_until > ri->cache_info.last_listed_as_valid_until) - ri->cache_info.last_listed_as_valid_until = live_until; - } - } SMARTLIST_FOREACH_JOIN_END(rs, ri); - } SMARTLIST_FOREACH_END(ns); - router_dir_info_changed(); } @@ -2166,7 +1678,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, node, ri, now, 0, 0, 0, 0); + set_routerstatus_from_routerinfo(&rs, node, ri, now, 0, 0); smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs)); } SMARTLIST_FOREACH_END(ri); @@ -2183,9 +1695,22 @@ networkstatus_dump_bridge_status_to_file(time_t now) char *status = networkstatus_getinfo_by_purpose("bridge", now); const or_options_t *options = get_options(); char *fname = NULL; + char *thresholds = NULL; + char *published_thresholds_and_status = NULL; + routerlist_t *rl = router_get_routerlist(); + char published[ISO_TIME_LEN+1]; + + format_iso_time(published, now); + dirserv_compute_bridge_flag_thresholds(rl); + thresholds = dirserv_get_flag_thresholds_line(); + tor_asprintf(&published_thresholds_and_status, + "published %s\nflag-thresholds %s\n%s", + published, thresholds, status); tor_asprintf(&fname, "%s"PATH_SEPARATOR"networkstatus-bridges", options->DataDirectory); - write_str_to_file(fname,status,0); + write_str_to_file(fname,published_thresholds_and_status,0); + tor_free(thresholds); + tor_free(published_thresholds_and_status); tor_free(fname); tor_free(status); } @@ -2388,6 +1913,33 @@ getinfo_helper_networkstatus(control_connection_t *conn, } else if (!strcmpstart(question, "ns/purpose/")) { *answer = networkstatus_getinfo_by_purpose(question+11, time(NULL)); return *answer ? 0 : -1; + } else if (!strcmp(question, "consensus/packages")) { + const networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && ns->package_lines) + *answer = smartlist_join_strings(ns->package_lines, "\n", 0, NULL); + else + *errmsg = "No consensus available"; + return *answer ? 0 : -1; + } else if (!strcmp(question, "consensus/valid-after") || + !strcmp(question, "consensus/fresh-until") || + !strcmp(question, "consensus/valid-until")) { + const networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns) { + time_t t; + if (!strcmp(question, "consensus/valid-after")) + t = ns->valid_after; + else if (!strcmp(question, "consensus/fresh-until")) + t = ns->fresh_until; + else + t = ns->valid_until; + + char tbuf[ISO_TIME_LEN+1]; + format_iso_time(tbuf, t); + *answer = tor_strdup(tbuf); + } else { + *errmsg = "No consensus available"; + } + return *answer ? 0 : -1; } else { return 0; } @@ -2402,15 +1954,6 @@ void networkstatus_free_all(void) { int i; - if (networkstatus_v2_list) { - SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, - networkstatus_v2_free(ns)); - smartlist_free(networkstatus_v2_list); - networkstatus_v2_list = NULL; - } - - digestmap_free(v2_download_status_map, tor_free_); - v2_download_status_map = NULL; networkstatus_vote_free(current_ns_consensus); networkstatus_vote_free(current_md_consensus); current_md_consensus = current_ns_consensus = NULL; diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 761f8e7f0e..d6e9e37013 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,16 +12,12 @@ #ifndef TOR_NETWORKSTATUS_H #define TOR_NETWORKSTATUS_H -/** How old do we allow a v2 network-status to get before removing it - * completely? */ -#define MAX_NETWORKSTATUS_AGE (10*24*60*60) +#include "testsupport.h" void networkstatus_reset_warnings(void); void networkstatus_reset_download_failures(void); -int router_reload_v2_networkstatus(void); int router_reload_consensus_networkstatus(void); void routerstatus_free(routerstatus_t *rs); -void networkstatus_v2_free(networkstatus_v2_t *ns); void networkstatus_vote_free(networkstatus_t *ns); networkstatus_voter_info_t *networkstatus_get_voter_by_id( networkstatus_t *vote, @@ -31,36 +27,29 @@ int networkstatus_check_consensus_signature(networkstatus_t *consensus, int networkstatus_check_document_signature(const networkstatus_t *consensus, document_signature_t *sig, const authority_cert_t *cert); -char *networkstatus_get_cache_filename(const char *identity_digest); -int router_set_networkstatus_v2(const char *s, time_t arrived_at, - v2_networkstatus_source_t source, - smartlist_t *requested_fingerprints); -void networkstatus_v2_list_clean(time_t now); int compare_digest_to_routerstatus_entry(const void *_key, const void **_member); int compare_digest_to_vote_routerstatus_entry(const void *_key, const void **_member); -const routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns, - const char *digest); 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); + +MOCK_DECL(download_status_t *,router_get_dl_status_by_descriptor_digest, + (const char *d)); + 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); +MOCK_DECL(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); @@ -69,15 +58,15 @@ int networkstatus_nickname_is_unnamed(const char *nickname); 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(const or_options_t *options); +int should_delay_dir_fetches(const or_options_t *options,const char **msg_out); void update_networkstatus_downloads(time_t now); void update_certificate_downloads(time_t now); int consensus_is_waiting_for_certs(void); int client_would_use_router(const routerstatus_t *rs, time_t now, const or_options_t *options); networkstatus_t *networkstatus_get_latest_consensus(void); -networkstatus_t *networkstatus_get_latest_consensus_by_flavor( - consensus_flavor_t f); +MOCK_DECL(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, int flavor); @@ -115,5 +104,9 @@ document_signature_t *document_signature_dup(const document_signature_t *sig); void networkstatus_free_all(void); int networkstatus_get_weight_scale_param(networkstatus_t *ns); +#ifdef NETWORKSTATUS_PRIVATE +STATIC void vote_routerstatus_free(vote_routerstatus_t *rs); +#endif + #endif diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 178f084b69..8f8adb42b5 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" @@ -24,7 +24,26 @@ static void nodelist_drop_node(node_t *node, int remove_from_ht); static void node_free(node_t *node); + +/** count_usable_descriptors counts descriptors with these flag(s) + */ +typedef enum { + /* All descriptors regardless of flags */ + USABLE_DESCRIPTOR_ALL = 0, + /* Only descriptors with the Exit flag */ + USABLE_DESCRIPTOR_EXIT_ONLY = 1 +} usable_descriptor_t; +static void count_usable_descriptors(int *num_present, + int *num_usable, + smartlist_t *descs_out, + const networkstatus_t *consensus, + const or_options_t *options, + time_t now, + routerset_t *in_set, + usable_descriptor_t exit_only); static void update_router_have_minimum_dir_info(void); +static double get_frac_paths_needed_for_circs(const or_options_t *options, + const networkstatus_t *ns); /** A nodelist_t holds a node_t object for every router we're "willing to use * for something". Specifically, it should hold a node_t for every node that @@ -41,14 +60,7 @@ typedef struct 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 + return (unsigned) siphash24g(node->identity, DIGEST_LEN); } static INLINE unsigned int @@ -58,8 +70,8 @@ node_id_eq(const node_t *node1, const node_t *node2) } 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); +HT_GENERATE2(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, + 0.6, tor_reallocarray_, tor_free_) /** The global nodelist. */ static nodelist_t *the_nodelist=NULL; @@ -90,8 +102,8 @@ node_get_mutable_by_id(const char *identity_digest) /** 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) +MOCK_IMPL(const node_t *, +node_get_by_id,(const char *identity_digest)) { return node_get_mutable_by_id(identity_digest); } @@ -211,7 +223,7 @@ void nodelist_set_consensus(networkstatus_t *ns) { const or_options_t *options = get_options(); - int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); + int authdir = authdir_mode_v3(options); int client = !server_mode(options); init_nodelist(); @@ -246,7 +258,6 @@ nodelist_set_consensus(networkstatus_t *ns) 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; node->ipv6_preferred = 0; @@ -272,8 +283,7 @@ nodelist_set_consensus(networkstatus_t *ns) 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 = - node->ipv6_preferred = 0; + node->is_bad_exit = node->ipv6_preferred = 0; } } } SMARTLIST_FOREACH_END(node); @@ -337,6 +347,25 @@ nodelist_drop_node(node_t *node, int remove_from_ht) node->nodelist_idx = -1; } +/** Return a newly allocated smartlist of the nodes that have <b>md</b> as + * their microdescriptor. */ +smartlist_t * +nodelist_find_nodes_with_microdesc(const microdesc_t *md) +{ + smartlist_t *result = smartlist_new(); + + if (the_nodelist == NULL) + return result; + + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + if (node->md == md) { + smartlist_add(result, node); + } + } SMARTLIST_FOREACH_END(node); + + return result; +} + /** Release storage held by <b>node</b> */ static void node_free(node_t *node) @@ -460,8 +489,8 @@ nodelist_assert_ok(void) /** 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) +MOCK_IMPL(smartlist_t *, +nodelist_get_list,(void)) { init_nodelist(); return the_nodelist->nodes; @@ -503,8 +532,8 @@ node_get_by_hex_id(const char *hex_id) * 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) +MOCK_IMPL(const node_t *, +node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) { const node_t *node; if (!the_nodelist) @@ -644,7 +673,7 @@ node_get_purpose(const node_t *node) /** 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> */ + * <b>verbose_name_out</b> */ void node_get_verbose_nickname(const node_t *node, char *verbose_name_out) @@ -660,6 +689,25 @@ node_get_verbose_nickname(const node_t *node, strlcpy(verbose_name_out+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); } +/** Compute the verbose ("extended") nickname of node with + * given <b>id_digest</b> and store it into the MAX_VERBOSE_NICKNAME_LEN+1 + * character buffer at <b>verbose_name_out</b> + * + * If node_get_by_id() returns NULL, base 16 encoding of + * <b>id_digest</b> is returned instead. */ +void +node_get_verbose_nickname_by_id(const char *id_digest, + char *verbose_name_out) +{ + const node_t *node = node_get_by_id(id_digest); + if (!node) { + verbose_name_out[0] = '$'; + base16_encode(verbose_name_out+1, HEX_DIGEST_LEN+1, id_digest, DIGEST_LEN); + } else { + node_get_verbose_nickname(node, verbose_name_out); + } +} + /** Return true iff it seems that <b>node</b> allows circuits to exit * through it directlry from the client. */ int @@ -771,7 +819,7 @@ void node_get_address_string(const node_t *node, char *buf, size_t len) { if (node->ri) { - strlcpy(buf, node->ri->address, len); + strlcpy(buf, fmt_addr32(node->ri->addr), len); } else if (node->rs) { tor_addr_t addr; tor_addr_from_ipv4h(&addr, node->rs->addr); @@ -1216,37 +1264,76 @@ router_set_status(const char *digest, int up) if (!up && node_is_me(node) && !net_is_disabled()) log_warn(LD_NET, "We just marked ourself as down. Are your external " "addresses reachable?"); + + if (bool_neq(node->is_running, up)) + router_dir_info_changed(); + node->is_running = up; } - - router_dir_info_changed(); } /** True iff, the last time we checked whether we had enough directory info - * to build circuits, the answer was "yes". */ + * to build circuits, the answer was "yes". If there are no exits in the + * consensus, we act as if we have 100% of the exit directory info. */ static int have_min_dir_info = 0; + +/** Does the consensus contain nodes that can exit? */ +static consensus_path_type_t have_consensus_path = CONSENSUS_PATH_UNKNOWN; + /** True iff enough has changed since the last time we checked whether we had * enough directory info to build circuits that our old answer can no longer * be trusted. */ static int need_to_update_have_min_dir_info = 1; /** String describing what we're missing before we have enough directory * info. */ -static char dir_info_status[256] = ""; - -/** Return true iff we have enough networkstatus and router information to - * start building circuits. Right now, this means "more than half the - * networkstatus documents, and at least 1/4 of expected routers." */ -//XXX should consider whether we have enough exiting nodes here. +static char dir_info_status[512] = ""; + +/** Return true iff we have enough consensus information to + * start building circuits. Right now, this means "a consensus that's + * less than a day old, and at least 60% of router descriptors (configurable), + * weighted by bandwidth. Treat the exit fraction as 100% if there are + * no exits in the consensus." + * To obtain the final weighted bandwidth, we multiply the + * weighted bandwidth fraction for each position (guard, middle, exit). */ int router_have_minimum_dir_info(void) { + static int logged_delay=0; + const char *delay_fetches_msg = NULL; + if (should_delay_dir_fetches(get_options(), &delay_fetches_msg)) { + if (!logged_delay) + log_notice(LD_DIR, "Delaying directory fetches: %s", delay_fetches_msg); + logged_delay=1; + strlcpy(dir_info_status, delay_fetches_msg, sizeof(dir_info_status)); + return 0; + } + logged_delay = 0; /* reset it if we get this far */ + if (PREDICT_UNLIKELY(need_to_update_have_min_dir_info)) { update_router_have_minimum_dir_info(); - need_to_update_have_min_dir_info = 0; } + return have_min_dir_info; } +/** Set to CONSENSUS_PATH_EXIT if there is at least one exit node + * in the consensus. We update this flag in compute_frac_paths_available if + * there is at least one relay that has an Exit flag in the consensus. + * Used to avoid building exit circuits when they will almost certainly fail. + * Set to CONSENSUS_PATH_INTERNAL if there are no exits in the consensus. + * (This situation typically occurs during bootstrap of a test network.) + * Set to CONSENSUS_PATH_UNKNOWN if we have never checked, or have + * reason to believe our last known value was invalid or has expired. + * If we're in a network with TestingDirAuthVoteExit set, + * this can cause router_have_consensus_path() to be set to + * CONSENSUS_PATH_EXIT, even if there are no nodes with accept exit policies. + */ +consensus_path_type_t +router_have_consensus_path(void) +{ + return have_consensus_path; +} + /** Called when our internal view of the directory has changed. This can be * when the authorities change, networkstatuses change, the list of routerdescs * changes, or number of running routers changes. @@ -1267,22 +1354,26 @@ get_dir_info_status_string(void) } /** Iterate over the servers listed in <b>consensus</b>, and count how many of - * them seem like ones we'd use, and how many of <em>those</em> we have - * descriptors for. Store the former in *<b>num_usable</b> and the latter in - * *<b>num_present</b>. If <b>in_set</b> is non-NULL, only consider those - * routers in <b>in_set</b>. If <b>exit_only</b> is true, only consider nodes - * with the Exit flag. If *descs_out is present, add a node_t for each - * usable descriptor to it. + * them seem like ones we'd use (store this in *<b>num_usable</b>), and how + * many of <em>those</em> we have descriptors for (store this in + * *<b>num_present</b>). + * + * If <b>in_set</b> is non-NULL, only consider those routers in <b>in_set</b>. + * If <b>exit_only</b> is USABLE_DESCRIPTOR_EXIT_ONLY, only consider nodes + * with the Exit flag. + * If *<b>descs_out</b> is present, add a node_t for each usable descriptor + * to it. */ static void count_usable_descriptors(int *num_present, int *num_usable, smartlist_t *descs_out, const networkstatus_t *consensus, const or_options_t *options, time_t now, - routerset_t *in_set, int exit_only) + routerset_t *in_set, + usable_descriptor_t exit_only) { const int md = (consensus->flavor == FLAV_MICRODESC); - *num_present = 0, *num_usable=0; + *num_present = 0, *num_usable = 0; SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, routerstatus_t *, rs) { @@ -1290,7 +1381,7 @@ count_usable_descriptors(int *num_present, int *num_usable, if (!node) continue; /* This would be a bug: every entry in the consensus is * supposed to have a node. */ - if (exit_only && ! rs->is_exit) + if (exit_only == USABLE_DESCRIPTOR_EXIT_ONLY && ! rs->is_exit) continue; if (in_set && ! routerset_contains_routerstatus(in_set, rs, -1)) continue; @@ -1314,11 +1405,22 @@ count_usable_descriptors(int *num_present, int *num_usable, log_debug(LD_DIR, "%d usable, %d present (%s%s).", *num_usable, *num_present, - md ? "microdesc" : "desc", exit_only ? " exits" : "s"); + md ? "microdesc" : "desc", + exit_only == USABLE_DESCRIPTOR_EXIT_ONLY ? " exits" : "s"); } -/** Return an extimate of which fraction of usable paths through the Tor - * network we have available for use. */ +/** Return an estimate of which fraction of usable paths through the Tor + * network we have available for use. Count how many routers seem like ones + * we'd use (store this in *<b>num_usable_out</b>), and how many of + * <em>those</em> we have descriptors for (store this in + * *<b>num_present_out</b>.) + * + * If **<b>status_out</b> is present, allocate a new string and print the + * available percentages of guard, middle, and exit nodes to it, noting + * whether there are exits in the consensus. + * If there are no guards in the consensus, + * we treat the exit fraction as 100%. + */ static double compute_frac_paths_available(const networkstatus_t *consensus, const or_options_t *options, time_t now, @@ -1328,16 +1430,20 @@ compute_frac_paths_available(const networkstatus_t *consensus, smartlist_t *guards = smartlist_new(); smartlist_t *mid = smartlist_new(); smartlist_t *exits = smartlist_new(); - smartlist_t *myexits= smartlist_new(); - double f_guard, f_mid, f_exit, f_myexit; - int np, nu; /* Ignored */ - const int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); + double f_guard, f_mid, f_exit; + double f_path = 0.0; + /* Used to determine whether there are any exits in the consensus */ + int np = 0; + /* Used to determine whether there are any exits with descriptors */ + int nu = 0; + const int authdir = authdir_mode_v3(options); count_usable_descriptors(num_present_out, num_usable_out, - mid, consensus, options, now, NULL, 0); + mid, consensus, options, now, NULL, + USABLE_DESCRIPTOR_ALL); if (options->EntryNodes) { count_usable_descriptors(&np, &nu, guards, consensus, options, now, - options->EntryNodes, 0); + options->EntryNodes, USABLE_DESCRIPTOR_ALL); } else { SMARTLIST_FOREACH(mid, const node_t *, node, { if (authdir) { @@ -1350,37 +1456,148 @@ compute_frac_paths_available(const networkstatus_t *consensus, }); } + /* All nodes with exit flag + * If we're in a network with TestingDirAuthVoteExit set, + * this can cause false positives on have_consensus_path, + * incorrectly setting it to CONSENSUS_PATH_EXIT. This is + * an unavoidable feature of forcing authorities to declare + * certain nodes as exits. + */ count_usable_descriptors(&np, &nu, exits, consensus, options, now, - NULL, 1); - count_usable_descriptors(&np, &nu, myexits, consensus, options, now, - options->ExitNodes, 1); + NULL, USABLE_DESCRIPTOR_EXIT_ONLY); + log_debug(LD_NET, + "%s: %d present, %d usable", + "exits", + np, + nu); + + /* We need at least 1 exit present in the consensus to consider + * building exit paths */ + /* Update our understanding of whether the consensus has exits */ + consensus_path_type_t old_have_consensus_path = have_consensus_path; + have_consensus_path = ((nu > 0) ? + CONSENSUS_PATH_EXIT : + CONSENSUS_PATH_INTERNAL); + + if (have_consensus_path == CONSENSUS_PATH_INTERNAL + && old_have_consensus_path != have_consensus_path) { + log_notice(LD_NET, + "The current consensus has no exit nodes. " + "Tor can only build internal paths, " + "such as paths to hidden services."); + + /* However, exit nodes can reachability self-test using this consensus, + * join the network, and appear in a later consensus. This will allow + * the network to build exit paths, such as paths for world wide web + * browsing (as distinct from hidden service web browsing). */ + } f_guard = frac_nodes_with_descriptors(guards, WEIGHT_FOR_GUARD); f_mid = frac_nodes_with_descriptors(mid, WEIGHT_FOR_MID); f_exit = frac_nodes_with_descriptors(exits, WEIGHT_FOR_EXIT); - f_myexit= frac_nodes_with_descriptors(myexits,WEIGHT_FOR_EXIT); + + log_debug(LD_NET, + "f_guard: %.2f, f_mid: %.2f, f_exit: %.2f", + f_guard, + f_mid, + f_exit); smartlist_free(guards); smartlist_free(mid); smartlist_free(exits); - smartlist_free(myexits); - /* This is a tricky point here: we don't want to make it easy for a - * directory to trickle exits to us until it learns which exits we have - * configured, so require that we have a threshold both of total exits - * and usable exits. */ - if (f_myexit < f_exit) - f_exit = f_myexit; + if (options->ExitNodes) { + double f_myexit, f_myexit_unflagged; + smartlist_t *myexits= smartlist_new(); + smartlist_t *myexits_unflagged = smartlist_new(); + + /* All nodes with exit flag in ExitNodes option */ + count_usable_descriptors(&np, &nu, myexits, consensus, options, now, + options->ExitNodes, USABLE_DESCRIPTOR_EXIT_ONLY); + log_debug(LD_NET, + "%s: %d present, %d usable", + "myexits", + np, + nu); + + /* Now compute the nodes in the ExitNodes option where which we don't know + * what their exit policy is, or we know it permits something. */ + count_usable_descriptors(&np, &nu, myexits_unflagged, + consensus, options, now, + options->ExitNodes, USABLE_DESCRIPTOR_ALL); + log_debug(LD_NET, + "%s: %d present, %d usable", + "myexits_unflagged (initial)", + np, + nu); + + SMARTLIST_FOREACH_BEGIN(myexits_unflagged, const node_t *, node) { + if (node_has_descriptor(node) && node_exit_policy_rejects_all(node)) { + SMARTLIST_DEL_CURRENT(myexits_unflagged, node); + /* this node is not actually an exit */ + np--; + /* this node is unusable as an exit */ + nu--; + } + } SMARTLIST_FOREACH_END(node); - tor_asprintf(status_out, - "%d%% of guards bw, " - "%d%% of midpoint bw, and " - "%d%% of exit bw", - (int)(f_guard*100), - (int)(f_mid*100), - (int)(f_exit*100)); + log_debug(LD_NET, + "%s: %d present, %d usable", + "myexits_unflagged (final)", + np, + nu); + + f_myexit= frac_nodes_with_descriptors(myexits,WEIGHT_FOR_EXIT); + f_myexit_unflagged= + frac_nodes_with_descriptors(myexits_unflagged,WEIGHT_FOR_EXIT); + + log_debug(LD_NET, + "f_exit: %.2f, f_myexit: %.2f, f_myexit_unflagged: %.2f", + f_exit, + f_myexit, + f_myexit_unflagged); + + /* If our ExitNodes list has eliminated every possible Exit node, and there + * were some possible Exit nodes, then instead consider nodes that permit + * exiting to some ports. */ + if (smartlist_len(myexits) == 0 && + smartlist_len(myexits_unflagged)) { + f_myexit = f_myexit_unflagged; + } - return f_guard * f_mid * f_exit; + smartlist_free(myexits); + smartlist_free(myexits_unflagged); + + /* This is a tricky point here: we don't want to make it easy for a + * directory to trickle exits to us until it learns which exits we have + * configured, so require that we have a threshold both of total exits + * and usable exits. */ + if (f_myexit < f_exit) + f_exit = f_myexit; + } + + /* if the consensus has no exits, treat the exit fraction as 100% */ + if (router_have_consensus_path() != CONSENSUS_PATH_EXIT) { + f_exit = 1.0; + } + + f_path = f_guard * f_mid * f_exit; + + if (status_out) + tor_asprintf(status_out, + "%d%% of guards bw, " + "%d%% of midpoint bw, and " + "%d%% of exit bw%s = " + "%d%% of path bw", + (int)(f_guard*100), + (int)(f_mid*100), + (int)(f_exit*100), + (router_have_consensus_path() == CONSENSUS_PATH_EXIT ? + "" : + " (no exits in consensus)"), + (int)(f_path*100)); + + return f_path; } /** We just fetched a new set of descriptors. Compute how far through @@ -1391,19 +1608,19 @@ count_loading_descriptors_progress(void) { int num_present = 0, num_usable=0; time_t now = time(NULL); + const or_options_t *options = get_options(); const networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); - double fraction; + double paths, fraction; if (!consensus) return 0; /* can't count descriptors if we have no list of them */ - count_usable_descriptors(&num_present, &num_usable, NULL, - consensus, get_options(), now, NULL, 0); + paths = compute_frac_paths_available(consensus, options, now, + &num_present, &num_usable, + NULL); - if (num_usable == 0) - return 0; /* don't div by 0 */ - fraction = num_present / (num_usable/4.); + fraction = paths / get_frac_paths_needed_for_circs(options,consensus); if (fraction > 1.0) return 0; /* it's not the number of descriptors holding us back */ return BOOTSTRAP_STATUS_LOADING_DESCRIPTORS + (int) @@ -1451,16 +1668,9 @@ update_router_have_minimum_dir_info(void) goto done; } - if (should_delay_dir_fetches(get_options())) { - log_notice(LD_DIR, "no known bridge descriptors running yet; stalling"); - strlcpy(dir_info_status, "No live bridge descriptors.", - sizeof(dir_info_status)); - res = 0; - goto done; - } - using_md = consensus->flavor == FLAV_MICRODESC; + /* Check fraction of available paths */ { char *status = NULL; int num_present=0, num_usable=0; @@ -1474,7 +1684,6 @@ update_router_have_minimum_dir_info(void) "can only build %d%% of likely paths. (We have %s.)", using_md?"micro":"", num_present, num_usable, (int)(paths*100), status); - /* log_notice(LD_NET, "%s", dir_info_status); */ tor_free(status); res = 0; control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0); @@ -1486,12 +1695,17 @@ update_router_have_minimum_dir_info(void) } done: + + /* If paths have just become available in this update. */ if (res && !have_min_dir_info) { - log_notice(LD_DIR, - "We now have enough directory information to build circuits."); control_event_client_status(LOG_NOTICE, "ENOUGH_DIR_INFO"); - control_event_bootstrap(BOOTSTRAP_STATUS_CONN_OR, 0); + if (control_event_bootstrap(BOOTSTRAP_STATUS_CONN_OR, 0) == 0) { + log_notice(LD_DIR, + "We now have enough directory information to build circuits."); + } } + + /* If paths have just become unavailable in this update. */ if (!res && have_min_dir_info) { int quiet = directory_too_idle_to_fetch_descriptors(options, now); tor_log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR, @@ -1502,8 +1716,8 @@ update_router_have_minimum_dir_info(void) * is back up and usable, and b) disable some activities that Tor * should only do while circuits are working, like reachability tests * and fetching bridge descriptors only over circuits. */ - can_complete_circuit = 0; - + note_that_we_maybe_cant_complete_circuits(); + have_consensus_path = CONSENSUS_PATH_UNKNOWN; control_event_client_status(LOG_NOTICE, "NOT_ENOUGH_DIR_INFO"); } have_min_dir_info = res; diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 8a4665a8bf..a131e0dd4e 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,7 +17,7 @@ } STMT_END node_t *node_get_mutable_by_id(const char *identity_digest); -const node_t *node_get_by_id(const char *identity_digest); +MOCK_DECL(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_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out); node_t *nodelist_add_microdesc(microdesc_t *md); @@ -26,13 +26,17 @@ 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); +smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md); void nodelist_free_all(void); void nodelist_assert_ok(void); -const node_t *node_get_by_nickname(const char *nickname, int warn_if_unnamed); +MOCK_DECL(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); +void node_get_verbose_nickname_by_id(const char *id_digest, + 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); @@ -57,7 +61,7 @@ void node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out); void node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out); int node_has_curve25519_onion_key(const node_t *node); -smartlist_t *nodelist_get_list(void); +MOCK_DECL(smartlist_t *, nodelist_get_list, (void)); /* Temporary during transition to multiple addresses. */ void node_get_addr(const node_t *node, tor_addr_t *addr_out); @@ -75,7 +79,37 @@ int node_is_unreliable(const node_t *router, int need_uptime, int router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port, int need_uptime); void router_set_status(const char *digest, int up); + +/** router_have_minimum_dir_info tests to see if we have enough + * descriptor information to create circuits. + * If there are exits in the consensus, we wait until we have enough + * info to create exit paths before creating any circuits. If there are + * no exits in the consensus, we wait for enough info to create internal + * paths, and should avoid creating exit paths, as they will simply fail. + * We make sure we create all available circuit types at the same time. */ int router_have_minimum_dir_info(void); + +/** Set to CONSENSUS_PATH_EXIT if there is at least one exit node + * in the consensus. We update this flag in compute_frac_paths_available if + * there is at least one relay that has an Exit flag in the consensus. + * Used to avoid building exit circuits when they will almost certainly fail. + * Set to CONSENSUS_PATH_INTERNAL if there are no exits in the consensus. + * (This situation typically occurs during bootstrap of a test network.) + * Set to CONSENSUS_PATH_UNKNOWN if we have never checked, or have + * reason to believe our last known value was invalid or has expired. + */ +typedef enum { + /* we haven't checked yet, or we have invalidated our previous check */ + CONSENSUS_PATH_UNKNOWN = -1, + /* The consensus only has internal relays, and we should only + * create internal paths, circuits, streams, ... */ + CONSENSUS_PATH_INTERNAL = 0, + /* The consensus has at least one exit, and can therefore (potentially) + * create exit and internal paths, circuits, streams, ... */ + CONSENSUS_PATH_EXIT = 1 +} consensus_path_type_t; +consensus_path_type_t router_have_consensus_path(void); + void router_dir_info_changed(void); const char *get_dir_info_status_string(void); int count_loading_descriptors_progress(void); diff --git a/src/or/ntmain.c b/src/or/ntmain.c index 8b67b86822..833d870041 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -1,9 +1,8 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define MAIN_PRIVATE #include "or.h" #include "config.h" #include "main.h" @@ -315,6 +314,7 @@ nt_service_main(void) case CMD_LIST_FINGERPRINT: case CMD_HASH_PASSWORD: case CMD_VERIFY_CONFIG: + case CMD_DUMP_CONFIG: log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, " "--hash-password, or --verify-config) in NT service."); break; diff --git a/src/or/ntmain.h b/src/or/ntmain.h index d3027936cd..eb55a296f6 100644 --- a/src/or/ntmain.h +++ b/src/or/ntmain.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,10 +13,8 @@ #define TOR_NTMAIN_H #ifdef _WIN32 -#if !defined (WINCE) #define NT_SERVICE #endif -#endif #ifdef NT_SERVICE int nt_service_parse_options(int argc, char **argv, int *should_exit); diff --git a/src/or/onion.c b/src/or/onion.c index 1a0bcf106e..4864792511 100644 --- a/src/or/onion.c +++ b/src/or/onion.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -22,7 +22,6 @@ #include "relay.h" #include "rephist.h" #include "router.h" -#include "tor_queue.h" /** Type for a linked list of circuits that are waiting for a free CPU worker * to process a waiting onion handshake. */ @@ -59,7 +58,7 @@ static void onion_queue_entry_remove(onion_queue_t *victim); * MAX_ONIONSKIN_CHALLENGE/REPLY_LEN." Also, make sure that we can pass * over-large values via EXTEND2/EXTENDED2, for future-compatibility.*/ -/** Return true iff we have room to queue another oninoskin of type +/** Return true iff we have room to queue another onionskin of type * <b>type</b>. */ static int have_room_for_onionskin(uint16_t type) @@ -112,15 +111,11 @@ have_room_for_onionskin(uint16_t type) (uint64_t)options->MaxOnionQueueDelay) return 0; -#ifdef CURVE25519_ENABLED /* If we support the ntor handshake, then don't let TAP handshakes use * more than 2/3 of the space on the queue. */ if (type == ONION_HANDSHAKE_TYPE_TAP && tap_usec / 1000 > (uint64_t)options->MaxOnionQueueDelay * 2 / 3) return 0; -#else - (void) type; -#endif return 1; } @@ -300,6 +295,8 @@ onion_pending_remove(or_circuit_t *circ) victim = circ->onionqueue_entry; if (victim) onion_queue_entry_remove(victim); + + cpuworker_cancel_circ_handshake(circ); } /** Remove a queue entry <b>victim</b> from the queue, unlinking it from @@ -330,50 +327,49 @@ onion_queue_entry_remove(onion_queue_t *victim) void clear_pending_onions(void) { - onion_queue_t *victim; + onion_queue_t *victim, *next; int i; for (i=0; i<=MAX_ONION_HANDSHAKE_TYPE; i++) { - while ((victim = TOR_TAILQ_FIRST(&ol_list[i]))) { + for (victim = TOR_TAILQ_FIRST(&ol_list[i]); victim; victim = next) { + next = TOR_TAILQ_NEXT(victim,next); onion_queue_entry_remove(victim); } + tor_assert(TOR_TAILQ_EMPTY(&ol_list[i])); } memset(ol_entries, 0, sizeof(ol_entries)); } /* ============================================================ */ -/** Fill in a server_onion_keys_t object at <b>keys</b> with all of the keys +/** Return a new server_onion_keys_t object with all of the keys * and other info we might need to do onion handshakes. (We make a copy of * our keys for each cpuworker to avoid race conditions with the main thread, * and to avoid locking) */ -void -setup_server_onion_keys(server_onion_keys_t *keys) +server_onion_keys_t * +server_onion_keys_new(void) { - memset(keys, 0, sizeof(server_onion_keys_t)); + server_onion_keys_t *keys = tor_malloc_zero(sizeof(server_onion_keys_t)); memcpy(keys->my_identity, router_get_my_id_digest(), DIGEST_LEN); dup_onion_keys(&keys->onion_key, &keys->last_onion_key); -#ifdef CURVE25519_ENABLED keys->curve25519_key_map = construct_ntor_key_map(); keys->junk_keypair = tor_malloc_zero(sizeof(curve25519_keypair_t)); curve25519_keypair_generate(keys->junk_keypair, 0); -#endif + return keys; } -/** Release all storage held in <b>keys</b>, but do not free <b>keys</b> - * itself (as it's likely to be stack-allocated.) */ +/** Release all storage held in <b>keys</b>. */ void -release_server_onion_keys(server_onion_keys_t *keys) +server_onion_keys_free(server_onion_keys_t *keys) { if (! keys) return; crypto_pk_free(keys->onion_key); crypto_pk_free(keys->last_onion_key); -#ifdef CURVE25519_ENABLED ntor_key_map_free(keys->curve25519_key_map); tor_free(keys->junk_keypair); -#endif - memset(keys, 0, sizeof(server_onion_keys_t)); + memwipe(keys, 0, sizeof(server_onion_keys_t)); + tor_free(keys); } /** Release whatever storage is held in <b>state</b>, depending on its @@ -390,12 +386,10 @@ onion_handshake_state_release(onion_handshake_state_t *state) fast_handshake_state_free(state->u.fast); state->u.fast = NULL; break; -#ifdef CURVE25519_ENABLED case ONION_HANDSHAKE_TYPE_NTOR: ntor_handshake_state_free(state->u.ntor); state->u.ntor = NULL; break; -#endif default: log_warn(LD_BUG, "called with unknown handshake state type %d", (int)state->tag); @@ -435,7 +429,6 @@ onion_skin_create(int type, r = CREATE_FAST_LEN; break; case ONION_HANDSHAKE_TYPE_NTOR: -#ifdef CURVE25519_ENABLED if (tor_mem_is_zero((const char*)node->curve25519_onion_key.public_key, CURVE25519_PUBKEY_LEN)) return -1; @@ -446,9 +439,6 @@ onion_skin_create(int type, return -1; r = NTOR_ONIONSKIN_LEN; -#else - return -1; -#endif break; default: log_warn(LD_BUG, "called with unknown handshake state type %d", type); @@ -500,7 +490,6 @@ onion_skin_server_handshake(int type, memcpy(rend_nonce_out, reply_out+DIGEST_LEN, DIGEST_LEN); break; case ONION_HANDSHAKE_TYPE_NTOR: -#ifdef CURVE25519_ENABLED if (onionskin_len < NTOR_ONIONSKIN_LEN) return -1; { @@ -521,9 +510,6 @@ onion_skin_server_handshake(int type, tor_free(keys_tmp); r = NTOR_REPLY_LEN; } -#else - return -1; -#endif break; default: log_warn(LD_BUG, "called with unknown handshake state type %d", type); @@ -540,48 +526,59 @@ onion_skin_server_handshake(int type, * bytes worth of key material in <b>keys_out_len</b>, set * <b>rend_authenticator_out</b> to the "KH" field that can be used to * establish introduction points at this hop, and return 0. On failure, - * return -1. */ + * return -1, and set *msg_out to an error message if this is worth + * complaining to the usre about. */ int onion_skin_client_handshake(int type, const onion_handshake_state_t *handshake_state, const uint8_t *reply, size_t reply_len, uint8_t *keys_out, size_t keys_out_len, - uint8_t *rend_authenticator_out) + uint8_t *rend_authenticator_out, + const char **msg_out) { if (handshake_state->tag != type) return -1; switch (type) { case ONION_HANDSHAKE_TYPE_TAP: - if (reply_len != TAP_ONIONSKIN_REPLY_LEN) + if (reply_len != TAP_ONIONSKIN_REPLY_LEN) { + if (msg_out) + *msg_out = "TAP reply was not of the correct length."; return -1; + } if (onion_skin_TAP_client_handshake(handshake_state->u.tap, (const char*)reply, - (char *)keys_out, keys_out_len) < 0) + (char *)keys_out, keys_out_len, + msg_out) < 0) return -1; memcpy(rend_authenticator_out, reply+DH_KEY_LEN, DIGEST_LEN); return 0; case ONION_HANDSHAKE_TYPE_FAST: - if (reply_len != CREATED_FAST_LEN) + if (reply_len != CREATED_FAST_LEN) { + if (msg_out) + *msg_out = "TAP reply was not of the correct length."; return -1; + } if (fast_client_handshake(handshake_state->u.fast, reply, - keys_out, keys_out_len) < 0) + keys_out, keys_out_len, msg_out) < 0) return -1; memcpy(rend_authenticator_out, reply+DIGEST_LEN, DIGEST_LEN); return 0; -#ifdef CURVE25519_ENABLED case ONION_HANDSHAKE_TYPE_NTOR: - if (reply_len < NTOR_REPLY_LEN) + if (reply_len < NTOR_REPLY_LEN) { + if (msg_out) + *msg_out = "ntor reply was not of the correct length."; return -1; + } { size_t keys_tmp_len = keys_out_len + DIGEST_LEN; uint8_t *keys_tmp = tor_malloc(keys_tmp_len); if (onion_skin_ntor_client_handshake(handshake_state->u.ntor, - reply, - keys_tmp, keys_tmp_len) < 0) { + reply, + keys_tmp, keys_tmp_len, msg_out) < 0) { tor_free(keys_tmp); return -1; } @@ -591,7 +588,6 @@ onion_skin_client_handshake(int type, tor_free(keys_tmp); } return 0; -#endif default: log_warn(LD_BUG, "called with unknown handshake state type %d", type); tor_fragile_assert(); @@ -630,12 +626,10 @@ check_create_cell(const create_cell_t *cell, int unknown_ok) if (cell->handshake_len != CREATE_FAST_LEN) return -1; break; -#ifdef CURVE25519_ENABLED case ONION_HANDSHAKE_TYPE_NTOR: if (cell->handshake_len != NTOR_ONIONSKIN_LEN) return -1; break; -#endif default: if (! unknown_ok) return -1; @@ -861,16 +855,19 @@ extend_cell_parse(extend_cell_t *cell_out, const uint8_t command, } case RELAY_COMMAND_EXTEND2: { - uint8_t n_specs = *payload, spectype, speclen; + uint8_t n_specs, spectype, speclen; int i; int found_ipv4 = 0, found_ipv6 = 0, found_id = 0; tor_addr_make_unspec(&cell_out->orport_ipv4.addr); tor_addr_make_unspec(&cell_out->orport_ipv6.addr); + if (payload_length == 0) + return -1; + cell_out->cell_type = RELAY_COMMAND_EXTEND2; - ++payload; + n_specs = *payload++; /* Parse the specifiers. We'll only take the first IPv4 and first IPv6 - * addres, and the node ID, and ignore everything else */ + * address, and the node ID, and ignore everything else */ for (i = 0; i < n_specs; ++i) { if (eop - payload < 2) return -1; diff --git a/src/or/onion.h b/src/or/onion.h index d62f032b87..45454f480d 100644 --- a/src/or/onion.h +++ b/src/or/onion.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -23,17 +23,15 @@ typedef struct server_onion_keys_t { uint8_t my_identity[DIGEST_LEN]; crypto_pk_t *onion_key; crypto_pk_t *last_onion_key; -#ifdef CURVE25519_ENABLED di_digest256_map_t *curve25519_key_map; curve25519_keypair_t *junk_keypair; -#endif } server_onion_keys_t; #define MAX_ONIONSKIN_CHALLENGE_LEN 255 #define MAX_ONIONSKIN_REPLY_LEN 255 -void setup_server_onion_keys(server_onion_keys_t *keys); -void release_server_onion_keys(server_onion_keys_t *keys); +server_onion_keys_t *server_onion_keys_new(void); +void server_onion_keys_free(server_onion_keys_t *keys); void onion_handshake_state_release(onion_handshake_state_t *state); @@ -51,7 +49,8 @@ int onion_skin_client_handshake(int type, const onion_handshake_state_t *handshake_state, const uint8_t *reply, size_t reply_len, uint8_t *keys_out, size_t key_out_len, - uint8_t *rend_authenticator_out); + uint8_t *rend_authenticator_out, + const char **msg_out); /** A parsed CREATE, CREATE_FAST, or CREATE2 cell. */ typedef struct create_cell_t { diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c index aa034a8bd6..7584112570 100644 --- a/src/or/onion_fast.c +++ b/src/or/onion_fast.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -22,7 +22,7 @@ fast_handshake_state_free(fast_handshake_state_t *victim) tor_free(victim); } -/** Create the state needed to perform a CREATE_FAST hasnshake. Return 0 +/** Create the state needed to perform a CREATE_FAST handshake. Return 0 * on success, -1 on failure. */ int fast_onionskin_create(fast_handshake_state_t **handshake_state_out, @@ -92,7 +92,8 @@ int fast_client_handshake(const fast_handshake_state_t *handshake_state, const uint8_t *handshake_reply_out,/*DIGEST_LEN*2 bytes*/ uint8_t *key_out, - size_t key_out_len) + size_t key_out_len, + const char **msg_out) { uint8_t tmp[DIGEST_LEN+DIGEST_LEN]; uint8_t *out; @@ -104,12 +105,14 @@ fast_client_handshake(const fast_handshake_state_t *handshake_state, out_len = key_out_len+DIGEST_LEN; out = tor_malloc(out_len); if (crypto_expand_key_material_TAP(tmp, sizeof(tmp), out, out_len)) { + if (msg_out) + *msg_out = "Failed to expand key material"; goto done; } if (tor_memneq(out, handshake_reply_out+DIGEST_LEN, DIGEST_LEN)) { /* H(K) does *not* match. Something fishy. */ - log_warn(LD_PROTOCOL,"Digest DOES NOT MATCH on fast handshake. " - "Bug or attack."); + if (msg_out) + *msg_out = "Digest DOES NOT MATCH on fast handshake. Bug or attack."; goto done; } memcpy(key_out, out+DIGEST_LEN, key_out_len); diff --git a/src/or/onion_fast.h b/src/or/onion_fast.h index 8c078378d2..d50d503a73 100644 --- a/src/or/onion_fast.h +++ b/src/or/onion_fast.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -32,7 +32,8 @@ int fast_server_handshake(const uint8_t *message_in, int fast_client_handshake(const fast_handshake_state_t *handshake_state, const uint8_t *handshake_reply_out, uint8_t *key_out, - size_t key_out_len); + size_t key_out_len, + const char **msg_out); #endif diff --git a/src/or/onion_ntor.c b/src/or/onion_ntor.c index 9cf7d5dd6e..539f06f61f 100644 --- a/src/or/onion_ntor.c +++ b/src/or/onion_ntor.c @@ -1,10 +1,10 @@ -/* Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "orconfig.h" -#include "crypto.h" #define ONION_NTOR_PRIVATE +#include "crypto.h" #include "onion_ntor.h" #include "torlog.h" #include "util.h" @@ -226,7 +226,8 @@ onion_skin_ntor_client_handshake( const ntor_handshake_state_t *handshake_state, const uint8_t *handshake_reply, uint8_t *key_out, - size_t key_out_len) + size_t key_out_len, + const char **msg_out) { const tweakset_t *T = &proto1_tweaks; /* Sensitive stack-allocated material. Kept in an anonymous struct to make @@ -256,7 +257,7 @@ onion_skin_ntor_client_handshake( si += CURVE25519_OUTPUT_LEN; curve25519_handshake(si, &handshake_state->seckey_x, &handshake_state->pubkey_B); - bad |= safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); + bad |= (safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN) << 1); si += CURVE25519_OUTPUT_LEN; APPEND(si, handshake_state->router_id, DIGEST_LEN); APPEND(si, handshake_state->pubkey_B.public_key, CURVE25519_PUBKEY_LEN); @@ -281,7 +282,7 @@ onion_skin_ntor_client_handshake( /* Compute auth */ h_tweak(s.auth, s.auth_input, sizeof(s.auth_input), T->t_mac); - bad |= tor_memneq(s.auth, auth_candidate, DIGEST256_LEN); + bad |= (tor_memneq(s.auth, auth_candidate, DIGEST256_LEN) << 2); crypto_expand_key_material_rfc5869_sha256( s.secret_input, sizeof(s.secret_input), @@ -290,6 +291,23 @@ onion_skin_ntor_client_handshake( key_out, key_out_len); memwipe(&s, 0, sizeof(s)); + + if (bad) { + if (bad & 4) { + if (msg_out) + *msg_out = NULL; /* Don't report this one; we probably just had the + * wrong onion key.*/ + log_fn(LOG_INFO, LD_PROTOCOL, + "Invalid result from curve25519 handshake: %d", bad); + } + if (bad & 3) { + if (msg_out) + *msg_out = "Zero output from curve25519 handshake"; + log_fn(LOG_WARN, LD_PROTOCOL, + "Invalid result from curve25519 handshake: %d", bad); + } + } + return bad ? -1 : 0; } diff --git a/src/or/onion_ntor.h b/src/or/onion_ntor.h index c942e6e0f0..0a39c1de16 100644 --- a/src/or/onion_ntor.h +++ b/src/or/onion_ntor.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_ONION_NTOR_H @@ -17,7 +17,6 @@ typedef struct ntor_handshake_state_t ntor_handshake_state_t; /** Length of an ntor reply, as sent from server to client. */ #define NTOR_REPLY_LEN 64 -#ifdef CURVE25519_ENABLED void ntor_handshake_state_free(ntor_handshake_state_t *state); int onion_skin_ntor_create(const uint8_t *router_id, @@ -37,7 +36,8 @@ int onion_skin_ntor_client_handshake( const ntor_handshake_state_t *handshake_state, const uint8_t *handshake_reply, uint8_t *key_out, - size_t key_out_len); + size_t key_out_len, + const char **msg_out); #ifdef ONION_NTOR_PRIVATE @@ -59,5 +59,3 @@ struct ntor_handshake_state_t { #endif -#endif - diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c index 3782e75abf..487cbeec04 100644 --- a/src/or/onion_tap.c +++ b/src/or/onion_tap.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -122,8 +122,9 @@ onion_skin_TAP_server_handshake( "Couldn't decrypt onionskin: client may be using old onion key"); goto err; } else if (len != DH_KEY_LEN) { - log_warn(LD_PROTOCOL, "Unexpected onionskin length after decryption: %ld", - (long)len); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Unexpected onionskin length after decryption: %ld", + (long)len); goto err; } @@ -182,7 +183,8 @@ int onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, const char *handshake_reply, /* TAP_ONIONSKIN_REPLY_LEN bytes */ char *key_out, - size_t key_out_len) + size_t key_out_len, + const char **msg_out) { ssize_t len; char *key_material=NULL; @@ -194,13 +196,16 @@ onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state, handshake_reply, DH_KEY_LEN, key_material, key_material_len); - if (len < 0) + if (len < 0) { + if (msg_out) + *msg_out = "DH computation failed."; goto err; + } if (tor_memneq(key_material, handshake_reply+DH_KEY_LEN, DIGEST_LEN)) { /* H(K) does *not* match. Something fishy. */ - log_warn(LD_PROTOCOL,"Digest DOES NOT MATCH on onion handshake. " - "Bug or attack."); + if (msg_out) + *msg_out = "Digest DOES NOT MATCH on onion handshake. Bug or attack."; goto err; } diff --git a/src/or/onion_tap.h b/src/or/onion_tap.h index b978b66737..c548b3d99f 100644 --- a/src/or/onion_tap.h +++ b/src/or/onion_tap.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -31,7 +31,8 @@ int onion_skin_TAP_server_handshake(const char *onion_skin, int onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, const char *handshake_reply, char *key_out, - size_t key_out_len); + size_t key_out_len, + const char **msg_out); #endif diff --git a/src/or/or.h b/src/or/or.h index 4459957a06..0d81b54d94 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,7 +14,7 @@ #include "orconfig.h" -#ifdef __COVERITY__ +#if defined(__clang_analyzer__) || defined(__COVERITY__) /* If we're building for a static analysis, turn on all the off-by-default * features. */ #ifndef INSTRUMENT_DOWNLOADS @@ -42,9 +42,6 @@ #include <sys/param.h> /* FreeBSD needs this to know what version it is */ #endif #include "torint.h" -#ifdef HAVE_SYS_WAIT_H -#include <sys/wait.h> -#endif #ifdef HAVE_SYS_FCNTL_H #include <sys/fcntl.h> #endif @@ -99,6 +96,7 @@ #include "ht.h" #include "replaycache.h" #include "crypto_curve25519.h" +#include "tor_queue.h" /* These signals are defined to help handle_control_signal work. */ @@ -121,6 +119,7 @@ * conflict with system-defined signals. */ #define SIGNEWNYM 129 #define SIGCLEARDNSCACHE 130 +#define SIGHEARTBEAT 131 #if (SIZEOF_CELL_T != 0) /* On Irix, stdlib.h defines a cell_t type, so we need to make sure @@ -195,6 +194,7 @@ typedef enum { * and let it use any circuit ID it wants. */ CIRC_ID_TYPE_NEITHER=2 } circ_id_type_t; +#define circ_id_type_bitfield_t ENUM_BF(circ_id_type_t) #define CONN_TYPE_MIN_ 3 /** Type for sockets listening for OR connections. */ @@ -213,8 +213,7 @@ typedef enum { #define CONN_TYPE_DIR_LISTENER 8 /** Type for HTTP connections to the directory server. */ #define CONN_TYPE_DIR 9 -/** Connection from the main process to a CPU worker process. */ -#define CONN_TYPE_CPUWORKER 10 +/* Type 10 is unused. */ /** Type for listening for connections from user interface process. */ #define CONN_TYPE_CONTROL_LISTENER 11 /** Type for connections from user interface process. */ @@ -227,8 +226,14 @@ typedef enum { #define CONN_TYPE_AP_NATD_LISTENER 14 /** Type for sockets listening for DNS requests. */ #define CONN_TYPE_AP_DNS_LISTENER 15 -#define CONN_TYPE_MAX_ 15 -/* !!!! If CONN_TYPE_MAX_ is ever over 15, we must grow the type field in + +/** Type for connections from the Extended ORPort. */ +#define CONN_TYPE_EXT_OR 16 +/** Type for sockets listening for Extended ORPort connections. */ +#define CONN_TYPE_EXT_OR_LISTENER 17 + +#define CONN_TYPE_MAX_ 17 +/* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in * connection_t. */ /* Proxy client types */ @@ -236,9 +241,11 @@ typedef enum { #define PROXY_CONNECT 1 #define PROXY_SOCKS4 2 #define PROXY_SOCKS5 3 -/* !!!! If there is ever a PROXY_* type over 2, we must grow the proxy_type +/* !!!! If there is ever a PROXY_* type over 3, we must grow the proxy_type * field in or_connection_t */ -/* pluggable transports proxy type */ + +/* Pluggable transport proxy type. Don't use this in or_connection_t, + * instead use the actual underlying proxy type (see above). */ #define PROXY_PLUGGABLE 4 /* Proxy client handshake states */ @@ -268,17 +275,6 @@ typedef enum { /** State for any listener connection. */ #define LISTENER_STATE_READY 0 -#define CPUWORKER_STATE_MIN_ 1 -/** State for a connection to a cpuworker process that's idle. */ -#define CPUWORKER_STATE_IDLE 1 -/** State for a connection to a cpuworker process that's processing a - * handshake. */ -#define CPUWORKER_STATE_BUSY_ONION 2 -#define CPUWORKER_STATE_MAX_ 2 - -#define CPUWORKER_TASK_ONION CPUWORKER_STATE_BUSY_ONION -#define CPUWORKER_TASK_SHUTDOWN 255 - #define OR_CONN_STATE_MIN_ 1 /** State for a connection to an OR: waiting for connect() to finish. */ #define OR_CONN_STATE_CONNECTING 1 @@ -306,6 +302,25 @@ typedef enum { #define OR_CONN_STATE_OPEN 8 #define OR_CONN_STATE_MAX_ 8 +/** States of the Extended ORPort protocol. Be careful before changing + * the numbers: they matter. */ +#define EXT_OR_CONN_STATE_MIN_ 1 +/** Extended ORPort authentication is waiting for the authentication + * type selected by the client. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE 1 +/** Extended ORPort authentication is waiting for the client nonce. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE 2 +/** Extended ORPort authentication is waiting for the client hash. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH 3 +#define EXT_OR_CONN_STATE_AUTH_MAX 3 +/** Authentication finished and the Extended ORPort is now accepting + * traffic. */ +#define EXT_OR_CONN_STATE_OPEN 4 +/** Extended ORPort is flushing its last messages and preparing to + * start accepting OR connections. */ +#define EXT_OR_CONN_STATE_FLUSHING 5 +#define EXT_OR_CONN_STATE_MAX_ 5 + #define EXIT_CONN_STATE_MIN_ 1 /** State for an exit connection: waiting for response from DNS farm. */ #define EXIT_CONN_STATE_RESOLVING 1 @@ -372,16 +387,10 @@ typedef enum { #define CONTROL_CONN_STATE_NEEDAUTH 2 #define CONTROL_CONN_STATE_MAX_ 2 -#define DIR_PURPOSE_MIN_ 3 -/** A connection to a directory server: download a rendezvous - * descriptor. */ -#define DIR_PURPOSE_FETCH_RENDDESC 3 -/** A connection to a directory server: set after a rendezvous +#define DIR_PURPOSE_MIN_ 4 +/** A connection to a directory server: set after a v2 rendezvous * descriptor is downloaded. */ -#define DIR_PURPOSE_HAS_FETCHED_RENDDESC 4 -/** A connection to a directory server: download one or more v2 - * network-status objects */ -#define DIR_PURPOSE_FETCH_V2_NETWORKSTATUS 5 +#define DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 4 /** A connection to a directory server: download one or more server * descriptors. */ #define DIR_PURPOSE_FETCH_SERVERDESC 6 @@ -390,9 +399,6 @@ typedef enum { #define DIR_PURPOSE_FETCH_EXTRAINFO 7 /** A connection to a directory server: upload a server descriptor. */ #define DIR_PURPOSE_UPLOAD_DIR 8 -/** A connection to a directory server: upload a rendezvous - * descriptor. */ -#define DIR_PURPOSE_UPLOAD_RENDDESC 9 /** A connection to a directory server: upload a v3 networkstatus vote. */ #define DIR_PURPOSE_UPLOAD_VOTE 10 /** A connection to a directory server: upload a v3 consensus signature */ @@ -426,7 +432,6 @@ typedef enum { * directory server. */ #define DIR_PURPOSE_IS_UPLOAD(p) \ ((p)==DIR_PURPOSE_UPLOAD_DIR || \ - (p)==DIR_PURPOSE_UPLOAD_RENDDESC || \ (p)==DIR_PURPOSE_UPLOAD_VOTE || \ (p)==DIR_PURPOSE_UPLOAD_SIGNATURES) @@ -585,7 +590,8 @@ typedef enum { #define END_OR_CONN_REASON_NO_ROUTE 6 /* no route to host/net */ #define END_OR_CONN_REASON_IO_ERROR 7 /* read/write error */ #define END_OR_CONN_REASON_RESOURCE_LIMIT 8 /* sockets, buffers, etc */ -#define END_OR_CONN_REASON_MISC 9 +#define END_OR_CONN_REASON_PT_MISSING 9 /* PT failed or not available */ +#define END_OR_CONN_REASON_MISC 10 /* Reasons why we (or a remote OR) might close a stream. See tor-spec.txt for * documentation of these. The values must match. */ @@ -659,6 +665,10 @@ typedef enum { /* Negative reasons are internal: we never send them in a DESTROY or TRUNCATE * call; they only go to the controller for tracking */ + +/* Closing introduction point that were opened in parallel. */ +#define END_CIRC_REASON_IP_NOW_REDUNDANT -4 + /** Our post-timeout circuit time measurement period expired. * We must give up now */ #define END_CIRC_REASON_MEASUREMENT_EXPIRED -3 @@ -823,9 +833,15 @@ typedef enum { /** Maximum number of queued cells on a circuit for which we are the * midpoint before we give up and kill it. This must be >= circwindow * to avoid killing innocent circuits, and >= circwindow*2 to give - * leaky-pipe a chance for being useful someday. + * leaky-pipe a chance of working someday. The ORCIRC_MAX_MIDDLE_KILL_THRESH + * ratio controls the margin of error between emitting a warning and + * killing the circuit. */ -#define ORCIRC_MAX_MIDDLE_CELLS (21*(CIRCWINDOW_START_MAX)/10) +#define ORCIRC_MAX_MIDDLE_CELLS (CIRCWINDOW_START_MAX*2) +/** Ratio of hard (circuit kill) to soft (warning) thresholds for the + * ORCIRC_MAX_MIDDLE_CELLS tests. + */ +#define ORCIRC_MAX_MIDDLE_KILL_THRESH (1.1f) /* Cell commands. These values are defined in tor-spec.txt. */ #define CELL_PADDING 0 @@ -846,6 +862,7 @@ typedef enum { #define CELL_AUTH_CHALLENGE 130 #define CELL_AUTHENTICATE 131 #define CELL_AUTHORIZE 132 +#define CELL_COMMAND_MAX_ 132 /** How long to test reachability before complaining to the user. */ #define TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT (20*60) @@ -1073,9 +1090,17 @@ typedef struct var_cell_t { uint8_t payload[FLEXIBLE_ARRAY_MEMBER]; } var_cell_t; +/** A parsed Extended ORPort message. */ +typedef struct ext_or_cmd_t { + uint16_t cmd; /** Command type */ + uint16_t len; /** Body length */ + char body[FLEXIBLE_ARRAY_MEMBER]; /** Message body */ +} ext_or_cmd_t; + /** A cell as packed for writing to the network. */ typedef struct packed_cell_t { - struct packed_cell_t *next; /**< Next cell queued on this circuit. */ + /** Next cell queued on this circuit. */ + TOR_SIMPLEQ_ENTRY(packed_cell_t) next; char body[CELL_MAX_NETWORK_SIZE]; /**< Cell as packed for network. */ uint32_t inserted_time; /**< Time (in milliseconds since epoch, with high * bits truncated) when this cell was inserted. */ @@ -1084,8 +1109,8 @@ typedef struct packed_cell_t { /** A queue of cells on a circuit, waiting to be added to the * or_connection_t's outbuf. */ typedef struct cell_queue_t { - packed_cell_t *head; /**< The first cell, or NULL if the queue is empty. */ - packed_cell_t *tail; /**< The last cell, or NULL if the queue is empty. */ + /** Linked list of packed_cell_t*/ + TOR_SIMPLEQ_HEAD(cell_simpleq, packed_cell_t) head; int n; /**< The number of cells in the queue. */ } cell_queue_t; @@ -1106,6 +1131,51 @@ typedef struct socks_request_t socks_request_t; #define generic_buffer_t buf_t #endif +typedef struct entry_port_cfg_t { + /* Client port types (socks, dns, trans, natd) only: */ + uint8_t isolation_flags; /**< Zero or more isolation flags */ + int session_group; /**< A session group, or -1 if this port is not in a + * session group. */ + + /* Socks only: */ + /** When both no-auth and user/pass are advertised by a SOCKS client, select + * no-auth. */ + unsigned int socks_prefer_no_auth : 1; + + /* Client port types only: */ + unsigned int ipv4_traffic : 1; + unsigned int ipv6_traffic : 1; + unsigned int prefer_ipv6 : 1; + + /** For a socks listener: should we cache IPv4/IPv6 DNS information that + * exit nodes tell us? + * + * @{ */ + unsigned int cache_ipv4_answers : 1; + unsigned int cache_ipv6_answers : 1; + /** @} */ + /** For a socks listeners: if we find an answer in our client-side DNS cache, + * should we use it? + * + * @{ */ + unsigned int use_cached_ipv4_answers : 1; + unsigned int use_cached_ipv6_answers : 1; + /** @} */ + /** For socks listeners: When we can automap an address to IPv4 or IPv6, + * do we prefer IPv6? */ + unsigned int prefer_ipv6_virtaddr : 1; + +} entry_port_cfg_t; + +typedef struct server_port_cfg_t { + /* Server port types (or, dir) only: */ + unsigned int no_advertise : 1; + unsigned int no_listen : 1; + unsigned int all_addrs : 1; + unsigned int bind_ipv4_only : 1; + unsigned int bind_ipv6_only : 1; +} server_port_cfg_t; + /* Values for connection_t.magic: used to make sure that downcasts (casts from * connection_t to foo_connection_t) are safe. */ #define BASE_CONNECTION_MAGIC 0x7C3C304Eu @@ -1139,7 +1209,7 @@ typedef struct connection_t { * *_CONNECTION_MAGIC. */ uint8_t state; /**< Current state of this connection. */ - unsigned int type:4; /**< What kind of connection is this? */ + unsigned int type:5; /**< What kind of connection is this? */ unsigned int purpose:5; /**< Only used for DIR and EXIT types currently. */ /* The next fields are all one-bit booleans. Some are only applicable to @@ -1223,6 +1293,14 @@ typedef struct connection_t { /** Unique identifier for this connection on this Tor instance. */ uint64_t global_identifier; + + /** Bytes read since last call to control_event_conn_bandwidth_used(). + * Only used if we're configured to emit CONN_BW events. */ + uint32_t n_read_conn_bw; + + /** Bytes written since last call to control_event_conn_bandwidth_used(). + * Only used if we're configured to emit CONN_BW events. */ + uint32_t n_written_conn_bw; } connection_t; /** Subtype of connection_t; used for a listener socket. */ @@ -1233,52 +1311,7 @@ typedef struct listener_connection_t { * to the evdns_server_port it uses to listen to and answer connections. */ struct evdns_server_port *dns_server_port; - /** @name Isolation parameters - * - * For an AP listener, these fields describe how to isolate streams that - * arrive on the listener. - * - * @{ - */ - /** The session group for this listener. */ - int session_group; - /** One or more ISO_ flags to describe how to isolate streams. */ - uint8_t isolation_flags; - /**@}*/ - /** For SOCKS connections only: If this is set, we will choose "no - * authentication" instead of "username/password" authentication if both - * are offered. Used as input to parse_socks. */ - unsigned int socks_prefer_no_auth : 1; - - /** For a SOCKS listeners, these fields describe whether we should - * allow IPv4 and IPv6 addresses from our exit nodes, respectively. - * - * @{ - */ - unsigned int socks_ipv4_traffic : 1; - unsigned int socks_ipv6_traffic : 1; - /** @} */ - /** For a socks listener: should we tell the exit that we prefer IPv6 - * addresses? */ - unsigned int socks_prefer_ipv6 : 1; - - /** For a socks listener: should we cache IPv4/IPv6 DNS information that - * exit nodes tell us? - * - * @{ */ - unsigned int cache_ipv4_answers : 1; - unsigned int cache_ipv6_answers : 1; - /** @} */ - /** For a socks listeners: if we find an answer in our client-side DNS cache, - * should we use it? - * - * @{ */ - unsigned int use_cached_ipv4_answers : 1; - unsigned int use_cached_ipv6_answers : 1; - /** @} */ - /** For socks listeners: When we can automap an address to IPv4 or IPv6, - * do we prefer IPv6? */ - unsigned int prefer_ipv6_virtaddr : 1; + entry_port_cfg_t entry_cfg; } listener_connection_t; @@ -1384,6 +1417,21 @@ typedef struct or_handshake_state_t { /**@}*/ } or_handshake_state_t; +/** Length of Extended ORPort connection identifier. */ +#define EXT_OR_CONN_ID_LEN DIGEST_LEN /* 20 */ +/* + * OR_CONN_HIGHWATER and OR_CONN_LOWWATER moved from connection_or.c so + * channeltls.c can see them too. + */ + +/** When adding cells to an OR connection's outbuf, keep adding until the + * outbuf is at least this long, or we run out of cells. */ +#define OR_CONN_HIGHWATER (32*1024) + +/** Add cells to an OR connection's outbuf whenever the outbuf's data length + * drops below this size. */ +#define OR_CONN_LOWWATER (16*1024) + /** Subtype of connection_t for an "OR connection" -- that is, one that speaks * cells over TLS. */ typedef struct or_connection_t { @@ -1392,6 +1440,20 @@ typedef struct or_connection_t { /** Hash of the public RSA key for the other side's identity key, or zeroes * if the other side hasn't shown us a valid identity key. */ char identity_digest[DIGEST_LEN]; + + /** Extended ORPort connection identifier. */ + char *ext_or_conn_id; + /** This is the ClientHash value we expect to receive from the + * client during the Extended ORPort authentication protocol. We + * compute it upon receiving the ClientNoce from the client, and we + * compare it with the acual ClientHash value sent by the + * client. */ + char *ext_or_auth_correct_client_hash; + /** String carrying the name of the pluggable transport + * (e.g. "obfs2") that is obfuscating this connection. If no + * pluggable transports are used, it's NULL. */ + char *ext_or_transport; + char *nickname; /**< Nickname of OR on other side (if any). */ tor_tls_t *tls; /**< TLS connection state. */ @@ -1422,15 +1484,20 @@ typedef struct or_connection_t { unsigned int is_outgoing:1; unsigned int proxy_type:2; /**< One of PROXY_NONE...PROXY_SOCKS5 */ unsigned int wide_circ_ids:1; + /** True iff this connection has had its bootstrap failure logged with + * control_event_bootstrap_problem. */ + unsigned int have_noted_bootstrap_problem:1; + uint16_t link_proto; /**< What protocol version are we using? 0 for * "none negotiated yet." */ - + uint16_t idle_timeout; /**< How long can this connection sit with no + * circuits on it before we close it? Based on + * IDLE_CIRCUIT_TIMEOUT_{NON,}CANONICAL and + * on is_canonical, randomized. */ or_handshake_state_t *handshake_state; /**< If we are setting this connection * up, state information to do so. */ time_t timestamp_lastempty; /**< When was the outbuf last completely empty?*/ - time_t timestamp_last_added_nonpadding; /** When did we last add a - * non-padding cell to the outbuf? */ /* bandwidth* and *_bucket only used by ORs in OPEN state: */ int bandwidthrate; /**< Bytes/s added to the bucket. (OPEN ORs only.) */ @@ -1449,6 +1516,18 @@ typedef struct or_connection_t { struct or_connection_t *next_with_same_id; /**< Next connection with same * identity digest as this one. */ + /** Last emptied read token bucket in msec since midnight; only used if + * TB_EMPTY events are enabled. */ + uint32_t read_emptied_time; + /** Last emptied write token bucket in msec since midnight; only used if + * TB_EMPTY events are enabled. */ + uint32_t write_emptied_time; + + /* + * Count the number of bytes flushed out on this orconn, and the number of + * bytes TLS actually sent - used for overhead estimation for scheduling. + */ + uint64_t bytes_xmitted, bytes_xmitted_by_tls; } or_connection_t; /** Subtype of connection_t for an "edge connection" -- that is, an entry (ap) @@ -1520,12 +1599,10 @@ typedef struct entry_connection_t { * only.) */ /* === Isolation related, AP only. === */ - /** AP only: based on which factors do we isolate this stream? */ - uint8_t isolation_flags; - /** AP only: what session group is this stream in? */ - int session_group; + entry_port_cfg_t entry_cfg; /** AP only: The newnym epoch in which we created this connection. */ unsigned nym_epoch; + /** AP only: The original requested address before we rewrote it. */ char *original_dest_address; /* Other fields to isolate on already exist. The ClientAddr is addr. The @@ -1584,33 +1661,8 @@ typedef struct entry_connection_t { */ unsigned int may_use_optimistic_data : 1; - /** Should we permit IPv4 and IPv6 traffic to use this connection? - * - * @{ */ - unsigned int ipv4_traffic_ok : 1; - unsigned int ipv6_traffic_ok : 1; - /** @} */ - /** Should we say we prefer IPv6 traffic? */ - unsigned int prefer_ipv6_traffic : 1; - - /** For a socks listener: should we cache IPv4/IPv6 DNS information that - * exit nodes tell us? - * - * @{ */ - unsigned int cache_ipv4_answers : 1; - unsigned int cache_ipv6_answers : 1; - /** @} */ - /** For a socks listeners: if we find an answer in our client-side DNS cache, - * should we use it? - * - * @{ */ - unsigned int use_cached_ipv4_answers : 1; - unsigned int use_cached_ipv6_answers : 1; - /** @} */ - /** For socks listeners: When we can automap an address to IPv4 or IPv6, - * do we prefer IPv6? */ - unsigned int prefer_ipv6_virtaddr : 1; - + /** Are we a socks SocksSocket listener? */ + unsigned int is_socks_socket:1; } entry_connection_t; typedef enum { @@ -1619,6 +1671,7 @@ typedef enum { DIR_SPOOL_CACHED_DIR, DIR_SPOOL_NETWORKSTATUS, DIR_SPOOL_MICRODESC, /* NOTE: if we add another entry, add another bit. */ } dir_spool_source_t; +#define dir_spool_source_bitfield_t ENUM_BF(dir_spool_source_t) /** Subtype of connection_t for an "directory connection" -- that is, an HTTP * connection to retrieve or serve directory material. */ @@ -1638,7 +1691,7 @@ typedef struct dir_connection_t { * "spooling" of directory material to the outbuf. Otherwise, we'd have * to append everything to the outbuf in one enormous chunk. */ /** What exactly are we spooling right now? */ - ENUM_BF(dir_spool_source_t) dir_spool_src : 3; + dir_spool_source_bitfield_t dir_spool_src : 3; /** If we're fetching descriptors, what router purpose shall we assign * to them? */ @@ -1668,8 +1721,9 @@ typedef struct dir_connection_t { typedef struct control_connection_t { connection_t base_; - uint32_t event_mask; /**< Bitfield: which events does this controller - * care about? */ + uint64_t event_mask; /**< Bitfield: which events does this controller + * care about? + * EVENT_MAX_ is >31, so we need a 64 bit mask */ /** True if we have sent a protocolinfo reply on this connection. */ unsigned int have_sent_protocolinfo:1; @@ -1811,12 +1865,13 @@ typedef enum { ADDR_POLICY_ACCEPT=1, ADDR_POLICY_REJECT=2, } addr_policy_action_t; +#define addr_policy_action_bitfield_t ENUM_BF(addr_policy_action_t) /** A reference-counted address policy rule. */ typedef struct addr_policy_t { int refcnt; /**< Reference count */ /** What to do when the policy matches.*/ - ENUM_BF(addr_policy_action_t) policy_type:2; + addr_policy_action_bitfield_t policy_type:2; unsigned int is_private:1; /**< True iff this is the pseudo-address, * "private". */ unsigned int is_canonical:1; /**< True iff this policy is the canonical @@ -1868,6 +1923,7 @@ typedef enum { */ SAVED_IN_JOURNAL } saved_location_t; +#define saved_location_bitfield_t ENUM_BF(saved_location_t) /** Enumeration: what kind of download schedule are we using for a given * object? */ @@ -1876,6 +1932,7 @@ typedef enum { DL_SCHED_CONSENSUS = 1, DL_SCHED_BRIDGE = 2, } download_schedule_t; +#define download_schedule_bitfield_t ENUM_BF(download_schedule_t) /** Information about our plans for retrying downloads for a downloadable * object. */ @@ -1884,7 +1941,8 @@ typedef struct download_status_t { * again? */ uint8_t n_download_failures; /**< Number of failures trying to download the * most recent descriptor. */ - ENUM_BF(download_schedule_t) schedule : 8; + download_schedule_bitfield_t schedule : 8; + } download_status_t; /** If n_download_failures is this high, the download can never happen. */ @@ -1926,9 +1984,7 @@ typedef struct signed_descriptor_t { * routerlist->old_routers? -1 for none. */ int routerlist_index; /** The valid-until time of the most recent consensus that listed this - * descriptor, or a bit after the publication time of the most recent v2 - * networkstatus that listed it. 0 for "never listed in a consensus or - * status, so far as we know." */ + * descriptor. 0 for "never listed in a consensus, so far as we know." */ time_t last_listed_as_valid_until; /* If true, we do not ever try to save this object in the cache. */ unsigned int do_not_cache : 1; @@ -1947,7 +2003,6 @@ typedef int16_t country_t; /** Information about another onion router in the network. */ typedef struct { signed_descriptor_t cache_info; - char *address; /**< Location of OR: either a hostname or an IP address. */ char *nickname; /**< Human-readable OR name. */ uint32_t addr; /**< IPv4 address of OR, in host order. */ @@ -2065,16 +2120,10 @@ typedef struct routerstatus_t { unsigned int is_unnamed:1; /**< True iff "nickname" belongs to another * router. */ unsigned int is_valid:1; /**< True iff this router isn't invalid. */ - unsigned int is_v2_dir:1; /**< True iff this router can serve directory - * information with v2 of the directory - * protocol. (All directory caches cache v1 - * directories.) */ unsigned int is_possible_guard:1; /**< True iff this router would be a good * choice as an entry guard. */ unsigned int is_bad_exit:1; /**< True iff this node is a bad choice for * an exit node. */ - 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 v2-or-later hidden * service directory. */ /** True iff we know version info for this router. (i.e., a "v" entry was @@ -2085,9 +2134,6 @@ typedef struct routerstatus_t { /** True iff this router is a version that, if it caches directory info, * we can get microdescriptors from. */ unsigned int version_supports_microdesc_cache:1; - /** True iff this router is a version that allows DATA cells to arrive on - * a stream before it has sent a CONNECTED cell. */ - unsigned int version_supports_optimistic_data:1; /** True iff this router has a version that allows it to accept EXTEND2 * cells */ unsigned int version_supports_extend2_cells:1; @@ -2099,18 +2145,18 @@ typedef struct routerstatus_t { uint32_t bandwidth_kb; /**< Bandwidth (capacity) of the router as reported in * the vote/consensus, in kilobytes/sec. */ + + /** The consensus has guardfraction information for this router. */ + unsigned int has_guardfraction:1; + /** The guardfraction value of this router. */ + uint32_t guardfraction_percentage; + char *exitsummary; /**< exit policy summary - * XXX weasel: this probably should not stay a string. */ /* ---- The fields below aren't derived from the networkstatus; they * hold local information only. */ - /** True if we, as a directory mirror, want to download the corresponding - * routerinfo from the authority who gave us this routerstatus. (That is, - * if we don't have the routerinfo, and if we haven't already tried to get it - * from this authority.) Applies in v2 networkstatus document only. - */ - unsigned int need_to_mirror:1; 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; @@ -2152,7 +2198,7 @@ typedef struct microdesc_t { */ time_t last_listed; /** Where is this microdescriptor currently stored? */ - ENUM_BF(saved_location_t) saved_location : 3; + saved_location_bitfield_t saved_location : 3; /** If true, do not attempt to cache this microdescriptor on disk. */ unsigned int no_save : 1; /** If true, this microdesc has an entry in the microdesc_map */ @@ -2240,8 +2286,6 @@ typedef struct node_t { 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. */ @@ -2275,52 +2319,6 @@ typedef struct node_t { } node_t; -/** How many times will we try to download a router's descriptor before giving - * up? */ -#define MAX_ROUTERDESC_DOWNLOAD_FAILURES 8 - -/** How many times will we try to download a microdescriptor before giving - * up? */ -#define MAX_MICRODESC_DOWNLOAD_FAILURES 8 - -/** Contents of a v2 (non-consensus, non-vote) network status object. */ -typedef struct networkstatus_v2_t { - /** When did we receive the network-status document? */ - time_t received_on; - - /** What was the digest of the document? */ - char networkstatus_digest[DIGEST_LEN]; - - /* These fields come from the actual network-status document.*/ - time_t published_on; /**< Declared publication date. */ - - char *source_address; /**< Canonical directory server hostname. */ - uint32_t source_addr; /**< Canonical directory server IP. */ - uint16_t source_dirport; /**< Canonical directory server dirport. */ - - unsigned int binds_names:1; /**< True iff this directory server binds - * names. */ - unsigned int recommends_versions:1; /**< True iff this directory server - * recommends client and server software - * versions. */ - unsigned int lists_bad_exits:1; /**< True iff this directory server marks - * malfunctioning exits as bad. */ - /** True iff this directory server marks malfunctioning directories as - * bad. */ - unsigned int lists_bad_directories:1; - - char identity_digest[DIGEST_LEN]; /**< Digest of signing key. */ - char *contact; /**< How to contact directory admin? (may be NULL). */ - crypto_pk_t *signing_key; /**< Key used to sign this directory. */ - char *client_versions; /**< comma-separated list of recommended client - * versions. */ - char *server_versions; /**< comma-separated list of recommended server - * versions. */ - - smartlist_t *entries; /**< List of routerstatus_t*. This list is kept - * sorted by identity_digest. */ -} networkstatus_v2_t; - /** Linked list of microdesc hash lines for a single router in a directory * vote. */ @@ -2408,8 +2406,8 @@ typedef enum { /** A common structure to hold a v3 network status vote, or a v3 network * status consensus. */ typedef struct networkstatus_t { - ENUM_BF(networkstatus_type_t) type : 8; /**< Vote, consensus, or opinion? */ - ENUM_BF(consensus_flavor_t) flavor : 8; /**< If a consensus, what kind? */ + networkstatus_type_t type; /**< Vote, consensus, or opinion? */ + consensus_flavor_t flavor; /**< If a consensus, what kind? */ unsigned int has_measured_bws : 1;/**< True iff this networkstatus contains * measured= bandwidth values. */ @@ -2425,6 +2423,9 @@ typedef struct networkstatus_t { /** Vote only: what methods is this voter willing to use? */ smartlist_t *supported_methods; + /** List of 'package' lines describing hashes of downloadable packages */ + smartlist_t *package_lines; + /** How long does this vote/consensus claim that authorities take to * distribute their votes to one another? */ int vote_seconds; @@ -2492,10 +2493,6 @@ typedef struct desc_store_t { * filename for a temporary file when rebuilding the store, and .new to this * filename for the journal. */ const char *fname_base; - /** Alternative (obsolete) value for fname_base: if the file named by - * fname_base isn't present, we read from here instead, but we never write - * here. */ - const char *fname_alt_base; /** Human-readable description of what this store contains. */ const char *description; @@ -2550,9 +2547,7 @@ typedef struct extend_info_t { uint16_t port; /**< OR port. */ tor_addr_t addr; /**< IP address. */ crypto_pk_t *onion_key; /**< Current onionskin key. */ -#ifdef CURVE25519_ENABLED curve25519_public_key_t curve25519_onion_key; -#endif } extend_info_t; /** Certificate for v3 directory protocol: binds long-term authority identity @@ -2572,9 +2567,6 @@ typedef struct authority_cert_t { uint32_t addr; /** This authority's directory port. */ uint16_t dir_port; - /** True iff this certificate was cross-certified by signing the identity - * key with the signing key. */ - uint8_t is_cross_certified; } authority_cert_t; /** Bitfield enum type listing types of information that directory authorities @@ -2588,15 +2580,8 @@ typedef struct authority_cert_t { */ typedef enum { NO_DIRINFO = 0, - /** Serves/signs v1 directory information: Big lists of routers, and short - * routerstatus documents. */ - V1_DIRINFO = 1 << 0, - /** Serves/signs v2 directory information: i.e. v2 networkstatus documents */ - V2_DIRINFO = 1 << 1, /** Serves/signs v3 directory information: votes, consensuses, certs */ V3_DIRINFO = 1 << 2, - /** Serves hidden service descriptors. */ - HIDSERV_DIRINFO = 1 << 3, /** Serves bridge descriptors. */ BRIDGE_DIRINFO = 1 << 4, /** Serves extrainfo documents. */ @@ -2719,11 +2704,30 @@ typedef struct { time_t expiry_time; } cpath_build_state_t; +/** "magic" value for an origin_circuit_t */ #define ORIGIN_CIRCUIT_MAGIC 0x35315243u +/** "magic" value for an or_circuit_t */ #define OR_CIRCUIT_MAGIC 0x98ABC04Fu +/** "magic" value for a circuit that would have been freed by circuit_free, + * but which we're keeping around until a cpuworker reply arrives. See + * circuit_free() for more documentation. */ +#define DEAD_CIRCUIT_MAGIC 0xdeadc14c struct create_cell_t; +/** Entry in the cell stats list of a circuit; used only if CELL_STATS + * events are enabled. */ +typedef struct testing_cell_stats_entry_t { + uint8_t command; /**< cell command number. */ + /** Waiting time in centiseconds if this event is for a removed cell, + * or 0 if this event is for adding a cell to the queue. 22 bits can + * store more than 11 hours, enough to assume that a circuit with this + * delay would long have been closed. */ + unsigned int waiting_time:22; + unsigned int removed:1; /**< 0 for added to, 1 for removed from queue. */ + unsigned int exitward:1; /**< 0 for app-ward, 1 for exit-ward. */ +} testing_cell_stats_entry_t; + /** * A circuit is a path over the onion routing * network. Applications can connect to one end of the circuit, and can @@ -2785,6 +2789,13 @@ typedef struct circuit_t { * allowing n_streams to add any more cells. (OR circuit only.) */ unsigned int streams_blocked_on_p_chan : 1; + /** True iff we have queued a delete backwards on this circuit, but not put + * it on the output buffer. */ + unsigned int p_delete_pending : 1; + /** True iff we have queued a delete forwards on this circuit, but not put + * it on the output buffer. */ + unsigned int n_delete_pending : 1; + /** True iff this circuit has received a DESTROY cell in either direction */ unsigned int received_destroy : 1; @@ -2801,6 +2812,9 @@ typedef struct circuit_t { * more. */ int deliver_window; + /** Temporary field used during circuits_handle_oom. */ + uint32_t age_tmp; + /** For storage while n_chan is pending (state CIRCUIT_STATE_CHAN_WAIT). */ struct create_cell_t *n_chan_create_cell; @@ -2842,7 +2856,8 @@ typedef struct circuit_t { /** Unique ID for measuring tunneled network status requests. */ uint64_t dirreq_id; - struct circuit_t *next; /**< Next circuit in linked list of all circuits. */ + /** Index in smartlist of all circuits (global_circuitlist). */ + int global_circuitlist_idx; /** Next circuit in the doubly-linked ring of circuits waiting to add * cells to n_conn. NULL if we have no cells pending, or if we're not @@ -2852,6 +2867,11 @@ typedef struct circuit_t { * cells to n_conn. NULL if we have no cells pending, or if we're not * linked to an OR connection. */ struct circuit_t *prev_active_on_n_chan; + + /** Various statistics about cells being added to or removed from this + * circuit's queues; used only if CELL_STATS events are enabled and + * cleared after being sent to control port. */ + smartlist_t *testing_cell_stats; } circuit_t; /** Largest number of relay_early cells that we can send on a given @@ -2913,6 +2933,7 @@ typedef enum { */ PATH_STATE_ALREADY_COUNTED = 6, } path_state_t; +#define path_state_bitfield_t ENUM_BF(path_state_t) /** An origin_circuit_t holds data necessary to build and use a circuit. */ @@ -2922,6 +2943,17 @@ typedef struct origin_circuit_t { /** Linked list of AP streams (or EXIT streams if hidden service) * associated with this circuit. */ edge_connection_t *p_streams; + + /** Bytes read from any attached stream since last call to + * control_event_circ_bandwidth_used(). Only used if we're configured + * to emit CIRC_BW events. */ + uint32_t n_read_circ_bw; + + /** Bytes written to any attached stream since last call to + * control_event_circ_bandwidth_used(). Only used if we're configured + * to emit CIRC_BW events. */ + uint32_t n_written_circ_bw; + /** Build state for this circuit. It includes the intended path * length, the chosen exit router, rendezvous information, etc. */ @@ -2952,7 +2984,7 @@ typedef struct origin_circuit_t { * circuit building and usage accounting. See path_state_t * for more details. */ - ENUM_BF(path_state_t) path_state : 3; + path_state_bitfield_t path_state : 3; /* If this flag is set, we should not consider attaching any more * connections to this circuit. */ @@ -3099,6 +3131,10 @@ typedef struct or_circuit_t { /** Pointer to an entry on the onion queue, if this circuit is waiting for a * chance to give an onionskin to a cpuworker. Used only in onion.c */ struct onion_queue_t *onionqueue_entry; + /** Pointer to a workqueue entry, if this circuit has given an onionskin to + * a cpuworker and is waiting for a response. Used to decide whether it is + * safe to free a circuit or if it is still in use by a cpuworker. */ + struct workqueue_entry_s *workqueue_entry; /** The circuit_id used in the previous (backward) hop of this circuit. */ circid_t p_circ_id; @@ -3136,20 +3172,8 @@ typedef struct or_circuit_t { * is not marked for close. */ struct or_circuit_t *rend_splice; -#if REND_COOKIE_LEN >= DIGEST_LEN -#define REND_TOKEN_LEN REND_COOKIE_LEN -#else -#define REND_TOKEN_LEN DIGEST_LEN -#endif - - /** A hash of location-hidden service's PK if purpose is INTRO_POINT, or a - * rendezvous cookie if purpose is REND_POINT_WAITING. Filled with zeroes - * otherwise. - * ???? move to a subtype or adjunct structure? Wastes 20 bytes. -NM - */ - char rend_token[REND_TOKEN_LEN]; + struct or_circuit_rendinfo_s *rendinfo; - /* ???? move to a subtype or adjunct structure? Wastes 20 bytes -NM */ /** Stores KH for the handshake. */ char rend_circ_nonce[DIGEST_LEN];/* KH in tor-spec.txt */ @@ -3163,6 +3187,10 @@ typedef struct or_circuit_t { /** True iff this circuit was made with a CREATE_FAST cell. */ unsigned int is_first_hop : 1; + /** If set, this circuit carries HS traffic. Consider it in any HS + * statistics. */ + unsigned int circuit_carries_hs_traffic_stats : 1; + /** Number of cells that were removed from circuit queue; reset every * time when writing buffer stats to disk. */ uint32_t processed_cells; @@ -3171,28 +3199,74 @@ typedef struct or_circuit_t { * exit-ward queues of this circuit; reset every time when writing * buffer stats to disk. */ uint64_t total_cell_waiting_time; + + /** Maximum cell queue size for a middle relay; this is stored per circuit + * so append_cell_to_circuit_queue() can adjust it if it changes. If set + * to zero, it is initialized to the default value. + */ + uint32_t max_middle_cells; } or_circuit_t; +typedef struct or_circuit_rendinfo_s { + +#if REND_COOKIE_LEN != DIGEST_LEN +#error "The REND_TOKEN_LEN macro assumes REND_COOKIE_LEN == DIGEST_LEN" +#endif +#define REND_TOKEN_LEN DIGEST_LEN + + /** A hash of location-hidden service's PK if purpose is INTRO_POINT, or a + * rendezvous cookie if purpose is REND_POINT_WAITING. Filled with zeroes + * otherwise. + */ + char rend_token[REND_TOKEN_LEN]; + + /** True if this is a rendezvous point circuit; false if this is an + * introduction point. */ + unsigned is_rend_circ; + +} or_circuit_rendinfo_t; + /** Convert a circuit subtype to a circuit_t. */ #define TO_CIRCUIT(x) (&((x)->base_)) /** Convert a circuit_t* to a pointer to the enclosing or_circuit_t. Assert * if the cast is impossible. */ static or_circuit_t *TO_OR_CIRCUIT(circuit_t *); +static const or_circuit_t *CONST_TO_OR_CIRCUIT(const circuit_t *); /** Convert a circuit_t* to a pointer to the enclosing origin_circuit_t. * Assert if the cast is impossible. */ static origin_circuit_t *TO_ORIGIN_CIRCUIT(circuit_t *); +static const origin_circuit_t *CONST_TO_ORIGIN_CIRCUIT(const circuit_t *); + +/** Return 1 iff <b>node</b> has Exit flag and no BadExit flag. + * Otherwise, return 0. + */ +static INLINE int node_is_good_exit(const node_t *node) +{ + return node->is_exit && ! node->is_bad_exit; +} static INLINE or_circuit_t *TO_OR_CIRCUIT(circuit_t *x) { tor_assert(x->magic == OR_CIRCUIT_MAGIC); return DOWNCAST(or_circuit_t, x); } +static INLINE const or_circuit_t *CONST_TO_OR_CIRCUIT(const circuit_t *x) +{ + tor_assert(x->magic == OR_CIRCUIT_MAGIC); + return DOWNCAST(or_circuit_t, x); +} static INLINE origin_circuit_t *TO_ORIGIN_CIRCUIT(circuit_t *x) { tor_assert(x->magic == ORIGIN_CIRCUIT_MAGIC); return DOWNCAST(origin_circuit_t, x); } +static INLINE const origin_circuit_t *CONST_TO_ORIGIN_CIRCUIT( + const circuit_t *x) +{ + tor_assert(x->magic == ORIGIN_CIRCUIT_MAGIC); + return DOWNCAST(origin_circuit_t, x); +} /** Bitfield type: things that we're willing to use invalid routers for. */ typedef enum invalid_router_usage_t { @@ -3251,44 +3325,9 @@ typedef struct port_cfg_t { uint8_t type; /**< One of CONN_TYPE_*_LISTENER */ unsigned is_unix_addr : 1; /**< True iff this is an AF_UNIX address. */ - /* Client port types (socks, dns, trans, natd) only: */ - uint8_t isolation_flags; /**< Zero or more isolation flags */ - int session_group; /**< A session group, or -1 if this port is not in a - * session group. */ - /* Socks only: */ - /** When both no-auth and user/pass are advertised by a SOCKS client, select - * no-auth. */ - unsigned int socks_prefer_no_auth : 1; - - /* Server port types (or, dir) only: */ - unsigned int no_advertise : 1; - unsigned int no_listen : 1; - unsigned int all_addrs : 1; - unsigned int bind_ipv4_only : 1; - unsigned int bind_ipv6_only : 1; - - /* Client port types only: */ - unsigned int ipv4_traffic : 1; - unsigned int ipv6_traffic : 1; - unsigned int prefer_ipv6 : 1; + entry_port_cfg_t entry_cfg; - /** For a socks listener: should we cache IPv4/IPv6 DNS information that - * exit nodes tell us? - * - * @{ */ - unsigned int cache_ipv4_answers : 1; - unsigned int cache_ipv6_answers : 1; - /** @} */ - /** For a socks listeners: if we find an answer in our client-side DNS cache, - * should we use it? - * - * @{ */ - unsigned int use_cached_ipv4_answers : 1; - unsigned int use_cached_ipv6_answers : 1; - /** @} */ - /** For socks listeners: When we can automap an address to IPv4 or IPv6, - * do we prefer IPv6? */ - unsigned int prefer_ipv6_virtaddr : 1; + server_port_cfg_t server_cfg; /* Unix sockets only: */ /** Path for an AF_UNIX address */ @@ -3329,9 +3368,9 @@ typedef struct { /** What should the tor process actually do? */ enum { CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD, - CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS + CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG } command; - const char *command_arg; /**< Argument for command-line option. */ + char *command_arg; /**< Argument for command-line option. */ config_line_t *Logs; /**< New-style list of configuration lines * for logs */ @@ -3339,6 +3378,8 @@ typedef struct { int LogMessageDomains; /**< Boolean: Should we log the domain(s) in which * each log message occurs? */ + int TruncateLogFile; /**< Boolean: Should we truncate the log file + before we start writing? */ char *DebugLogFile; /**< Where to send verbose log messages. */ char *DataDirectory; /**< OR only: where to store long-term data. */ @@ -3405,6 +3446,7 @@ typedef struct { config_line_t *RecommendedVersions; config_line_t *RecommendedClientVersions; config_line_t *RecommendedServerVersions; + config_line_t *RecommendedPackages; /** Whether dirservers allow router descriptors with private IPs. */ int DirAllowPrivateAddresses; /** Whether routers accept EXTEND cells to routers with private IPs. */ @@ -3412,10 +3454,21 @@ typedef struct { char *User; /**< Name of user to run Tor as. */ char *Group; /**< Name of group to run Tor as. */ config_line_t *ORPort_lines; /**< Ports to listen on for OR connections. */ + /** Ports to listen on for extended OR connections. */ + config_line_t *ExtORPort_lines; /** Ports to listen on for SOCKS connections. */ config_line_t *SocksPort_lines; /** Ports to listen on for transparent pf/netfilter connections. */ config_line_t *TransPort_lines; + const char *TransProxyType; /**< What kind of transparent proxy + * implementation are we using? */ + /** Parsed value of TransProxyType. */ + enum { + TPT_DEFAULT, + TPT_PF_DIVERT, + TPT_IPFW, + TPT_TPROXY, + } TransProxyType_parsed; config_line_t *NATDPort_lines; /**< Ports to listen on for transparent natd * connections. */ config_line_t *ControlPort_lines; /**< Ports to listen on for control @@ -3424,13 +3477,18 @@ typedef struct { * for control connections. */ int ControlSocketsGroupWritable; /**< Boolean: Are control sockets g+rw? */ + int SocksSocketsGroupWritable; /**< Boolean: Are SOCKS sockets g+rw? */ /** Ports to listen on for directory connections. */ config_line_t *DirPort_lines; config_line_t *DNSPort_lines; /**< Ports to listen on for DNS requests. */ - uint64_t MaxMemInCellQueues; /**< If we have more memory than this allocated - * for circuit cell queues, run the OOM handler - */ + /* MaxMemInQueues value as input by the user. We clean this up to be + * MaxMemInQueues. */ + uint64_t MaxMemInQueues_raw; + uint64_t MaxMemInQueues;/**< If we have more memory than this allocated + * for queues and buffers, run the OOM handler */ + /** Above this value, consider ourselves low on RAM. */ + uint64_t MaxMemInQueues_low_threshold; /** @name port booleans * @@ -3447,20 +3505,13 @@ typedef struct { unsigned int ControlPort_set : 1; unsigned int DirPort_set : 1; unsigned int DNSPort_set : 1; + unsigned int ExtORPort_set : 1; /**@}*/ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ int AuthoritativeDir; /**< Boolean: is this an authoritative directory? */ - int V1AuthoritativeDir; /**< Boolean: is this an authoritative directory - * for version 1 directories? */ - int V2AuthoritativeDir; /**< Boolean: is this an authoritative directory - * for version 2 directories? */ int V3AuthoritativeDir; /**< Boolean: is this an authoritative directory * for version 3 directories? */ - int HSAuthoritativeDir; /**< Boolean: does this an authoritative directory - * handle hidden service requests? */ - int NamingAuthoritativeDir; /**< Boolean: is this an authoritative directory - * that's willing to bind names? */ int VersioningAuthoritativeDir; /**< Boolean: is this an authoritative * directory that's willing to recommend * versions? */ @@ -3486,6 +3537,9 @@ typedef struct { /** List of TCP/IP addresses that transports should listen at. */ config_line_t *ServerTransportListenAddr; + /** List of options that must be passed to pluggable transports. */ + config_line_t *ServerTransportOptions; + int BridgeRelay; /**< Boolean: are we acting as a bridge relay? We make * this explicit so we can change how we behave in the * future. */ @@ -3506,8 +3560,6 @@ typedef struct { int PublishHidServDescriptors; int FetchServerDescriptors; /**< Do we fetch server descriptors as normal? */ int FetchHidServDescriptors; /**< and hidden service descriptors? */ - int FetchV2Networkstatus; /**< Do we fetch v2 networkstatus documents when - * we don't need to? */ int HidServDirectoryV2; /**< Do we participate in the HS DHT? */ int VoteOnHidServDirectoriesV2; /**< As a directory authority, vote on @@ -3524,6 +3576,9 @@ typedef struct { * circuits.) */ int Tor2webMode; + /** A routerset that should be used when picking RPs for HS circuits. */ + routerset_t *Tor2webRendezvousPoints; + /** Close hidden service client circuits immediately when they reach * the normal circuit-build timeout, even if they have already sent * an INTRODUCE1 cell on its way to the service. */ @@ -3573,8 +3628,9 @@ typedef struct { * hostname ending with one of the suffixes in * <b>AutomapHostsSuffixes</b>, map it to a * virtual address. */ - smartlist_t *AutomapHostsSuffixes; /**< List of suffixes for - * <b>AutomapHostsOnResolve</b>. */ + /** List of suffixes for <b>AutomapHostsOnResolve</b>. The special value + * "." means "match everything." */ + smartlist_t *AutomapHostsSuffixes; int RendPostPeriod; /**< How often do we post each rendezvous service * descriptor? Remember to publish them independently. */ int KeepalivePeriod; /**< How often do we send padding cells to keep @@ -3598,6 +3654,10 @@ typedef struct { * a new one? */ int MaxCircuitDirtiness; /**< Never use circs that were first used more than this interval ago. */ + int PredictedPortsRelevanceTime; /** How long after we've requested a + * connection for a given port, do we want + * to continue to pick exits that support + * that port? */ uint64_t BandwidthRate; /**< How much bandwidth, on average, are we willing * to use in a second? */ uint64_t BandwidthBurst; /**< How much bandwidth, at maximum, are we willing @@ -3661,15 +3721,10 @@ typedef struct { /** If set, use these bridge authorities and not the default one. */ config_line_t *AlternateBridgeAuthority; - /** If set, use these HS authorities and not the default ones. */ - config_line_t *AlternateHSAuthority; - char *MyFamily; /**< Declared family for this OR. */ config_line_t *NodeFamilies; /**< List of config lines for * 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 * mark as bad exits. */ config_line_t *AuthDirReject; /**< Address policy for descriptors to @@ -3678,23 +3733,18 @@ typedef struct { * never mark as valid. */ /** @name AuthDir...CC * - * Lists of country codes to mark as BadDir, BadExit, or Invalid, or to + * Lists of country codes to mark as BadExit, or Invalid, or to * reject entirely. * * @{ */ - smartlist_t *AuthDirBadDirCCs; smartlist_t *AuthDirBadExitCCs; smartlist_t *AuthDirInvalidCCs; smartlist_t *AuthDirRejectCCs; /**@}*/ - int AuthDirListBadDirs; /**< True iff we should list bad dirs, - * and vote for all other dir mirrors as good. */ int AuthDirListBadExits; /**< True iff we should list bad exits, * and vote for all other exits as good. */ - int AuthDirRejectUnlisted; /**< Boolean: do we reject all routers that - * aren't named in our fingerprint file? */ int AuthDirMaxServersPerAddr; /**< Do not permit more than this * number of servers per IP address. */ int AuthDirMaxServersPerAuthAddr; /**< Do not permit more than this @@ -3715,6 +3765,11 @@ typedef struct { uint64_t AccountingMax; /**< How many bytes do we allow per accounting * interval before hibernation? 0 for "never * hibernate." */ + /** How do we determine when our AccountingMax has been reached? + * "max" for when in or out reaches AccountingMax + * "sum for when in plus out reaches AccountingMax */ + char *AccountingRule_option; + enum { ACCT_MAX, ACCT_SUM } AccountingRule; /** Base64-encoded hash of accepted passwords for the control system. */ config_line_t *HashedControlPassword; @@ -3723,8 +3778,13 @@ typedef struct { int CookieAuthentication; /**< Boolean: do we enable cookie-based auth for * the control system? */ - char *CookieAuthFile; /**< Location of a cookie authentication file. */ + char *CookieAuthFile; /**< Filesystem location of a ControlPort + * authentication cookie. */ + char *ExtORPortCookieAuthFile; /**< Filesystem location of Extended + * ORPort authentication cookie. */ int CookieAuthFileGroupReadable; /**< Boolean: Is the CookieAuthFile g+r? */ + int ExtORPortCookieAuthFileGroupReadable; /**< Boolean: Is the + * ExtORPortCookieAuthFile g+r? */ int LeaveStreamsUnattached; /**< Boolean: Does Tor attach new streams to * circuits itself (0), or does it expect a controller * to cope? (1) */ @@ -3745,6 +3805,7 @@ typedef struct { SAFELOG_SCRUB_ALL, SAFELOG_SCRUB_RELAY, SAFELOG_SCRUB_NONE } SafeLogging_; + int Sandbox; /**< Boolean: should sandboxing be enabled? */ int SafeSocks; /**< Boolean: should we outright refuse application * connections that use socks4 or socks5-with-local-dns? */ #define LOG_PROTOCOL_WARN (get_options()->ProtocolWarnings ? \ @@ -3764,6 +3825,12 @@ typedef struct { int NumEntryGuards; /**< How many entry guards do we try to establish? */ int UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info * from a smallish number of fixed nodes? */ + + /** If 1, we use any guardfraction information we see in the + * consensus. If 0, we don't. If -1, let the consensus parameter + * decide. */ + int UseGuardFraction; + int NumDirectoryGuards; /**< How many dir guards do we try to establish? * If 0, use value from NumEntryGuards. */ int RephistTrackTime; /**< How many seconds do we keep rephist info? */ @@ -3807,10 +3874,6 @@ typedef struct { * testing our DNS server. */ int EnforceDistinctSubnets; /**< If true, don't allow multiple routers in the * same network zone in the same circuit. */ - int TunnelDirConns; /**< If true, use BEGIN_DIR rather than BEGIN when - * 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 @@ -3845,8 +3908,11 @@ typedef struct { * instead of a hostname. */ int WarnUnsafeSocks; - /** If true, the user wants us to collect statistics on clients + /** If true, we're configured to collect statistics on clients * requesting network statuses from us as directory. */ + int DirReqStatistics_option; + /** Internal variable to remember whether we're actually acting on + * DirReqStatistics_option -- yes if it's set and we're a server, else no. */ int DirReqStatistics; /** If true, the user wants us to collect statistics on port usage. */ @@ -3861,6 +3927,10 @@ typedef struct { /** If true, the user wants us to collect statistics as entry node. */ int EntryStatistics; + /** If true, the user wants us to collect statistics as hidden service + * directory, introduction point, or rendezvous point. */ + int HiddenServiceStatistics; + /** If true, include statistics file contents in extra-info documents. */ int ExtraInfoStatistics; @@ -3896,6 +3966,9 @@ typedef struct { /** Location of bandwidth measurement file */ char *V3BandwidthsFile; + /** Location of guardfraction file */ + char *GuardfractionFile; + /** Authority only: key=value pairs that we add to our networkstatus * consensus vote on the 'params' line. */ char *ConsensusParams; @@ -3916,6 +3989,10 @@ typedef struct { * signatures. Only altered on testing networks.*/ int TestingV3AuthInitialDistDelay; + /** Offset in seconds added to the starting time for consensus + voting. Only altered on testing networks. */ + int TestingV3AuthVotingStartOffset; + /** If an authority has been around for less than this amount of time, it * does not believe its reachability information is accurate. Only * altered on testing networks. */ @@ -3926,6 +4003,51 @@ typedef struct { * networks. */ int TestingEstimatedDescriptorPropagationTime; + /** Schedule for when servers should download things in general. Only + * altered on testing networks. */ + smartlist_t *TestingServerDownloadSchedule; + + /** Schedule for when clients should download things in general. Only + * altered on testing networks. */ + smartlist_t *TestingClientDownloadSchedule; + + /** Schedule for when servers should download consensuses. Only altered + * on testing networks. */ + smartlist_t *TestingServerConsensusDownloadSchedule; + + /** Schedule for when clients should download consensuses. Only altered + * on testing networks. */ + smartlist_t *TestingClientConsensusDownloadSchedule; + + /** Schedule for when clients should download bridge descriptors. Only + * altered on testing networks. */ + smartlist_t *TestingBridgeDownloadSchedule; + + /** When directory clients have only a few descriptors to request, they + * batch them until they have more, or until this amount of time has + * passed. Only altered on testing networks. */ + int TestingClientMaxIntervalWithoutRequest; + + /** How long do we let a directory connection stall before expiring + * it? Only altered on testing networks. */ + int TestingDirConnectionMaxStall; + + /** How many times will we try to fetch a consensus before we give + * up? Only altered on testing networks. */ + int TestingConsensusMaxDownloadTries; + + /** How many times will we try to download a router's descriptor before + * giving up? Only altered on testing networks. */ + int TestingDescriptorMaxDownloadTries; + + /** How many times will we try to download a microdescriptor before + * giving up? Only altered on testing networks. */ + int TestingMicrodescMaxDownloadTries; + + /** How many times will we try to fetch a certificate before giving + * up? Only altered on testing networks. */ + int TestingCertMaxDownloadTries; + /** If true, we take part in a testing network. Change the defaults of a * couple of other configuration options and allow to change the values * of certain configuration options. */ @@ -3937,6 +4059,28 @@ typedef struct { /** Minimum value for the Fast flag threshold on testing networks. */ uint64_t TestingMinFastFlagThreshold; + /** Relays in a testing network which should be voted Exit + * regardless of exit policy. */ + routerset_t *TestingDirAuthVoteExit; + + /** Relays in a testing network which should be voted Guard + * regardless of uptime and bandwidth. */ + routerset_t *TestingDirAuthVoteGuard; + + /** Relays in a testing network which should be voted HSDir + * regardless of uptime and ORPort connectivity. + * Respects VoteOnHidServDirectoriesV2. */ + routerset_t *TestingDirAuthVoteHSDir; + + /** Enable CONN_BW events. Only altered on testing networks. */ + int TestingEnableConnBwEvent; + + /** Enable CELL_STATS events. Only altered on testing networks. */ + int TestingEnableCellStatsEvent; + + /** Enable TB_EMPTY events. Only altered on testing networks. */ + int TestingEnableTbEmptyEvent; + /** If true, and we have GeoIP data, and we're a bridge, keep a per-country * count of how many client addresses have contacted us so that we can help * the bridge authority guess which countries have blocked access to us. */ @@ -4075,16 +4219,6 @@ typedef struct { /** Fraction: */ double PathsNeededToBuildCircuits; - /** Do we serve v2 directory info at all? This is a temporary option, since - * we'd like to disable v2 directory serving entirely, but we need a way to - * make it temporarily disableable, in order to do fast testing and be - * able to turn it back on if it turns out to be non-workable. - * - * XXXX025 Make this always-on, or always-off. Right now, it's only - * enableable for authorities. - */ - int DisableV2DirectoryInfo_; - /** What expiry time shall we place on our SSL certs? "0" means we * should guess a suitable value. */ int SSLKeyLifetime; @@ -4092,8 +4226,26 @@ typedef struct { /** How long (seconds) do we keep a guard before picking a new one? */ int GuardLifetime; - /** Should we send the timestamps that pre-023 hidden services want? */ - int Support022HiddenServices; + /** Low-water mark for global scheduler - start sending when estimated + * queued size falls below this threshold. + */ + uint64_t SchedulerLowWaterMark__; + /** High-water mark for global scheduler - stop sending when estimated + * queued size exceeds this threshold. + */ + uint64_t SchedulerHighWaterMark__; + /** Flush size for global scheduler - flush this many cells at a time + * when sending. + */ + int SchedulerMaxFlushCells__; + + /** Is this an exit node? This is a tristate, where "1" means "yes, and use + * the default exit policy if none is given" and "0" means "no; exit policy + * is 'reject *'" and "auto" (-1) means "same as 1, but warn the user." + * + * XXXX Eventually, the default will be 0. */ + int ExitRelay; + } or_options_t; /** Persistent state for an onion router, as saved to disk. */ @@ -4184,7 +4336,8 @@ static INLINE void or_state_mark_dirty(or_state_t *state, time_t when) /** Please turn this IP address into an FQDN, privately. */ #define SOCKS_COMMAND_RESOLVE_PTR 0xF1 -#define SOCKS_COMMAND_IS_CONNECT(c) ((c)==SOCKS_COMMAND_CONNECT) +/* || 0 is for -Wparentheses-equality (-Wall?) appeasement under clang */ +#define SOCKS_COMMAND_IS_CONNECT(c) (((c)==SOCKS_COMMAND_CONNECT) || 0) #define SOCKS_COMMAND_IS_RESOLVE(c) ((c)==SOCKS_COMMAND_RESOLVE || \ (c)==SOCKS_COMMAND_RESOLVE_PTR) @@ -4349,30 +4502,7 @@ typedef struct { int after_firsthop_idx; } network_liveness_t; -/** Structure for circuit build times history */ -typedef struct { - /** The circular array of recorded build times in milliseconds */ - build_time_t circuit_build_times[CBT_NCIRCUITS_TO_OBSERVE]; - /** Current index in the circuit_build_times circular array */ - int build_times_idx; - /** Total number of build times accumulated. Max CBT_NCIRCUITS_TO_OBSERVE */ - int total_build_times; - /** Information about the state of our local network connection */ - network_liveness_t liveness; - /** Last time we built a circuit. Used to decide to build new test circs */ - time_t last_circ_at; - /** "Minimum" value of our pareto distribution (actually mode) */ - build_time_t Xm; - /** alpha exponent for pareto dist. */ - double alpha; - /** Have we computed a timeout? */ - int have_computed_timeout; - /** The exact value for that timeout in milliseconds. Stored as a double - * to maintain precision from calculations to and from quantile value. */ - double timeout_ms; - /** How long we wait before actually closing the circuit. */ - double close_ms; -} circuit_build_times_t; +typedef struct circuit_build_times_s circuit_build_times_t; /********************************* config.c ***************************/ @@ -4409,6 +4539,7 @@ typedef enum { * did this remapping happen." */ ADDRMAPSRC_NONE } addressmap_entry_source_t; +#define addressmap_entry_source_bitfield_t ENUM_BF(addressmap_entry_source_t) /********************************* control.c ***************************/ @@ -4560,8 +4691,6 @@ typedef enum { GEOIP_CLIENT_CONNECT = 0, /** We've served a networkstatus consensus as a directory server. */ GEOIP_CLIENT_NETWORKSTATUS = 1, - /** We've served a v2 networkstatus consensus as a directory server. */ - GEOIP_CLIENT_NETWORKSTATUS_V2 = 2, } geoip_client_action_t; /** Indicates either a positive reply or a reason for rejectng a network * status request that will be included in geoip statistics. */ @@ -4619,11 +4748,6 @@ typedef struct microdesc_cache_t microdesc_cache_t; /********************************* networkstatus.c *********************/ -/** Location where we found a v2 networkstatus. */ -typedef enum { - NS_FROM_CACHE, NS_FROM_DIR_BY_FP, NS_FROM_DIR_ALL, NS_GENERATED -} v2_networkstatus_source_t; - /** Possible statuses of a version of Tor, given opinions from the directory * servers. */ typedef enum version_status_t { @@ -4774,9 +4898,9 @@ typedef struct rend_service_descriptor_t { crypto_pk_t *pk; /**< This service's public key. */ int version; /**< Version of the descriptor format: 0 or 2. */ time_t timestamp; /**< Time when the descriptor was generated. */ - /** Bitmask: which rendezvous protocols are supported? - * (We allow bits '0', '1', and '2' to be set.) */ - int protocols : REND_PROTOCOL_VERSION_BITMASK_WIDTH; + /** Bitmask: which introduce/rendezvous protocols are supported? + * (We allow bits '0', '1', '2' and '3' to be set.) */ + unsigned protocols : REND_PROTOCOL_VERSION_BITMASK_WIDTH; /** List of the service's introduction points. Elements are removed if * introduction attempts fail. */ smartlist_t *intro_nodes; @@ -4792,7 +4916,8 @@ typedef struct rend_service_descriptor_t { /** A cached rendezvous descriptor. */ typedef struct rend_cache_entry_t { size_t len; /**< Length of <b>desc</b> */ - time_t received; /**< When was the descriptor received? */ + time_t last_served; /**< When did we last write this one to somebody? + * (HSDir only) */ char *desc; /**< Service descriptor */ rend_service_descriptor_t *parsed; /**< Parsed value of 'desc' */ } rend_cache_entry_t; @@ -4824,8 +4949,6 @@ typedef struct dir_server_t { /** What kind of authority is this? (Bitfield.) */ dirinfo_type_t type; - download_status_t v2_ns_dl_status; /**< Status of downloading this server's - * v2 network status. */ time_t addr_current_at; /**< When was the document that we derived the * address information from published? */ @@ -4836,7 +4959,8 @@ typedef struct dir_server_t { **/ } dir_server_t; -#define ROUTER_REQUIRED_MIN_BANDWIDTH (20*1024) +#define RELAY_REQUIRED_MIN_BANDWIDTH (75*1024) +#define BRIDGE_REQUIRED_MIN_BANDWIDTH (50*1024) #define ROUTER_MAX_DECLARED_BANDWIDTH INT32_MAX @@ -4860,22 +4984,19 @@ typedef struct dir_server_t { * or extrainfo documents. * * Passed to router_pick_directory_server (et al) - * - * [XXXX NOTE: This option is only implemented for pick_trusteddirserver, - * not pick_directory_server. If we make it work on pick_directory_server - * too, we could conservatively make it only prevent multiple fetches to - * the same authority, or we could aggressively make it prevent multiple - * fetches to _any_ single directory server.] */ #define PDS_NO_EXISTING_SERVERDESC_FETCH (1<<3) +/** Flag to indicate that we should not use any directory authority to which + * we have an existing directory connection for downloading microdescs. + * + * Passed to router_pick_directory_server (et al) + */ #define PDS_NO_EXISTING_MICRODESC_FETCH (1<<4) /** This node is to be chosen as a directory guard, so don't choose any * node that's currently a guard. */ #define PDS_FOR_GUARD (1<<5) -#define PDS_PREFER_TUNNELED_DIR_CONNS_ (1<<16) - /** Possible ways to weight routers when choosing one randomly. See * routerlist_sl_choose_by_bandwidth() for more information.*/ typedef enum bandwidth_weight_rule_t { @@ -4897,14 +5018,31 @@ typedef enum { /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ typedef enum was_router_added_t { + /* Router was added successfully. */ ROUTER_ADDED_SUCCESSFULLY = 1, + /* Router descriptor was added with warnings to submitter. */ ROUTER_ADDED_NOTIFY_GENERATOR = 0, + /* Extrainfo document was rejected because no corresponding router + * descriptor was found OR router descriptor was rejected because + * it was incompatible with its extrainfo document. */ ROUTER_BAD_EI = -1, - ROUTER_WAS_NOT_NEW = -2, + /* Router descriptor was rejected because it is already known. */ + ROUTER_IS_ALREADY_KNOWN = -2, + /* General purpose router was rejected, because it was not listed + * in consensus. */ ROUTER_NOT_IN_CONSENSUS = -3, + /* Router was neither in directory consensus nor in any of + * networkstatus documents. Caching it to access later. + * (Applies to fetched descriptors only.) */ ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS = -4, + /* Router was rejected by directory authority. */ ROUTER_AUTHDIR_REJECTS = -5, - ROUTER_WAS_NOT_WANTED = -6 + /* Bridge descriptor was rejected because such bridge was not one + * of the bridges we have listed in our configuration. */ + ROUTER_WAS_NOT_WANTED = -6, + /* Router descriptor was rejected because it was older than + * OLD_ROUTER_DESC_MAX_AGE. */ + ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'NOT_NEW' */ } was_router_added_t; /********************************* routerparse.c ************************/ diff --git a/src/or/policies.c b/src/or/policies.c index be4da55061..560b8cb4c3 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,6 +13,7 @@ #include "dirserv.h" #include "nodelist.h" #include "policies.h" +#include "router.h" #include "routerparse.h" #include "geoip.h" #include "ht.h" @@ -28,9 +29,6 @@ static smartlist_t *authdir_reject_policy = NULL; * to be marked as valid in our networkstatus. */ static smartlist_t *authdir_invalid_policy = NULL; /** Policy that addresses for incoming router descriptors must <b>not</b> - * match in order to not be marked as BadDirectory. */ -static smartlist_t *authdir_baddir_policy = NULL; -/** Policy that addresses for incoming router descriptors must <b>not</b> * match in order to not be marked as BadExit. */ static smartlist_t *authdir_badexit_policy = NULL; @@ -64,6 +62,13 @@ static const char *private_nets[] = { NULL }; +static int policies_parse_exit_policy_internal(config_line_t *cfg, + smartlist_t **dest, + int ipv6_exit, + int rejectprivate, + uint32_t local_address, + int add_default_policy); + /** Replace all "private" entries in *<b>policy</b> with their expanded * equivalents. */ void @@ -399,17 +404,6 @@ authdir_policy_valid_address(uint32_t addr, uint16_t port) return !addr_is_in_cc_list(addr, get_options()->AuthDirInvalidCCs); } -/** Return 1 if <b>addr</b>:<b>port</b> should be marked as a bad dir, - * based on <b>authdir_baddir_policy</b>. Else return 0. - */ -int -authdir_policy_baddir_address(uint32_t addr, uint16_t port) -{ - if (! addr_policy_permits_address(addr, port, authdir_baddir_policy)) - return 1; - return addr_is_in_cc_list(addr, get_options()->AuthDirBadDirCCs); -} - /** Return 1 if <b>addr</b>:<b>port</b> should be marked as a bad exit, * based on <b>authdir_badexit_policy</b>. Else return 0. */ @@ -436,11 +430,36 @@ validate_addr_policies(const or_options_t *options, char **msg) smartlist_t *addr_policy=NULL; *msg = NULL; - if (policies_parse_exit_policy(options->ExitPolicy, &addr_policy, - options->IPv6Exit, - options->ExitPolicyRejectPrivate, NULL, - !options->BridgeRelay)) + if (policies_parse_exit_policy_from_options(options,0,&addr_policy)) { REJECT("Error in ExitPolicy entry."); + } + + static int warned_about_exitrelay = 0; + + const int exitrelay_setting_is_auto = options->ExitRelay == -1; + const int policy_accepts_something = + ! (policy_is_reject_star(addr_policy, AF_INET) && + policy_is_reject_star(addr_policy, AF_INET6)); + + if (server_mode(options) && + ! warned_about_exitrelay && + exitrelay_setting_is_auto && + policy_accepts_something) { + /* Policy accepts something */ + warned_about_exitrelay = 1; + log_warn(LD_CONFIG, + "Tor is running as an exit relay%s. If you did not want this " + "behavior, please set the ExitRelay option to 0. If you do " + "want to run an exit Relay, please set the ExitRelay option " + "to 1 to disable this warning, and for forward compatibility.", + options->ExitPolicy == NULL ? + " with the default exit policy" : ""); + if (options->ExitPolicy == NULL) { + log_warn(LD_CONFIG, + "In a future version of Tor, ExitRelay 0 may become the " + "default when no ExitPolicy is given."); + } + } /* The rest of these calls *append* to addr_policy. So don't actually * use the results for anything other than checking if they parse! */ @@ -454,9 +473,6 @@ validate_addr_policies(const or_options_t *options, char **msg) if (parse_addr_policy(options->AuthDirInvalid, &addr_policy, ADDR_POLICY_REJECT)) REJECT("Error in AuthDirInvalid entry."); - if (parse_addr_policy(options->AuthDirBadDir, &addr_policy, - ADDR_POLICY_REJECT)) - REJECT("Error in AuthDirBadDir entry."); if (parse_addr_policy(options->AuthDirBadExit, &addr_policy, ADDR_POLICY_REJECT)) REJECT("Error in AuthDirBadExit entry."); @@ -482,10 +498,12 @@ validate_addr_policies(const or_options_t *options, char **msg) * Ignore port specifiers. */ static int -load_policy_from_option(config_line_t *config, smartlist_t **policy, +load_policy_from_option(config_line_t *config, const char *option_name, + smartlist_t **policy, int assume_action) { int r; + int killed_any_ports = 0; addr_policy_list_free(*policy); *policy = NULL; r = parse_addr_policy(config, policy, assume_action); @@ -504,9 +522,13 @@ load_policy_from_option(config_line_t *config, smartlist_t **policy, c = addr_policy_get_canonical_entry(&newp); SMARTLIST_REPLACE_CURRENT(*policy, n, c); addr_policy_free(n); + killed_any_ports = 1; } } SMARTLIST_FOREACH_END(n); } + if (killed_any_ports) { + log_warn(LD_CONFIG, "Ignoring ports in %s option.", option_name); + } return 0; } @@ -516,20 +538,19 @@ int policies_parse_from_options(const or_options_t *options) { int ret = 0; - if (load_policy_from_option(options->SocksPolicy, &socks_policy, -1) < 0) + if (load_policy_from_option(options->SocksPolicy, "SocksPolicy", + &socks_policy, -1) < 0) ret = -1; - if (load_policy_from_option(options->DirPolicy, &dir_policy, -1) < 0) + if (load_policy_from_option(options->DirPolicy, "DirPolicy", + &dir_policy, -1) < 0) ret = -1; - if (load_policy_from_option(options->AuthDirReject, + if (load_policy_from_option(options->AuthDirReject, "AuthDirReject", &authdir_reject_policy, ADDR_POLICY_REJECT) < 0) ret = -1; - if (load_policy_from_option(options->AuthDirInvalid, + if (load_policy_from_option(options->AuthDirInvalid, "AuthDirInvalid", &authdir_invalid_policy, ADDR_POLICY_REJECT) < 0) ret = -1; - if (load_policy_from_option(options->AuthDirBadDir, - &authdir_baddir_policy, ADDR_POLICY_REJECT) < 0) - ret = -1; - if (load_policy_from_option(options->AuthDirBadExit, + if (load_policy_from_option(options->AuthDirBadExit, "AuthDirBadExit", &authdir_badexit_policy, ADDR_POLICY_REJECT) < 0) ret = -1; if (parse_reachable_addresses() < 0) @@ -597,27 +618,31 @@ policy_eq(policy_map_ent_t *a, policy_map_ent_t *b) /** Return a hashcode for <b>ent</b> */ static unsigned int -policy_hash(policy_map_ent_t *ent) +policy_hash(const policy_map_ent_t *ent) { - addr_policy_t *a = ent->policy; - unsigned int r; - if (a->is_private) - r = 0x1234abcd; - else - r = tor_addr_hash(&a->addr); - r += a->prt_min << 8; - r += a->prt_max << 16; - r += a->maskbits; - if (a->policy_type == ADDR_POLICY_REJECT) - r ^= 0xffffffff; + const addr_policy_t *a = ent->policy; + addr_policy_t aa; + memset(&aa, 0, sizeof(aa)); + + aa.prt_min = a->prt_min; + aa.prt_max = a->prt_max; + aa.maskbits = a->maskbits; + aa.policy_type = a->policy_type; + aa.is_private = a->is_private; + + if (a->is_private) { + aa.is_private = 1; + } else { + tor_addr_copy_tight(&aa.addr, &a->addr); + } - return r; + return (unsigned) siphash24g(&aa, sizeof(aa)); } HT_PROTOTYPE(policy_map, policy_map_ent_t, node, policy_hash, policy_eq) -HT_GENERATE(policy_map, policy_map_ent_t, node, policy_hash, - policy_eq, 0.6, malloc, realloc, free) +HT_GENERATE2(policy_map, policy_map_ent_t, node, policy_hash, + policy_eq, 0.6, tor_reallocarray_, tor_free_) /** Given a pointer to an addr_policy_t, return a copy of the pointer to the * "canonical" copy of that addr_policy_t; the canonical copy is a single @@ -756,9 +781,9 @@ compare_unknown_tor_addr_to_addr_policy(uint16_t port, * We could do better by assuming that some ranges never match typical * addresses (127.0.0.1, and so on). But we'll try this for now. */ -addr_policy_result_t -compare_tor_addr_to_addr_policy(const tor_addr_t *addr, uint16_t port, - const smartlist_t *policy) +MOCK_IMPL(addr_policy_result_t, +compare_tor_addr_to_addr_policy,(const tor_addr_t *addr, uint16_t port, + const smartlist_t *policy)) { if (!policy) { /* no policy? accept all. */ @@ -955,11 +980,12 @@ exit_policy_remove_redundancies(smartlist_t *dest) * the functions used to parse the exit policy from a router descriptor, * see router_add_exit_policy. */ -int -policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, - int ipv6_exit, - int rejectprivate, const char *local_address, - int add_default_policy) +static int +policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest, + int ipv6_exit, + int rejectprivate, + uint32_t local_address, + int add_default_policy) { if (!ipv6_exit) { append_exit_policy_string(dest, "reject *6:*"); @@ -968,7 +994,7 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, append_exit_policy_string(dest, "reject private:*"); if (local_address) { char buf[POLICY_BUF_LEN]; - tor_snprintf(buf, sizeof(buf), "reject %s:*", local_address); + tor_snprintf(buf, sizeof(buf), "reject %s:*", fmt_addr32(local_address)); append_exit_policy_string(dest, buf); } } @@ -985,6 +1011,77 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, return 0; } +/** Parse exit policy in <b>cfg</b> into <b>dest</b> smartlist. + * + * Add entry that rejects all IPv6 destinations unless + * <b>EXIT_POLICY_IPV6_ENABLED</b> bit is set in <b>options</b> bitmask. + * + * If <b>EXIT_POLICY_REJECT_PRIVATE</b> bit is set in <b>options</b>, + * do add entry that rejects all destinations in private subnetwork + * Tor is running in. + * + * Respectively, if <b>EXIT_POLICY_ADD_DEFAULT</b> bit is set, add + * default exit policy entries to <b>result</b> smartlist. + */ +int +policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, + exit_policy_parser_cfg_t options, + uint32_t local_address) +{ + int ipv6_enabled = (options & EXIT_POLICY_IPV6_ENABLED) ? 1 : 0; + int reject_private = (options & EXIT_POLICY_REJECT_PRIVATE) ? 1 : 0; + int add_default = (options & EXIT_POLICY_ADD_DEFAULT) ? 1 : 0; + + return policies_parse_exit_policy_internal(cfg,dest,ipv6_enabled, + reject_private, + local_address, + add_default); +} + +/** Parse <b>ExitPolicy</b> member of <b>or_options</b> into <b>result</b> + * smartlist. + * If <b>or_options->IPv6Exit</b> is false, add an entry that + * rejects all IPv6 destinations. + * + * If <b>or_options->ExitPolicyRejectPrivate</b> is true, add entry that + * rejects all destinations in the private subnetwork of machine Tor + * instance is running in. + * + * If <b>or_options->BridgeRelay</b> is false, add entries of default + * Tor exit policy into <b>result</b> smartlist. + * + * If or_options->ExitRelay is false, then make our exit policy into + * "reject *:*" regardless. + */ +int +policies_parse_exit_policy_from_options(const or_options_t *or_options, + uint32_t local_address, + smartlist_t **result) +{ + exit_policy_parser_cfg_t parser_cfg = 0; + + if (or_options->ExitRelay == 0) { + append_exit_policy_string(result, "reject *4:*"); + append_exit_policy_string(result, "reject *6:*"); + return 0; + } + + if (or_options->IPv6Exit) { + parser_cfg |= EXIT_POLICY_IPV6_ENABLED; + } + + if (or_options->ExitPolicyRejectPrivate) { + parser_cfg |= EXIT_POLICY_REJECT_PRIVATE; + } + + if (!or_options->BridgeRelay) { + parser_cfg |= EXIT_POLICY_ADD_DEFAULT; + } + + return policies_parse_exit_policy(or_options->ExitPolicy,result, + parser_cfg,local_address); +} + /** Add "reject *:*" to the end of the policy in *<b>dest</b>, allocating * *<b>dest</b> as needed. */ void @@ -1321,9 +1418,9 @@ policy_summary_add_item(smartlist_t *summary, addr_policy_t *p) * The summary will either be an "accept" plus a comma-separated list of port * ranges or a "reject" plus port-ranges, depending on which is shorter. * - * If no exits are allowed at all then NULL is returned, if no ports - * are blocked instead of "reject " we return "accept 1-65535" (this - * is an exception to the shorter-representation-wins rule). + * If no exits are allowed at all then "reject 1-65535" is returned. If no + * ports are blocked instead of "reject " we return "accept 1-65535". (These + * are an exception to the shorter-representation-wins rule). */ char * policy_summarize(smartlist_t *policy, sa_family_t family) @@ -1680,6 +1777,28 @@ getinfo_helper_policies(control_connection_t *conn, (void) errmsg; if (!strcmp(question, "exit-policy/default")) { *answer = tor_strdup(DEFAULT_EXIT_POLICY); + } else if (!strcmpstart(question, "exit-policy/")) { + const routerinfo_t *me = router_get_my_routerinfo(); + + int include_ipv4 = 0; + int include_ipv6 = 0; + + if (!strcmp(question, "exit-policy/ipv4")) { + include_ipv4 = 1; + } else if (!strcmp(question, "exit-policy/ipv6")) { + include_ipv6 = 1; + } else if (!strcmp(question, "exit-policy/full")) { + include_ipv4 = include_ipv6 = 1; + } else { + return 0; /* No such key. */ + } + + if (!me) { + *errmsg = "router_get_my_routerinfo returned NULL"; + return -1; + } + + *answer = router_dump_exit_policy_to_string(me,include_ipv4,include_ipv6); } return 0; } @@ -1731,8 +1850,6 @@ policies_free_all(void) authdir_reject_policy = NULL; addr_policy_list_free(authdir_invalid_policy); authdir_invalid_policy = NULL; - addr_policy_list_free(authdir_baddir_policy); - authdir_baddir_policy = NULL; addr_policy_list_free(authdir_badexit_policy); authdir_badexit_policy = NULL; diff --git a/src/or/policies.h b/src/or/policies.h index facbbb6b5a..0225b57a2c 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,6 +18,12 @@ */ #define POLICY_BUF_LEN 72 +#define EXIT_POLICY_IPV6_ENABLED (1 << 0) +#define EXIT_POLICY_REJECT_PRIVATE (1 << 1) +#define EXIT_POLICY_ADD_DEFAULT (1 << 2) + +typedef int exit_policy_parser_cfg_t; + 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(const routerinfo_t *ri); @@ -27,7 +33,6 @@ int dir_policy_permits_address(const tor_addr_t *addr); int socks_policy_permits_address(const tor_addr_t *addr); int authdir_policy_permits_address(uint32_t addr, uint16_t port); int authdir_policy_valid_address(uint32_t addr, uint16_t port); -int authdir_policy_baddir_address(uint32_t addr, uint16_t port); int authdir_policy_badexit_address(uint32_t addr, uint16_t port); int validate_addr_policies(const or_options_t *options, char **msg); @@ -37,16 +42,24 @@ int policies_parse_from_options(const or_options_t *options); addr_policy_t *addr_policy_get_canonical_entry(addr_policy_t *ent); int cmp_addr_policies(smartlist_t *a, smartlist_t *b); -addr_policy_result_t compare_tor_addr_to_addr_policy(const tor_addr_t *addr, - uint16_t port, const smartlist_t *policy); +MOCK_DECL(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_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 ipv6exit, - int rejectprivate, const char *local_address, + int rejectprivate, uint32_t local_address, int add_default_policy); +*/ +int policies_parse_exit_policy_from_options(const or_options_t *or_options, + uint32_t local_address, + smartlist_t **result); +int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, + exit_policy_parser_cfg_t options, + uint32_t local_address); void policies_exit_policy_append_reject_star(smartlist_t **dest); void addr_policy_append_reject_addr(smartlist_t **dest, const tor_addr_t *addr); diff --git a/src/or/reasons.c b/src/or/reasons.c index 0674474e72..23ab6041a6 100644 --- a/src/or/reasons.c +++ b/src/or/reasons.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -231,6 +231,8 @@ orconn_end_reason_to_control_string(int r) return "RESOURCELIMIT"; case END_OR_CONN_REASON_MISC: return "MISC"; + case END_OR_CONN_REASON_PT_MISSING: + return "PT_MISSING"; case 0: return ""; default: @@ -348,6 +350,8 @@ circuit_end_reason_to_control_string(int reason) return "NOSUCHSERVICE"; case END_CIRC_REASON_MEASUREMENT_EXPIRED: return "MEASUREMENT_EXPIRED"; + case END_CIRC_REASON_IP_NOW_REDUNDANT: + return "IP_NOW_REDUNDANT"; default: if (is_remote) { /* @@ -365,7 +369,7 @@ circuit_end_reason_to_control_string(int reason) } } -/** Return a string corresponding to a SOCKS4 reponse code. */ +/** Return a string corresponding to a SOCKS4 response code. */ const char * socks4_response_code_to_string(uint8_t code) { @@ -383,7 +387,7 @@ socks4_response_code_to_string(uint8_t code) } } -/** Return a string corresponding to a SOCKS5 reponse code. */ +/** Return a string corresponding to a SOCKS5 response code. */ const char * socks5_response_code_to_string(uint8_t code) { diff --git a/src/or/reasons.h b/src/or/reasons.h index fe7e67722a..00a099061b 100644 --- a/src/or/reasons.h +++ b/src/or/reasons.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/relay.c b/src/or/relay.c index 7f06c6e145..50eaab83c8 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,6 +15,7 @@ #include "addressmap.h" #include "buffers.h" #include "channel.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -25,7 +26,6 @@ #include "control.h" #include "geoip.h" #include "main.h" -#include "mempool.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -36,6 +36,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "scheduler.h" static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, @@ -58,6 +59,9 @@ static void adjust_exit_policy_from_exitpolicy_failure(origin_circuit_t *circ, entry_connection_t *conn, node_t *node, const tor_addr_t *addr); +#if 0 +static int get_max_middle_cells(void); +#endif /** Stop reading on edge connections when we have this many cells * waiting on the appropriate queue. */ @@ -105,14 +109,14 @@ relay_set_digest(crypto_digest_t *digest, cell_t *cell) static int relay_digest_matches(crypto_digest_t *digest, cell_t *cell) { - char received_integrity[4], calculated_integrity[4]; + uint32_t received_integrity, calculated_integrity; relay_header_t rh; crypto_digest_t *backup_digest=NULL; backup_digest = crypto_digest_dup(digest); relay_header_unpack(&rh, cell->payload); - memcpy(received_integrity, rh.integrity, 4); + memcpy(&received_integrity, rh.integrity, 4); memset(rh.integrity, 0, 4); relay_header_pack(cell->payload, &rh); @@ -121,15 +125,15 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell) // received_integrity[2], received_integrity[3]); crypto_digest_add_bytes(digest, (char*) cell->payload, CELL_PAYLOAD_SIZE); - crypto_digest_get_digest(digest, calculated_integrity, 4); + crypto_digest_get_digest(digest, (char*) &calculated_integrity, 4); - if (tor_memneq(received_integrity, calculated_integrity, 4)) { + if (calculated_integrity != received_integrity) { // log_fn(LOG_INFO,"Recognized=0 but bad digest. Not recognizing."); // (%d vs %d).", received_integrity, calculated_integrity); /* restore digest to its old form */ crypto_digest_assign(digest, backup_digest); /* restore the relay header */ - memcpy(rh.integrity, received_integrity, 4); + memcpy(rh.integrity, &received_integrity, 4); relay_header_pack(cell->payload, &rh); crypto_digest_free(backup_digest); return 0; @@ -517,6 +521,7 @@ relay_header_unpack(relay_header_t *dest, const uint8_t *src) static const char * relay_command_to_string(uint8_t command) { + static char buf[64]; switch (command) { case RELAY_COMMAND_BEGIN: return "BEGIN"; case RELAY_COMMAND_DATA: return "DATA"; @@ -541,7 +546,12 @@ relay_command_to_string(uint8_t command) case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: return "RENDEZVOUS_ESTABLISHED"; case RELAY_COMMAND_INTRODUCE_ACK: return "INTRODUCE_ACK"; - default: return "(unrecognized)"; + case RELAY_COMMAND_EXTEND2: return "EXTEND2"; + case RELAY_COMMAND_EXTENDED2: return "EXTENDED2"; + default: + tor_snprintf(buf, sizeof(buf), "Unrecognized relay command %u", + (unsigned)command); + return buf; } } @@ -791,8 +801,10 @@ connection_ap_process_end_not_open( return 0; } - if ((tor_addr_family(&addr) == AF_INET && !conn->ipv4_traffic_ok) || - (tor_addr_family(&addr) == AF_INET6 && !conn->ipv6_traffic_ok)) { + if ((tor_addr_family(&addr) == AF_INET && + !conn->entry_cfg.ipv4_traffic) || + (tor_addr_family(&addr) == AF_INET6 && + !conn->entry_cfg.ipv6_traffic)) { log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got an EXITPOLICY failure on a connection with a " "mismatched family. Closing."); @@ -968,7 +980,7 @@ remap_event_helper(entry_connection_t *conn, const tor_addr_t *new_addr) * <b>addr_out</b> to the address we're connected to, and <b>ttl_out</b> to * the ttl of that address, in seconds, and return 0. On failure, return * -1. */ -int +STATIC int connected_cell_parse(const relay_header_t *rh, const cell_t *cell, tor_addr_t *addr_out, int *ttl_out) { @@ -1005,6 +1017,254 @@ connected_cell_parse(const relay_header_t *rh, const cell_t *cell, return 0; } +/** Drop all storage held by <b>addr</b>. */ +STATIC void +address_ttl_free(address_ttl_t *addr) +{ + if (!addr) + return; + tor_free(addr->hostname); + tor_free(addr); +} + +/** Parse a resolved cell in <b>cell</b>, with parsed header in <b>rh</b>. + * Return -1 on parse error. On success, add one or more newly allocated + * address_ttl_t to <b>addresses_out</b>; set *<b>errcode_out</b> to + * one of 0, RESOLVED_TYPE_ERROR, or RESOLVED_TYPE_ERROR_TRANSIENT, and + * return 0. */ +STATIC int +resolved_cell_parse(const cell_t *cell, const relay_header_t *rh, + smartlist_t *addresses_out, int *errcode_out) +{ + const uint8_t *cp; + uint8_t answer_type; + size_t answer_len; + address_ttl_t *addr; + size_t remaining; + int errcode = 0; + smartlist_t *addrs; + + tor_assert(cell); + tor_assert(rh); + tor_assert(addresses_out); + tor_assert(errcode_out); + + *errcode_out = 0; + + if (rh->length > RELAY_PAYLOAD_SIZE) + return -1; + + addrs = smartlist_new(); + + cp = cell->payload + RELAY_HEADER_SIZE; + + remaining = rh->length; + while (remaining) { + const uint8_t *cp_orig = cp; + if (remaining < 2) + goto err; + answer_type = *cp++; + answer_len = *cp++; + if (remaining < 2 + answer_len + 4) { + goto err; + } + if (answer_type == RESOLVED_TYPE_IPV4) { + if (answer_len != 4) { + goto err; + } + addr = tor_malloc_zero(sizeof(*addr)); + tor_addr_from_ipv4n(&addr->addr, get_uint32(cp)); + cp += 4; + addr->ttl = ntohl(get_uint32(cp)); + cp += 4; + smartlist_add(addrs, addr); + } else if (answer_type == RESOLVED_TYPE_IPV6) { + if (answer_len != 16) + goto err; + addr = tor_malloc_zero(sizeof(*addr)); + tor_addr_from_ipv6_bytes(&addr->addr, (const char*) cp); + cp += 16; + addr->ttl = ntohl(get_uint32(cp)); + cp += 4; + smartlist_add(addrs, addr); + } else if (answer_type == RESOLVED_TYPE_HOSTNAME) { + if (answer_len == 0) { + goto err; + } + addr = tor_malloc_zero(sizeof(*addr)); + addr->hostname = tor_memdup_nulterm(cp, answer_len); + cp += answer_len; + addr->ttl = ntohl(get_uint32(cp)); + cp += 4; + smartlist_add(addrs, addr); + } else if (answer_type == RESOLVED_TYPE_ERROR_TRANSIENT || + answer_type == RESOLVED_TYPE_ERROR) { + errcode = answer_type; + /* Ignore the error contents */ + cp += answer_len + 4; + } else { + cp += answer_len + 4; + } + tor_assert(((ssize_t)remaining) >= (cp - cp_orig)); + remaining -= (cp - cp_orig); + } + + if (errcode && smartlist_len(addrs) == 0) { + /* Report an error only if there were no results. */ + *errcode_out = errcode; + } + + smartlist_add_all(addresses_out, addrs); + smartlist_free(addrs); + + return 0; + + err: + /* On parse error, don't report any results */ + SMARTLIST_FOREACH(addrs, address_ttl_t *, a, address_ttl_free(a)); + smartlist_free(addrs); + return -1; +} + +/** Helper for connection_edge_process_resolved_cell: given an error code, + * an entry_connection, and a list of address_ttl_t *, report the best answer + * to the entry_connection. */ +static void +connection_ap_handshake_socks_got_resolved_cell(entry_connection_t *conn, + int error_code, + smartlist_t *results) +{ + address_ttl_t *addr_ipv4 = NULL; + address_ttl_t *addr_ipv6 = NULL; + address_ttl_t *addr_hostname = NULL; + address_ttl_t *addr_best = NULL; + + /* If it's an error code, that's easy. */ + if (error_code) { + tor_assert(error_code == RESOLVED_TYPE_ERROR || + error_code == RESOLVED_TYPE_ERROR_TRANSIENT); + connection_ap_handshake_socks_resolved(conn, + error_code,0,NULL,-1,-1); + return; + } + + /* Get the first answer of each type. */ + SMARTLIST_FOREACH_BEGIN(results, address_ttl_t *, addr) { + if (addr->hostname) { + if (!addr_hostname) { + addr_hostname = addr; + } + } else if (tor_addr_family(&addr->addr) == AF_INET) { + if (!addr_ipv4 && conn->entry_cfg.ipv4_traffic) { + addr_ipv4 = addr; + } + } else if (tor_addr_family(&addr->addr) == AF_INET6) { + if (!addr_ipv6 && conn->entry_cfg.ipv6_traffic) { + addr_ipv6 = addr; + } + } + } SMARTLIST_FOREACH_END(addr); + + /* Now figure out which type we wanted to deliver. */ + if (conn->socks_request->command == SOCKS_COMMAND_RESOLVE_PTR) { + if (addr_hostname) { + connection_ap_handshake_socks_resolved(conn, + RESOLVED_TYPE_HOSTNAME, + strlen(addr_hostname->hostname), + (uint8_t*)addr_hostname->hostname, + addr_hostname->ttl,-1); + } else { + connection_ap_handshake_socks_resolved(conn, + RESOLVED_TYPE_ERROR,0,NULL,-1,-1); + } + return; + } + + if (conn->entry_cfg.prefer_ipv6) { + addr_best = addr_ipv6 ? addr_ipv6 : addr_ipv4; + } else { + addr_best = addr_ipv4 ? addr_ipv4 : addr_ipv6; + } + + /* Now convert it to the ugly old interface */ + if (! addr_best) { + connection_ap_handshake_socks_resolved(conn, + RESOLVED_TYPE_ERROR,0,NULL,-1,-1); + return; + } + + connection_ap_handshake_socks_resolved_addr(conn, + &addr_best->addr, + addr_best->ttl, + -1); + + remap_event_helper(conn, &addr_best->addr); +} + +/** Handle a RELAY_COMMAND_RESOLVED cell that we received on a non-open AP + * stream. */ +STATIC int +connection_edge_process_resolved_cell(edge_connection_t *conn, + const cell_t *cell, + const relay_header_t *rh) +{ + entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); + smartlist_t *resolved_addresses = NULL; + int errcode = 0; + + if (conn->base_.state != AP_CONN_STATE_RESOLVE_WAIT) { + log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got a 'resolved' cell while " + "not in state resolve_wait. Dropping."); + return 0; + } + tor_assert(SOCKS_COMMAND_IS_RESOLVE(entry_conn->socks_request->command)); + + resolved_addresses = smartlist_new(); + if (resolved_cell_parse(cell, rh, resolved_addresses, &errcode)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Dropping malformed 'resolved' cell"); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TORPROTOCOL); + goto done; + } + + if (get_options()->ClientDNSRejectInternalAddresses) { + int orig_len = smartlist_len(resolved_addresses); + SMARTLIST_FOREACH_BEGIN(resolved_addresses, address_ttl_t *, addr) { + if (addr->hostname == NULL && tor_addr_is_internal(&addr->addr, 0)) { + log_info(LD_APP, "Got a resolved cell with answer %s; dropping that " + "answer.", + safe_str_client(fmt_addr(&addr->addr))); + address_ttl_free(addr); + SMARTLIST_DEL_CURRENT(resolved_addresses, addr); + } + } SMARTLIST_FOREACH_END(addr); + if (orig_len && smartlist_len(resolved_addresses) == 0) { + log_info(LD_APP, "Got a resolved cell with only private addresses; " + "dropping it."); + connection_ap_handshake_socks_resolved(entry_conn, + RESOLVED_TYPE_ERROR_TRANSIENT, + 0, NULL, 0, TIME_MAX); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_TORPROTOCOL); + goto done; + } + } + + connection_ap_handshake_socks_got_resolved_cell(entry_conn, + errcode, + resolved_addresses); + + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_DONE | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + + done: + SMARTLIST_FOREACH(resolved_addresses, address_ttl_t *, addr, + address_ttl_free(addr)); + smartlist_free(resolved_addresses); + return 0; +} + /** An incoming relay cell has arrived from circuit <b>circ</b> to * stream <b>conn</b>. * @@ -1066,8 +1326,8 @@ connection_edge_process_relay_cell_not_open( return 0; } - if ((family == AF_INET && ! entry_conn->ipv4_traffic_ok) || - (family == AF_INET6 && ! entry_conn->ipv6_traffic_ok)) { + if ((family == AF_INET && ! entry_conn->entry_cfg.ipv4_traffic) || + (family == AF_INET6 && ! entry_conn->entry_cfg.ipv6_traffic)) { log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got a connected cell to %s with unsupported address family." " Closing.", fmt_addr(&addr)); @@ -1106,8 +1366,9 @@ connection_edge_process_relay_cell_not_open( break; case DIR_PURPOSE_FETCH_SERVERDESC: case DIR_PURPOSE_FETCH_MICRODESC: - control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, - count_loading_descriptors_progress()); + if (TO_DIR_CONN(dirconn)->router_purpose == ROUTER_PURPOSE_GENERAL) + control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, + count_loading_descriptors_progress()); break; } } @@ -1128,67 +1389,7 @@ connection_edge_process_relay_cell_not_open( } if (conn->base_.type == CONN_TYPE_AP && rh->command == RELAY_COMMAND_RESOLVED) { - int ttl; - int answer_len; - uint8_t answer_type; - entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); - if (conn->base_.state != AP_CONN_STATE_RESOLVE_WAIT) { - log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got a 'resolved' cell while " - "not in state resolve_wait. Dropping."); - return 0; - } - tor_assert(SOCKS_COMMAND_IS_RESOLVE(entry_conn->socks_request->command)); - answer_len = cell->payload[RELAY_HEADER_SIZE+1]; - if (rh->length < 2 || answer_len+2>rh->length) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Dropping malformed 'resolved' cell"); - connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TORPROTOCOL); - return 0; - } - answer_type = cell->payload[RELAY_HEADER_SIZE]; - if (rh->length >= answer_len+6) - ttl = (int)ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+ - 2+answer_len)); - else - ttl = -1; - if (answer_type == RESOLVED_TYPE_IPV4 || - answer_type == RESOLVED_TYPE_IPV6) { - tor_addr_t addr; - if (decode_address_from_payload(&addr, cell->payload+RELAY_HEADER_SIZE, - rh->length) && - tor_addr_is_internal(&addr, 0) && - get_options()->ClientDNSRejectInternalAddresses) { - log_info(LD_APP,"Got a resolve with answer %s. Rejecting.", - fmt_addr(&addr)); - connection_ap_handshake_socks_resolved(entry_conn, - RESOLVED_TYPE_ERROR_TRANSIENT, - 0, NULL, 0, TIME_MAX); - connection_mark_unattached_ap(entry_conn, - END_STREAM_REASON_TORPROTOCOL); - return 0; - } - } - connection_ap_handshake_socks_resolved(entry_conn, - answer_type, - cell->payload[RELAY_HEADER_SIZE+1], /*answer_len*/ - cell->payload+RELAY_HEADER_SIZE+2, /*answer*/ - ttl, - -1); - if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4) { - tor_addr_t addr; - tor_addr_from_ipv4n(&addr, - get_uint32(cell->payload+RELAY_HEADER_SIZE+2)); - remap_event_helper(entry_conn, &addr); - } else if (answer_type == RESOLVED_TYPE_IPV6 && answer_len == 16) { - tor_addr_t addr; - tor_addr_from_ipv6_bytes(&addr, - (char*)(cell->payload+RELAY_HEADER_SIZE+2)); - remap_event_helper(entry_conn, &addr); - } - connection_mark_unattached_ap(entry_conn, - END_STREAM_REASON_DONE | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return 0; + return connection_edge_process_resolved_cell(conn, cell, rh); } log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, @@ -1242,7 +1443,6 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, switch (rh.command) { case RELAY_COMMAND_BEGIN: case RELAY_COMMAND_CONNECTED: - case RELAY_COMMAND_DATA: case RELAY_COMMAND_END: case RELAY_COMMAND_RESOLVE: case RELAY_COMMAND_RESOLVED: @@ -1267,6 +1467,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, * EXIT_CONN_STATE_CONNECTING or EXIT_CONN_STATE_RESOLVING. * This speeds up HTTP, for example. */ optimistic_data = 1; + } else if (rh.stream_id == 0 && rh.command == RELAY_COMMAND_DATA) { + log_warn(LD_BUG, "Somehow I had a connection that matched a " + "data cell with stream ID 0."); } else { return connection_edge_process_relay_cell_not_open( &rh, cell, circ, conn, layer_hint); @@ -1327,7 +1530,11 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, circuit_consider_sending_sendme(circ, layer_hint); - if (!conn) { + if (rh.stream_id == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Relay data cell with zero " + "stream_id. Dropping."); + return 0; + } else if (!conn) { log_info(domain,"data cell dropped, unknown stream (streamid %d).", rh.stream_id); return 0; @@ -1436,8 +1643,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } if ((reason = circuit_finish_handshake(TO_ORIGIN_CIRCUIT(circ), &extended_cell.created_cell)) < 0) { - log_warn(domain,"circuit_finish_handshake failed."); - return reason; + circuit_mark_for_close(circ, -reason); + return 0; /* We don't want to cause a warning, so we mark the circuit + * here. */ } } if ((reason=circuit_send_next_onion_skin(TO_ORIGIN_CIRCUIT(circ)))<0) { @@ -1497,7 +1705,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (layer_hint) { if (layer_hint->package_window + CIRCWINDOW_INCREMENT > CIRCWINDOW_START_MAX) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + static struct ratelim_t exit_warn_ratelim = RATELIM_INIT(600); + log_fn_ratelim(&exit_warn_ratelim, LOG_WARN, LD_PROTOCOL, "Unexpected sendme cell from exit relay. " "Closing circ."); return -END_CIRC_REASON_TORPROTOCOL; @@ -1509,7 +1718,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } else { if (circ->package_window + CIRCWINDOW_INCREMENT > CIRCWINDOW_START_MAX) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + static struct ratelim_t client_warn_ratelim = RATELIM_INIT(600); + log_fn_ratelim(&client_warn_ratelim,LOG_PROTOCOL_WARN, LD_PROTOCOL, "Unexpected sendme cell from client. " "Closing circ (window %d).", circ->package_window); @@ -2036,60 +2246,31 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) #define assert_cmux_ok_paranoid(chan) #endif -/** The total number of cells we have allocated from the memory pool. */ +/** The total number of cells we have allocated. */ static size_t total_cells_allocated = 0; -/** A memory pool to allocate packed_cell_t objects. */ -static mp_pool_t *cell_pool = NULL; - -/** Allocate structures to hold cells. */ -void -init_cell_pool(void) -{ - tor_assert(!cell_pool); - cell_pool = mp_pool_new(sizeof(packed_cell_t), 128*1024); -} - -/** Free all storage used to hold cells (and insertion times if we measure - * cell statistics). */ -void -free_cell_pool(void) -{ - /* Maybe we haven't called init_cell_pool yet; need to check for it. */ - if (cell_pool) { - mp_pool_destroy(cell_pool); - cell_pool = NULL; - } -} - -/** Free excess storage in cell pool. */ -void -clean_cell_pool(void) -{ - tor_assert(cell_pool); - mp_pool_clean(cell_pool, 0, 1); -} - /** Release storage held by <b>cell</b>. */ static INLINE void packed_cell_free_unchecked(packed_cell_t *cell) { --total_cells_allocated; - mp_pool_release(cell); + tor_free(cell); } /** Allocate and return a new packed_cell_t. */ -static INLINE packed_cell_t * +STATIC packed_cell_t * packed_cell_new(void) { ++total_cells_allocated; - return mp_pool_get(cell_pool); + return tor_malloc_zero(sizeof(packed_cell_t)); } /** Return a packed cell used outside by channel_t lower layer */ void packed_cell_free(packed_cell_t *cell) { + if (!cell) + return; packed_cell_free_unchecked(cell); } @@ -2098,19 +2279,18 @@ packed_cell_free(packed_cell_t *cell) void dump_cell_pool_usage(int severity) { - circuit_t *c; int n_circs = 0; int n_cells = 0; - for (c = circuit_get_global_list_(); c; c = c->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, c) { n_cells += c->n_chan_cells.n; if (!CIRCUIT_IS_ORIGIN(c)) n_cells += TO_OR_CIRCUIT(c)->p_chan_cells.n; ++n_circs; } + SMARTLIST_FOREACH_END(c); tor_log(severity, LD_MM, "%d cells allocated on %d circuits. %d cells leaked.", n_cells, n_circs, (int)total_cells_allocated - n_cells); - mp_pool_log_status(cell_pool, severity); } /** Allocate a new copy of packed <b>cell</b>. */ @@ -2119,7 +2299,6 @@ packed_cell_copy(const cell_t *cell, int wide_circ_ids) { packed_cell_t *c = packed_cell_new(); cell_pack(c, cell, wide_circ_ids); - c->next = NULL; return c; } @@ -2127,58 +2306,61 @@ packed_cell_copy(const cell_t *cell, int wide_circ_ids) void cell_queue_append(cell_queue_t *queue, packed_cell_t *cell) { - if (queue->tail) { - tor_assert(!queue->tail->next); - queue->tail->next = cell; - } else { - queue->head = cell; - } - queue->tail = cell; - cell->next = NULL; + TOR_SIMPLEQ_INSERT_TAIL(&queue->head, cell, next); ++queue->n; } -/** Append a newly allocated copy of <b>cell</b> to the end of <b>queue</b> */ +/** Append a newly allocated copy of <b>cell</b> to the end of the + * <b>exitward</b> (or app-ward) <b>queue</b> of <b>circ</b>. If + * <b>use_stats</b> is true, record statistics about the cell. + */ void -cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell, - int wide_circ_ids) +cell_queue_append_packed_copy(circuit_t *circ, cell_queue_t *queue, + int exitward, const cell_t *cell, + int wide_circ_ids, int use_stats) { struct timeval now; packed_cell_t *copy = packed_cell_copy(cell, wide_circ_ids); - tor_gettimeofday_cached(&now); + (void)circ; + (void)exitward; + (void)use_stats; + tor_gettimeofday_cached_monotonic(&now); + copy->inserted_time = (uint32_t)tv_to_msec(&now); cell_queue_append(queue, copy); } +/** Initialize <b>queue</b> as an empty cell queue. */ +void +cell_queue_init(cell_queue_t *queue) +{ + memset(queue, 0, sizeof(cell_queue_t)); + TOR_SIMPLEQ_INIT(&queue->head); +} + /** Remove and free every cell in <b>queue</b>. */ void cell_queue_clear(cell_queue_t *queue) { - packed_cell_t *cell, *next; - cell = queue->head; - while (cell) { - next = cell->next; + packed_cell_t *cell; + while ((cell = TOR_SIMPLEQ_FIRST(&queue->head))) { + TOR_SIMPLEQ_REMOVE_HEAD(&queue->head, next); packed_cell_free_unchecked(cell); - cell = next; } - queue->head = queue->tail = NULL; + TOR_SIMPLEQ_INIT(&queue->head); queue->n = 0; } /** Extract and return the cell at the head of <b>queue</b>; return NULL if * <b>queue</b> is empty. */ -static INLINE packed_cell_t * +STATIC packed_cell_t * cell_queue_pop(cell_queue_t *queue) { - packed_cell_t *cell = queue->head; + packed_cell_t *cell = TOR_SIMPLEQ_FIRST(&queue->head); if (!cell) return NULL; - queue->head = cell->next; - if (cell == queue->tail) { - tor_assert(!queue->head); - queue->tail = NULL; - } + TOR_SIMPLEQ_REMOVE_HEAD(&queue->head, next); --queue->n; return cell; } @@ -2188,22 +2370,61 @@ cell_queue_pop(cell_queue_t *queue) size_t packed_cell_mem_cost(void) { - return sizeof(packed_cell_t) + MP_POOL_ITEM_OVERHEAD; + return sizeof(packed_cell_t); } +/** DOCDOC */ +STATIC size_t +cell_queues_get_total_allocation(void) +{ + return total_cells_allocated * packed_cell_mem_cost(); +} + +/** How long after we've been low on memory should we try to conserve it? */ +#define MEMORY_PRESSURE_INTERVAL (30*60) + +/** The time at which we were last low on memory. */ +static time_t last_time_under_memory_pressure = 0; + /** Check whether we've got too much space used for cells. If so, * call the OOM handler and return 1. Otherwise, return 0. */ -static int +STATIC int cell_queues_check_size(void) { - size_t alloc = total_cells_allocated * packed_cell_mem_cost(); - if (alloc >= get_options()->MaxMemInCellQueues) { - circuits_handle_oom(alloc); - return 1; + size_t alloc = cell_queues_get_total_allocation(); + alloc += buf_get_total_allocation(); + alloc += tor_zlib_get_total_allocation(); + const size_t rend_cache_total = rend_cache_get_total_allocation(); + alloc += rend_cache_total; + if (alloc >= get_options()->MaxMemInQueues_low_threshold) { + last_time_under_memory_pressure = approx_time(); + if (alloc >= get_options()->MaxMemInQueues) { + /* If we're spending over 20% of the memory limit on hidden service + * descriptors, free them until we're down to 10%. + */ + if (rend_cache_total > get_options()->MaxMemInQueues / 5) { + const size_t bytes_to_remove = + rend_cache_total - (size_t)(get_options()->MaxMemInQueues / 10); + rend_cache_clean_v2_descs_as_dir(time(NULL), bytes_to_remove); + alloc -= rend_cache_total; + alloc += rend_cache_get_total_allocation(); + } + circuits_handle_oom(alloc); + return 1; + } } return 0; } +/** Return true if we've been under memory pressure in the last + * MEMORY_PRESSURE_INTERVAL seconds. */ +int +have_been_under_memory_pressure(void) +{ + return last_time_under_memory_pressure + MEMORY_PRESSURE_INTERVAL + < approx_time(); +} + /** * Update the number of cells available on the circuit's n_chan or p_chan's * circuit mux. @@ -2252,14 +2473,18 @@ update_circuit_on_cmux_(circuit_t *circ, cell_direction_t direction, assert_cmux_ok_paranoid(chan); } -/** Remove all circuits from the cmux on <b>chan</b>. */ +/** Remove all circuits from the cmux on <b>chan</b>. + * + * If <b>circuits_out</b> is non-NULL, add all detached circuits to + * <b>circuits_out</b>. + **/ void -channel_unlink_all_circuits(channel_t *chan) +channel_unlink_all_circuits(channel_t *chan, smartlist_t *circuits_out) { tor_assert(chan); tor_assert(chan->cmux); - circuitmux_detach_all_circuits(chan->cmux); + circuitmux_detach_all_circuits(chan->cmux, circuits_out); chan->num_n_circuits = 0; chan->num_p_circuits = 0; } @@ -2318,16 +2543,38 @@ set_streams_blocked_on_circ(circuit_t *circ, channel_t *chan, return n; } +/** Extract the command from a packed cell. */ +static uint8_t +packed_cell_get_command(const packed_cell_t *cell, int wide_circ_ids) +{ + if (wide_circ_ids) { + return get_uint8(cell->body+4); + } else { + return get_uint8(cell->body+2); + } +} + +/** Extract the circuit ID from a packed cell. */ +circid_t +packed_cell_get_circid(const packed_cell_t *cell, int wide_circ_ids) +{ + if (wide_circ_ids) { + return ntohl(get_uint32(cell->body)); + } else { + return ntohs(get_uint16(cell->body)); + } +} + /** Pull as many cells as possible (but no more than <b>max</b>) from the * queue of the first active circuit on <b>chan</b>, and write them to * <b>chan</b>->outbuf. Return the number of cells written. Advance * the active circuit pointer to the next active circuit in the ring. */ -int -channel_flush_from_first_active_circuit(channel_t *chan, int max) +MOCK_IMPL(int, +channel_flush_from_first_active_circuit, (channel_t *chan, int max)) { circuitmux_t *cmux = NULL; int n_flushed = 0; - cell_queue_t *queue; + cell_queue_t *queue, *destroy_queue=NULL; circuit_t *circ; or_circuit_t *or_circ; int streams_blocked; @@ -2340,7 +2587,18 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) /* Main loop: pick a circuit, send a cell, update the cmux */ while (n_flushed < max) { - circ = circuitmux_get_first_active_circuit(cmux); + circ = circuitmux_get_first_active_circuit(cmux, &destroy_queue); + if (destroy_queue) { + /* this code is duplicated from some of the logic below. Ugly! XXXX */ + tor_assert(destroy_queue->n > 0); + cell = cell_queue_pop(destroy_queue); + channel_write_packed_cell(chan, cell); + /* Update the cmux destroy counter */ + circuitmux_notify_xmit_destroy(cmux); + cell = NULL; + ++n_flushed; + continue; + } /* If it returns NULL, no cells left to send */ if (!circ) break; assert_cmux_ok_paranoid(chan); @@ -2366,15 +2624,33 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) cell = cell_queue_pop(queue); /* Calculate the exact time that this cell has spent in the queue. */ - if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) { + if (get_options()->CellStatistics || + get_options()->TestingEnableCellStatsEvent) { uint32_t msec_waiting; struct timeval tvnow; - or_circ = TO_OR_CIRCUIT(circ); tor_gettimeofday_cached(&tvnow); msec_waiting = ((uint32_t)tv_to_msec(&tvnow)) - cell->inserted_time; - or_circ->total_cell_waiting_time += msec_waiting; - or_circ->processed_cells++; + if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) { + or_circ = TO_OR_CIRCUIT(circ); + or_circ->total_cell_waiting_time += msec_waiting; + or_circ->processed_cells++; + } + + if (get_options()->TestingEnableCellStatsEvent) { + uint8_t command = packed_cell_get_command(cell, chan->wide_circ_ids); + + testing_cell_stats_entry_t *ent = + tor_malloc_zero(sizeof(testing_cell_stats_entry_t)); + ent->command = command; + ent->waiting_time = msec_waiting / 10; + ent->removed = 1; + if (circ->n_chan == chan) + ent->exitward = 1; + if (!circ->testing_cell_stats) + circ->testing_cell_stats = smartlist_new(); + smartlist_add(circ->testing_cell_stats, ent); + } } /* If we just flushed our queue and this circuit is used for a @@ -2420,6 +2696,20 @@ channel_flush_from_first_active_circuit(channel_t *chan, int max) return n_flushed; } +#if 0 +/** Indicate the current preferred cap for middle circuits; zero disables + * the cap. Right now it's just a constant, ORCIRC_MAX_MIDDLE_CELLS, but + * the logic in append_cell_to_circuit_queue() is written to be correct + * if we want to base it on a consensus param or something that might change + * in the future. + */ +static int +get_max_middle_cells(void) +{ + return ORCIRC_MAX_MIDDLE_CELLS; +} +#endif + /** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b> * transmitting in <b>direction</b>. */ void @@ -2430,11 +2720,16 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, or_circuit_t *orcirc = NULL; cell_queue_t *queue; int streams_blocked; +#if 0 + uint32_t tgt_max_middle_cells, p_len, n_len, tmp, hard_max_middle_cells; +#endif + int exitward; if (circ->marked_for_close) return; - if (direction == CELL_DIRECTION_OUT) { + exitward = (direction == CELL_DIRECTION_OUT); + if (exitward) { queue = &circ->n_chan_cells; streams_blocked = circ->streams_blocked_on_n_chan; } else { @@ -2451,28 +2746,82 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, if ((circ->n_chan != NULL) && CIRCUIT_IS_ORCIRC(circ)) { orcirc = TO_OR_CIRCUIT(circ); if (orcirc->p_chan) { - if (queue->n + 1 >= ORCIRC_MAX_MIDDLE_CELLS) { - /* Queueing this cell would put queue over the cap */ - log_warn(LD_CIRC, - "Got a cell exceeding the cap of %u in the %s direction " - "on middle circ ID %u on chan ID " U64_FORMAT - "; killing the circuit.", - ORCIRC_MAX_MIDDLE_CELLS, - (direction == CELL_DIRECTION_OUT) ? "n" : "p", - (direction == CELL_DIRECTION_OUT) ? - circ->n_circ_id : orcirc->p_circ_id, - U64_PRINTF_ARG( + /* We are a middle circuit if we have both n_chan and p_chan */ + /* We'll need to know the current preferred maximum */ + tgt_max_middle_cells = get_max_middle_cells(); + if (tgt_max_middle_cells > 0) { + /* Do we need to initialize middle_max_cells? */ + if (orcirc->max_middle_cells == 0) { + orcirc->max_middle_cells = tgt_max_middle_cells; + } else { + if (tgt_max_middle_cells > orcirc->max_middle_cells) { + /* If we want to increase the cap, we can do so right away */ + orcirc->max_middle_cells = tgt_max_middle_cells; + } else if (tgt_max_middle_cells < orcirc->max_middle_cells) { + /* + * If we're shrinking the cap, we can't shrink past either queue; + * compare tgt_max_middle_cells rather than tgt_max_middle_cells * + * ORCIRC_MAX_MIDDLE_KILL_THRESH so the queues don't shrink enough + * to generate spurious warnings, either. + */ + n_len = circ->n_chan_cells.n; + p_len = orcirc->p_chan_cells.n; + tmp = tgt_max_middle_cells; + if (tmp < n_len) tmp = n_len; + if (tmp < p_len) tmp = p_len; + orcirc->max_middle_cells = tmp; + } + /* else no change */ + } + } else { + /* tgt_max_middle_cells == 0 indicates we should disable the cap */ + orcirc->max_middle_cells = 0; + } + + /* Now we know orcirc->max_middle_cells is set correctly */ + if (orcirc->max_middle_cells > 0) { + hard_max_middle_cells = + (uint32_t)(((double)orcirc->max_middle_cells) * + ORCIRC_MAX_MIDDLE_KILL_THRESH); + + if ((unsigned)queue->n + 1 >= hard_max_middle_cells) { + /* Queueing this cell would put queue over the kill theshold */ + log_warn(LD_CIRC, + "Got a cell exceeding the hard cap of %u in the " + "%s direction on middle circ ID %u on chan ID " + U64_FORMAT "; killing the circuit.", + hard_max_middle_cells, + (direction == CELL_DIRECTION_OUT) ? "n" : "p", + (direction == CELL_DIRECTION_OUT) ? + circ->n_circ_id : orcirc->p_circ_id, + U64_PRINTF_ARG( + (direction == CELL_DIRECTION_OUT) ? + circ->n_chan->global_identifier : + orcirc->p_chan->global_identifier)); + circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); + return; + } else if ((unsigned)queue->n + 1 == orcirc->max_middle_cells) { + /* Only use ==, not >= for this test so we don't spam the log */ + log_warn(LD_CIRC, + "While trying to queue a cell, reached the soft cap of %u " + "in the %s direction on middle circ ID %u " + "on chan ID " U64_FORMAT ".", + orcirc->max_middle_cells, + (direction == CELL_DIRECTION_OUT) ? "n" : "p", (direction == CELL_DIRECTION_OUT) ? - circ->n_chan->global_identifier : - orcirc->p_chan->global_identifier)); - circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); - return; + circ->n_circ_id : orcirc->p_circ_id, + U64_PRINTF_ARG( + (direction == CELL_DIRECTION_OUT) ? + circ->n_chan->global_identifier : + orcirc->p_chan->global_identifier)); + } } } } #endif - cell_queue_append_packed_copy(queue, cell, chan->wide_circ_ids); + cell_queue_append_packed_copy(circ, queue, exitward, cell, + chan->wide_circ_ids, 1); if (PREDICT_UNLIKELY(cell_queues_check_size())) { /* We ran the OOM handler */ @@ -2497,14 +2846,8 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, log_debug(LD_GENERAL, "Made a circuit active."); } - if (!channel_has_queued_writes(chan)) { - /* There is no data at all waiting to be sent on the outbuf. Add a - * cell, so that we can notice when it gets flushed, flushed_some can - * get called, and we can start putting more data onto the buffer then. - */ - log_debug(LD_GENERAL, "Primed a buffer."); - channel_flush_from_first_active_circuit(chan, 1); - } + /* New way: mark this as having waiting cells for the scheduler */ + scheduler_channel_has_waiting_cells(chan); } /** Append an encoded value of <b>addr</b> to <b>payload_out</b>, which must diff --git a/src/or/relay.h b/src/or/relay.h index 1fef10a7da..a4f583d11e 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -42,25 +42,27 @@ extern uint64_t stats_n_data_bytes_packaged; extern uint64_t stats_n_data_cells_received; extern uint64_t stats_n_data_bytes_received; -void init_cell_pool(void); -void free_cell_pool(void); -void clean_cell_pool(void); void dump_cell_pool_usage(int severity); size_t packed_cell_mem_cost(void); +int have_been_under_memory_pressure(void); + /* For channeltls.c */ void packed_cell_free(packed_cell_t *cell); +void cell_queue_init(cell_queue_t *queue); void cell_queue_clear(cell_queue_t *queue); void cell_queue_append(cell_queue_t *queue, packed_cell_t *cell); -void cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell, - int wide_circ_ids); +void cell_queue_append_packed_copy(circuit_t *circ, cell_queue_t *queue, + int exitward, const cell_t *cell, + int wide_circ_ids, int use_stats); void append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, cell_t *cell, cell_direction_t direction, streamid_t fromstream); -void channel_unlink_all_circuits(channel_t *chan); -int channel_flush_from_first_active_circuit(channel_t *chan, int max); +void channel_unlink_all_circuits(channel_t *chan, smartlist_t *detached_out); +MOCK_DECL(int, channel_flush_from_first_active_circuit, + (channel_t *chan, int max)); void assert_circuit_mux_okay(channel_t *chan); void update_circuit_on_cmux_(circuit_t *circ, cell_direction_t direction, const char *file, int lineno); @@ -75,11 +77,30 @@ void circuit_clear_cell_queue(circuit_t *circ, channel_t *chan); void stream_choice_seed_weak_rng(void); -#ifdef RELAY_PRIVATE int relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, crypt_path_t **layer_hint, char *recognized); -int connected_cell_parse(const relay_header_t *rh, const cell_t *cell, + +circid_t packed_cell_get_circid(const packed_cell_t *cell, int wide_circ_ids); + +#ifdef RELAY_PRIVATE +STATIC int connected_cell_parse(const relay_header_t *rh, const cell_t *cell, tor_addr_t *addr_out, int *ttl_out); +/** An address-and-ttl tuple as yielded by resolved_cell_parse */ +typedef struct address_ttl_s { + tor_addr_t addr; + char *hostname; + int ttl; +} address_ttl_t; +STATIC void address_ttl_free(address_ttl_t *addr); +STATIC int resolved_cell_parse(const cell_t *cell, const relay_header_t *rh, + smartlist_t *addresses_out, int *errcode_out); +STATIC int connection_edge_process_resolved_cell(edge_connection_t *conn, + const cell_t *cell, + const relay_header_t *rh); +STATIC packed_cell_t *packed_cell_new(void); +STATIC packed_cell_t *cell_queue_pop(cell_queue_t *queue); +STATIC size_t cell_queues_get_total_allocation(void); +STATIC int cell_queues_check_size(void); #endif #endif diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 7abbfd6fc5..162e0ac53e 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -8,6 +8,7 @@ **/ #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -25,6 +26,7 @@ #include "router.h" #include "routerlist.h" #include "routerset.h" +#include "control.h" static extend_info_t *rend_client_get_random_intro_impl( const rend_cache_entry_t *rend_query, @@ -128,16 +130,6 @@ rend_client_reextend_intro_circuit(origin_circuit_t *circ) return result; } -/** Return true iff we should send timestamps in our INTRODUCE1 cells */ -static int -rend_client_should_send_timestamp(void) -{ - if (get_options()->Support022HiddenServices >= 0) - return get_options()->Support022HiddenServices; - - return networkstatus_get_param(NULL, "Support022HiddenServices", 1, 0, 1); -} - /** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell * down introcirc if possible. */ @@ -249,14 +241,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc, REND_DESC_COOKIE_LEN); v3_shift += 2+REND_DESC_COOKIE_LEN; } - if (rend_client_should_send_timestamp()) { - uint32_t now = (uint32_t)time(NULL); - now += 300; - now -= now % 600; - set_uint32(tmp+v3_shift+1, htonl(now)); - } else { - set_uint32(tmp+v3_shift+1, 0); - } + /* Once this held a timestamp. */ + set_uint32(tmp+v3_shift+1, 0); v3_shift += 4; } /* if version 2 only write version number */ else if (entry->parsed->protocols & (1<<2)) { @@ -368,15 +354,13 @@ rend_client_rendcirc_has_opened(origin_circuit_t *circ) } /** - * Called to close other intro circuits we launched in parallel - * due to timeout. + * Called to close other intro circuits we launched in parallel. */ static void rend_client_close_other_intros(const char *onion_address) { - circuit_t *c; /* abort parallel intro circs, if any */ - for (c = circuit_get_global_list_(); c; c = c->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, c) { if ((c->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || c->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) && !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { @@ -387,10 +371,11 @@ rend_client_close_other_intros(const char *onion_address) log_info(LD_REND|LD_CIRC, "Closing introduction circuit %d that we " "built in parallel (Purpose %d).", oc->global_identifier, c->purpose); - circuit_mark_for_close(c, END_CIRC_REASON_TIMEOUT); + circuit_mark_for_close(c, END_CIRC_REASON_IP_NOW_REDUNDANT); } } } + SMARTLIST_FOREACH_END(c); } /** Called when get an ACK or a NAK for a REND_INTRODUCE1 cell. @@ -466,6 +451,13 @@ rend_client_introduction_acked(origin_circuit_t *circ, /* XXXX If that call failed, should we close the rend circuit, * too? */ return result; + } else { + /* Close circuit because no more intro points are usable thus not + * useful anymore. Change it's purpose before so we don't report an + * intro point failure again triggering an extra descriptor fetch. */ + circuit_change_purpose(TO_CIRCUIT(circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); } } return 0; @@ -562,7 +554,12 @@ directory_clean_last_hid_serv_requests(time_t now) /** Remove all requests related to the hidden service named * <b>onion_address</b> from the history of times of requests to - * hidden service directories. */ + * hidden service directories. + * + * This is called from rend_client_note_connection_attempt_ended(), which + * must be idempotent, so any future changes to this function must leave + * it idempotent too. + */ static void purge_hid_serv_from_last_hid_serv_requests(const char *onion_address) { @@ -617,11 +614,19 @@ static int directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) { smartlist_t *responsible_dirs = smartlist_new(); + smartlist_t *usable_responsible_dirs = smartlist_new(); + const or_options_t *options = get_options(); routerstatus_t *hs_dir; char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; time_t now = time(NULL); char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64]; - int tor2web_mode = get_options()->Tor2webMode; +#ifdef ENABLE_TOR2WEB_MODE + const int tor2web_mode = options->Tor2webMode; + const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS; +#else + const int how_to_fetch = DIRIND_ANONYMOUS; +#endif + int excluded_some; tor_assert(desc_id); tor_assert(rend_query); /* Determine responsible dirs. Even if we can't get all we want, @@ -642,16 +647,33 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) dir, desc_id_base32, rend_query, 0, 0); const node_t *node = node_get_by_id(dir->identity_digest); if (last + REND_HID_SERV_DIR_REQUERY_PERIOD >= now || - !node || !node_has_descriptor(node)) - SMARTLIST_DEL_CURRENT(responsible_dirs, dir); + !node || !node_has_descriptor(node)) { + SMARTLIST_DEL_CURRENT(responsible_dirs, dir); + continue; + } + if (! routerset_contains_node(options->ExcludeNodes, node)) { + smartlist_add(usable_responsible_dirs, dir); + } }); - hs_dir = smartlist_choose(responsible_dirs); + excluded_some = + smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); + + hs_dir = smartlist_choose(usable_responsible_dirs); + if (! hs_dir && ! options->StrictNodes) + hs_dir = smartlist_choose(responsible_dirs); + smartlist_free(responsible_dirs); + smartlist_free(usable_responsible_dirs); if (!hs_dir) { log_info(LD_REND, "Could not pick one of the responsible hidden " "service directories, because we requested them all " "recently without success."); + if (options->StrictNodes && excluded_some) { + log_warn(LD_REND, "Could not pick a hidden service directory for the " + "requested hidden service: they are all either down or " + "excluded, and StrictNodes is set."); + } return 0; } @@ -680,7 +702,7 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) directory_initiate_command_routerstatus_rend(hs_dir, DIR_PURPOSE_FETCH_RENDDESC_V2, ROUTER_PURPOSE_GENERAL, - tor2web_mode?DIRIND_ONEHOP:DIRIND_ANONYMOUS, + how_to_fetch, desc_id_base32, NULL, 0, 0, rend_query); @@ -693,6 +715,9 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) (rend_query->auth_type == REND_NO_AUTH ? "[none]" : escaped_safe_str_client(descriptor_cookie_base64)), routerstatus_describe(hs_dir)); + control_event_hs_descriptor_requested(rend_query, + hs_dir->identity_digest, + desc_id_base32); return 1; } @@ -772,8 +797,7 @@ rend_client_cancel_descriptor_fetches(void) SMARTLIST_FOREACH_BEGIN(connection_array, connection_t *, conn) { if (conn->type == CONN_TYPE_DIR && - (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC || - conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2)) { + conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) { /* It's a rendezvous descriptor fetch in progress -- cancel it * by marking the connection for close. * @@ -1069,8 +1093,11 @@ rend_client_desc_trynow(const char *query) /** Clear temporary state used only during an attempt to connect to * the hidden service named <b>onion_address</b>. Called when a - * connection attempt has ended; may be called occasionally at other - * times, and should be reasonably harmless. */ + * connection attempt has ended; it is possible for this to be called + * multiple times while handling an ended connection attempt, and + * any future changes to this function must ensure it remains + * idempotent. + */ void rend_client_note_connection_attempt_ended(const char *onion_address) { diff --git a/src/or/rendclient.h b/src/or/rendclient.h index 1f731d0ae5..098c61d0a1 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index d1f8b1af99..6698f2feaf 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -155,7 +155,7 @@ rend_compute_v2_desc_id(char *desc_id_out, const char *service_id, } /* Calculate current time-period. */ time_period = get_time_period(now, 0, service_id_binary); - /* Calculate secret-id-part = h(time-period + replica). */ + /* Calculate secret-id-part = h(time-period | replica). */ get_secret_id_part_bytes(secret_id_part, time_period, descriptor_cookie, replica); /* Calculate descriptor ID. */ @@ -411,7 +411,7 @@ rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc) &test_intro_content, &test_intro_size, &test_encoded_size, - &test_next, desc->desc_str); + &test_next, desc->desc_str, 1); rend_service_descriptor_free(test_parsed); tor_free(test_intro_content); return (res >= 0); @@ -528,7 +528,7 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, return -1; } /* Base64-encode introduction points. */ - ipos_base64 = tor_malloc_zero(ipos_len * 2); + ipos_base64 = tor_calloc(ipos_len, 2); if (base64_encode(ipos_base64, ipos_len * 2, ipos, ipos_len)<0) { log_warn(LD_REND, "Could not encode introduction point string to " "base64. length=%d", (int)ipos_len); @@ -556,7 +556,7 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, char desc_digest[DIGEST_LEN]; rend_encoded_v2_service_descriptor_t *enc = tor_malloc_zero(sizeof(rend_encoded_v2_service_descriptor_t)); - /* Calculate secret-id-part = h(time-period + cookie + replica). */ + /* Calculate secret-id-part = h(time-period | cookie | replica). */ get_secret_id_part_bytes(secret_id_part, time_period, descriptor_cookie, k); base32_encode(secret_id_part_base32, sizeof(secret_id_part_base32), @@ -672,79 +672,6 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, return seconds_valid; } -/** Parse a service descriptor at <b>str</b> (<b>len</b> bytes). On - * success, return a newly alloced service_descriptor_t. On failure, - * return NULL. - */ -rend_service_descriptor_t * -rend_parse_service_descriptor(const char *str, size_t len) -{ - rend_service_descriptor_t *result = NULL; - int i, n_intro_points; - size_t keylen, asn1len; - const char *end, *cp, *eos; - rend_intro_point_t *intro; - - result = tor_malloc_zero(sizeof(rend_service_descriptor_t)); - cp = str; - end = str+len; - if (end-cp<2) goto truncated; - result->version = 0; - if (end-cp < 2) goto truncated; - asn1len = ntohs(get_uint16(cp)); - cp += 2; - if ((size_t)(end-cp) < asn1len) goto truncated; - result->pk = crypto_pk_asn1_decode(cp, asn1len); - if (!result->pk) goto truncated; - cp += asn1len; - if (end-cp < 4) goto truncated; - result->timestamp = (time_t) ntohl(get_uint32(cp)); - cp += 4; - result->protocols = 1<<2; /* always use intro format 2 */ - if (end-cp < 2) goto truncated; - n_intro_points = ntohs(get_uint16(cp)); - cp += 2; - - result->intro_nodes = smartlist_new(); - for (i=0;i<n_intro_points;++i) { - if (end-cp < 2) goto truncated; - eos = (const char *)memchr(cp,'\0',end-cp); - if (!eos) goto truncated; - /* Write nickname to extend info, but postpone the lookup whether - * we know that router. It's not part of the parsing process. */ - intro = tor_malloc_zero(sizeof(rend_intro_point_t)); - intro->extend_info = tor_malloc_zero(sizeof(extend_info_t)); - strlcpy(intro->extend_info->nickname, cp, - sizeof(intro->extend_info->nickname)); - smartlist_add(result->intro_nodes, intro); - cp = eos+1; - } - keylen = crypto_pk_keysize(result->pk); - tor_assert(end-cp >= 0); - if ((size_t)(end-cp) < keylen) goto truncated; - if ((size_t)(end-cp) > keylen) { - log_warn(LD_PROTOCOL, - "Signature is %d bytes too long on service descriptor.", - (int)((size_t)(end-cp) - keylen)); - goto error; - } - note_crypto_pk_op(REND_CLIENT); - if (crypto_pk_public_checksig_digest(result->pk, - (char*)str,cp-str, /* data */ - (char*)cp,end-cp /* signature*/ - )<0) { - log_warn(LD_PROTOCOL, "Bad signature on service descriptor."); - goto error; - } - - return result; - truncated: - log_warn(LD_PROTOCOL, "Truncated service descriptor."); - error: - rend_service_descriptor_free(result); - return NULL; -} - /** Sets <b>out</b> to the first 10 bytes of the digest of <b>pk</b>, * base32 encoded. NUL-terminates out. (We use this string to * identify services in directory requests and .onion URLs.) @@ -777,6 +704,9 @@ static strmap_t *rend_cache = NULL; * directories. */ static digestmap_t *rend_cache_v2_dir = NULL; +/** DOCDOC */ +static size_t rend_cache_total_allocation = 0; + /** Initializes the service descriptor cache. */ void @@ -786,12 +716,64 @@ rend_cache_init(void) rend_cache_v2_dir = digestmap_new(); } +/** Return the approximate number of bytes needed to hold <b>e</b>. */ +static size_t +rend_cache_entry_allocation(const rend_cache_entry_t *e) +{ + if (!e) + return 0; + + /* This doesn't count intro_nodes or key size */ + return sizeof(*e) + e->len + sizeof(*e->parsed); +} + +/** DOCDOC */ +size_t +rend_cache_get_total_allocation(void) +{ + return rend_cache_total_allocation; +} + +/** Decrement the total bytes attributed to the rendezvous cache by n. */ +static void +rend_cache_decrement_allocation(size_t n) +{ + static int have_underflowed = 0; + + if (rend_cache_total_allocation >= n) { + rend_cache_total_allocation -= n; + } else { + rend_cache_total_allocation = 0; + if (! have_underflowed) { + have_underflowed = 1; + log_warn(LD_BUG, "Underflow in rend_cache_decrement_allocation"); + } + } +} + +/** Increase the total bytes attributed to the rendezvous cache by n. */ +static void +rend_cache_increment_allocation(size_t n) +{ + static int have_overflowed = 0; + if (rend_cache_total_allocation <= SIZE_MAX - n) { + rend_cache_total_allocation += n; + } else { + rend_cache_total_allocation = SIZE_MAX; + if (! have_overflowed) { + have_overflowed = 1; + log_warn(LD_BUG, "Overflow in rend_cache_increment_allocation"); + } + } +} + /** Helper: free storage held by a single service descriptor cache entry. */ static void rend_cache_entry_free(rend_cache_entry_t *e) { if (!e) return; + rend_cache_decrement_allocation(rend_cache_entry_allocation(e)); rend_service_descriptor_free(e->parsed); tor_free(e->desc); tor_free(e); @@ -813,6 +795,7 @@ rend_cache_free_all(void) digestmap_free(rend_cache_v2_dir, rend_cache_entry_free_); rend_cache = NULL; rend_cache_v2_dir = NULL; + rend_cache_total_allocation = 0; } /** Removes all old entries from the service descriptor cache. @@ -843,38 +826,53 @@ void rend_cache_purge(void) { if (rend_cache) { - log_info(LD_REND, "Purging client/v0-HS-authority HS descriptor cache"); + log_info(LD_REND, "Purging HS descriptor cache"); strmap_free(rend_cache, rend_cache_entry_free_); } rend_cache = strmap_new(); } /** Remove all old v2 descriptors and those for which this hidden service - * directory is not responsible for any more. */ + * directory is not responsible for any more. + * + * If at all possible, remove at least <b>force_remove</b> bytes of data. + */ void -rend_cache_clean_v2_descs_as_dir(time_t now) +rend_cache_clean_v2_descs_as_dir(time_t now, size_t force_remove) { digestmap_iter_t *iter; time_t cutoff = now - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW; - for (iter = digestmap_iter_init(rend_cache_v2_dir); - !digestmap_iter_done(iter); ) { - const char *key; - void *val; - rend_cache_entry_t *ent; - digestmap_iter_get(iter, &key, &val); - ent = val; - if (ent->parsed->timestamp < cutoff || - !hid_serv_responsible_for_desc_id(key)) { - char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN); - log_info(LD_REND, "Removing descriptor with ID '%s' from cache", - safe_str_client(key_base32)); - iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter); - rend_cache_entry_free(ent); - } else { - iter = digestmap_iter_next(rend_cache_v2_dir, iter); + const int LAST_SERVED_CUTOFF_STEP = 1800; + time_t last_served_cutoff = cutoff; + size_t bytes_removed = 0; + do { + for (iter = digestmap_iter_init(rend_cache_v2_dir); + !digestmap_iter_done(iter); ) { + const char *key; + void *val; + rend_cache_entry_t *ent; + digestmap_iter_get(iter, &key, &val); + ent = val; + if (ent->parsed->timestamp < cutoff || + ent->last_served < last_served_cutoff || + !hid_serv_responsible_for_desc_id(key)) { + char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN); + log_info(LD_REND, "Removing descriptor with ID '%s' from cache", + safe_str_client(key_base32)); + bytes_removed += rend_cache_entry_allocation(ent); + iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter); + rend_cache_entry_free(ent); + } else { + iter = digestmap_iter_next(rend_cache_v2_dir, iter); + } } - } + + /* In case we didn't remove enough bytes, advance the cutoff a little. */ + last_served_cutoff += LAST_SERVED_CUTOFF_STEP; + if (last_served_cutoff > now) + break; + } while (bytes_removed < force_remove); } /** Determines whether <b>a</b> is in the interval of <b>b</b> (excluded) and @@ -954,27 +952,6 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) return 1; } -/** <b>query</b> is a base32'ed service id. If it's malformed, return -1. - * Else look it up. - * - If it is found, point *desc to it, and write its length into - * *desc_len, and return 1. - * - If it is not found, return 0. - * Note: calls to rend_cache_clean or rend_cache_store may invalidate - * *desc. - */ -int -rend_cache_lookup_desc(const char *query, int version, const char **desc, - size_t *desc_len) -{ - rend_cache_entry_t *e; - int r; - r = rend_cache_lookup_entry(query,version,&e); - if (r <= 0) return r; - *desc = e->desc; - *desc_len = e->len; - return 1; -} - /** Lookup the v2 service descriptor with base32-encoded <b>desc_id</b> and * copy the pointer to it to *<b>desc</b>. Return 1 on success, 0 on * well-formed-but-not-found, and -1 on failure. @@ -997,6 +974,7 @@ rend_cache_lookup_v2_desc_as_dir(const char *desc_id, const char **desc) e = digestmap_get(rend_cache_v2_dir, desc_id_digest); if (e) { *desc = e->desc; + e->last_served = approx_time(); return 1; } return 0; @@ -1006,132 +984,19 @@ rend_cache_lookup_v2_desc_as_dir(const char *desc_id, const char **desc) * descriptor */ #define MAX_INTRO_POINTS 10 -/** Parse *desc, calculate its service id, and store it in the cache. - * If we have a newer v0 descriptor with the same ID, ignore this one. - * If we have an older descriptor with the same ID, replace it. - * If we are acting as client due to the published flag and have any v2 - * descriptor with the same ID, reject this one in order to not get - * confused with having both versions for the same service. - * - * Return -2 if it's malformed or otherwise rejected; return -1 if we - * already have a v2 descriptor here; return 0 if it's the same or older - * than one we've already got; return 1 if it's novel. - * - * The published flag tells us if we store the descriptor - * in our role as directory (1) or if we cache it as client (0). - * - * If <b>service_id</b> is non-NULL and the descriptor is not for that - * service ID, reject it. <b>service_id</b> must be specified if and - * only if <b>published</b> is 0 (we fetched this descriptor). - */ -int -rend_cache_store(const char *desc, size_t desc_len, int published, - const char *service_id) -{ - rend_cache_entry_t *e; - rend_service_descriptor_t *parsed; - char query[REND_SERVICE_ID_LEN_BASE32+1]; - char key[REND_SERVICE_ID_LEN_BASE32+2]; /* 0<query>\0 */ - time_t now; - tor_assert(rend_cache); - parsed = rend_parse_service_descriptor(desc,desc_len); - if (!parsed) { - log_warn(LD_PROTOCOL,"Couldn't parse service descriptor."); - return -2; - } - if (rend_get_service_id(parsed->pk, query)<0) { - log_warn(LD_BUG,"Couldn't compute service ID."); - rend_service_descriptor_free(parsed); - return -2; - } - if ((service_id != NULL) && strcmp(query, service_id)) { - log_warn(LD_REND, "Received service descriptor for service ID %s; " - "expected descriptor for service ID %s.", - query, safe_str(service_id)); - rend_service_descriptor_free(parsed); - return -2; - } - now = time(NULL); - if (parsed->timestamp < now-REND_CACHE_MAX_AGE-REND_CACHE_MAX_SKEW) { - log_fn(LOG_PROTOCOL_WARN, LD_REND, - "Service descriptor %s is too old.", - safe_str_client(query)); - rend_service_descriptor_free(parsed); - return -2; - } - if (parsed->timestamp > now+REND_CACHE_MAX_SKEW) { - log_fn(LOG_PROTOCOL_WARN, LD_REND, - "Service descriptor %s is too far in the future.", - safe_str_client(query)); - rend_service_descriptor_free(parsed); - return -2; - } - /* Do we have a v2 descriptor and fetched this descriptor as a client? */ - tor_snprintf(key, sizeof(key), "2%s", query); - if (!published && strmap_get_lc(rend_cache, key)) { - log_info(LD_REND, "We already have a v2 descriptor for service %s.", - safe_str_client(query)); - rend_service_descriptor_free(parsed); - return -1; - } - if (parsed->intro_nodes && - smartlist_len(parsed->intro_nodes) > MAX_INTRO_POINTS) { - log_warn(LD_REND, "Found too many introduction points on a hidden " - "service descriptor for %s. This is probably a (misguided) " - "attempt to improve reliability, but it could also be an " - "attempt to do a guard enumeration attack. Rejecting.", - safe_str_client(query)); - rend_service_descriptor_free(parsed); - return -2; - } - tor_snprintf(key, sizeof(key), "0%s", query); - e = (rend_cache_entry_t*) strmap_get_lc(rend_cache, key); - if (e && e->parsed->timestamp > parsed->timestamp) { - log_info(LD_REND,"We already have a newer service descriptor %s with the " - "same ID and version.", - safe_str_client(query)); - rend_service_descriptor_free(parsed); - return 0; - } - if (e && e->len == desc_len && tor_memeq(desc,e->desc,desc_len)) { - log_info(LD_REND,"We already have this service descriptor %s.", - safe_str_client(query)); - e->received = time(NULL); - rend_service_descriptor_free(parsed); - return 0; - } - if (!e) { - e = tor_malloc_zero(sizeof(rend_cache_entry_t)); - strmap_set_lc(rend_cache, key, e); - } else { - rend_service_descriptor_free(e->parsed); - tor_free(e->desc); - } - e->received = time(NULL); - e->parsed = parsed; - e->len = desc_len; - e->desc = tor_malloc(desc_len); - memcpy(e->desc, desc, desc_len); - - log_debug(LD_REND,"Successfully stored rend desc '%s', len %d.", - safe_str_client(query), (int)desc_len); - return 1; -} - /** Parse the v2 service descriptor(s) in <b>desc</b> and store it/them to the * local rend cache. Don't attempt to decrypt the included list of introduction * points (as we don't have a descriptor cookie for it). * * If we have a newer descriptor with the same ID, ignore this one. * If we have an older descriptor with the same ID, replace it. - * Return -2 if we are not acting as hidden service directory; - * return -1 if the descriptor(s) were not parsable; return 0 if all - * descriptors are the same or older than those we've already got; - * return a positive number for the number of novel stored descriptors. + * + * Return an appropriate rend_cache_store_status_t. */ -int +rend_cache_store_status_t rend_cache_store_v2_desc_as_dir(const char *desc) { + const or_options_t *options = get_options(); rend_service_descriptor_t *parsed; char desc_id[DIGEST_LEN]; char *intro_content; @@ -1149,11 +1014,11 @@ rend_cache_store_v2_desc_as_dir(const char *desc) /* Cannot store descs, because we are (currently) not acting as * hidden service directory. */ log_info(LD_REND, "Cannot store descs: Not acting as hs dir"); - return -2; + return RCS_NOTDIR; } while (rend_parse_v2_service_descriptor(&parsed, desc_id, &intro_content, &intro_size, &encoded_size, - &next_desc, current_desc) >= 0) { + &next_desc, current_desc, 1) >= 0) { number_parsed++; /* We don't care about the introduction points. */ tor_free(intro_content); @@ -1193,24 +1058,35 @@ rend_cache_store_v2_desc_as_dir(const char *desc) if (e && !strcmp(desc, e->desc)) { log_info(LD_REND, "We already have this service descriptor with desc " "ID %s.", safe_str(desc_id_base32)); - e->received = time(NULL); goto skip; } /* Store received descriptor. */ if (!e) { e = tor_malloc_zero(sizeof(rend_cache_entry_t)); digestmap_set(rend_cache_v2_dir, desc_id, e); + /* Treat something just uploaded as having been served a little + * while ago, so that flooding with new descriptors doesn't help + * too much. + */ + e->last_served = approx_time() - 3600; } else { + rend_cache_decrement_allocation(rend_cache_entry_allocation(e)); rend_service_descriptor_free(e->parsed); tor_free(e->desc); } - e->received = time(NULL); e->parsed = parsed; e->desc = tor_strndup(current_desc, encoded_size); e->len = encoded_size; + rend_cache_increment_allocation(rend_cache_entry_allocation(e)); log_info(LD_REND, "Successfully stored service descriptor with desc ID " "'%s' and len %d.", safe_str(desc_id_base32), (int)encoded_size); + + /* Statistics: Note down this potentially new HS. */ + if (options->HiddenServiceStatistics) { + rep_hist_stored_maybe_new_hs(e->parsed->pk); + } + number_stored++; goto advance; skip: @@ -1225,11 +1101,11 @@ rend_cache_store_v2_desc_as_dir(const char *desc) } if (!number_parsed) { log_info(LD_REND, "Could not parse any descriptor."); - return -1; + return RCS_BADDESC; } log_info(LD_REND, "Parsed %d and added %d descriptor%s.", number_parsed, number_stored, number_stored != 1 ? "s" : ""); - return number_stored; + return RCS_OKAY; } /** Parse the v2 service descriptor in <b>desc</b>, decrypt the included list @@ -1239,16 +1115,17 @@ rend_cache_store_v2_desc_as_dir(const char *desc) * * If we have a newer v2 descriptor with the same ID, ignore this one. * If we have an older descriptor with the same ID, replace it. - * If we have any v0 descriptor with the same ID, reject this one in order - * to not get confused with having both versions for the same service. * If the descriptor's service ID does not match * <b>rend_query</b>-\>onion_address, reject it. - * Return -2 if it's malformed or otherwise rejected; return -1 if we - * already have a v0 descriptor here; return 0 if it's the same or older - * than one we've already got; return 1 if it's novel. + * + * If the descriptor's descriptor ID doesn't match <b>desc_id_base32</b>, + * reject it. + * + * Return an appropriate rend_cache_store_status_t. */ -int +rend_cache_store_status_t rend_cache_store_v2_desc_as_client(const char *desc, + const char *desc_id_base32, const rend_data_t *rend_query) { /*XXXX this seems to have a bit of duplicate code with @@ -1275,31 +1152,43 @@ rend_cache_store_v2_desc_as_client(const char *desc, time_t now = time(NULL); char key[REND_SERVICE_ID_LEN_BASE32+2]; char service_id[REND_SERVICE_ID_LEN_BASE32+1]; + char want_desc_id[DIGEST_LEN]; rend_cache_entry_t *e; - int retval; + rend_cache_store_status_t retval = RCS_BADDESC; tor_assert(rend_cache); tor_assert(desc); + tor_assert(desc_id_base32); + memset(want_desc_id, 0, sizeof(want_desc_id)); + if (base32_decode(want_desc_id, sizeof(want_desc_id), + desc_id_base32, strlen(desc_id_base32)) != 0) { + log_warn(LD_BUG, "Couldn't decode base32 %s for descriptor id.", + escaped_safe_str_client(desc_id_base32)); + goto err; + } /* Parse the descriptor. */ if (rend_parse_v2_service_descriptor(&parsed, desc_id, &intro_content, &intro_size, &encoded_size, - &next_desc, desc) < 0) { + &next_desc, desc, 0) < 0) { log_warn(LD_REND, "Could not parse descriptor."); - retval = -2; goto err; } /* Compute service ID from public key. */ if (rend_get_service_id(parsed->pk, service_id)<0) { log_warn(LD_REND, "Couldn't compute service ID."); - retval = -2; goto err; } if (strcmp(rend_query->onion_address, service_id)) { log_warn(LD_REND, "Received service descriptor for service ID %s; " "expected descriptor for service ID %s.", service_id, safe_str(rend_query->onion_address)); - retval = -2; goto err; } + if (tor_memneq(desc_id, want_desc_id, DIGEST_LEN)) { + log_warn(LD_REND, "Received service descriptor for %s with incorrect " + "descriptor ID.", service_id); + goto err; + } + /* Decode/decrypt introduction points. */ if (intro_content && intro_size > 0) { int n_intro_points; @@ -1329,7 +1218,6 @@ rend_cache_store_v2_desc_as_client(const char *desc, log_warn(LD_REND, "Failed to parse introduction points. Either the " "service has published a corrupt descriptor or you have " "provided invalid authorization data."); - retval = -2; goto err; } else if (n_intro_points > MAX_INTRO_POINTS) { log_warn(LD_REND, "Found too many introduction points on a hidden " @@ -1337,7 +1225,7 @@ rend_cache_store_v2_desc_as_client(const char *desc, "attempt to improve reliability, but it could also be an " "attempt to do a guard enumeration attack. Rejecting.", safe_str_client(rend_query->onion_address)); - retval = -2; + goto err; } } else { @@ -1350,22 +1238,12 @@ rend_cache_store_v2_desc_as_client(const char *desc, if (parsed->timestamp < now - REND_CACHE_MAX_AGE-REND_CACHE_MAX_SKEW) { log_warn(LD_REND, "Service descriptor with service ID %s is too old.", safe_str_client(service_id)); - retval = -2; goto err; } /* Is descriptor too far in the future? */ if (parsed->timestamp > now + REND_CACHE_MAX_SKEW) { log_warn(LD_REND, "Service descriptor with service ID %s is too far in " "the future.", safe_str_client(service_id)); - retval = -2; - goto err; - } - /* Do we have a v0 descriptor? */ - tor_snprintf(key, sizeof(key), "0%s", service_id); - if (strmap_get_lc(rend_cache, key)) { - log_info(LD_REND, "We already have a v0 descriptor for service ID %s.", - safe_str_client(service_id)); - retval = -1; goto err; } /* Do we already have a newer descriptor? */ @@ -1375,32 +1253,33 @@ rend_cache_store_v2_desc_as_client(const char *desc, log_info(LD_REND, "We already have a newer service descriptor for " "service ID %s with the same desc ID and version.", safe_str_client(service_id)); - retval = 0; - goto err; + goto okay; } /* Do we already have this descriptor? */ if (e && !strcmp(desc, e->desc)) { log_info(LD_REND,"We already have this service descriptor %s.", safe_str_client(service_id)); - e->received = time(NULL); - retval = 0; - goto err; + goto okay; } if (!e) { e = tor_malloc_zero(sizeof(rend_cache_entry_t)); strmap_set_lc(rend_cache, key, e); } else { + rend_cache_decrement_allocation(rend_cache_entry_allocation(e)); rend_service_descriptor_free(e->parsed); tor_free(e->desc); } - e->received = time(NULL); e->parsed = parsed; e->desc = tor_malloc_zero(encoded_size + 1); strlcpy(e->desc, desc, encoded_size + 1); e->len = encoded_size; + rend_cache_increment_allocation(rend_cache_entry_allocation(e)); log_debug(LD_REND,"Successfully stored rend desc '%s', len %d.", safe_str_client(service_id), (int)encoded_size); - return 1; + return RCS_OKAY; + + okay: + retval = RCS_OKAY; err: rend_service_descriptor_free(parsed); diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index f476593d2b..8396cc3551 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -26,8 +26,6 @@ void rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, const uint8_t *payload); void rend_service_descriptor_free(rend_service_descriptor_t *desc); -rend_service_descriptor_t *rend_parse_service_descriptor(const char *str, - size_t len); int rend_get_service_id(crypto_pk_t *pk, char *out); void rend_encoded_v2_service_descriptor_free( rend_encoded_v2_service_descriptor_t *desc); @@ -35,20 +33,24 @@ void rend_intro_point_free(rend_intro_point_t *intro); void rend_cache_init(void); void rend_cache_clean(time_t now); -void rend_cache_clean_v2_descs_as_dir(time_t now); +void rend_cache_clean_v2_descs_as_dir(time_t now, size_t min_to_remove); void rend_cache_purge(void); void rend_cache_free_all(void); int rend_valid_service_id(const char *query); -int rend_cache_lookup_desc(const char *query, int version, const char **desc, - size_t *desc_len); int rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **entry_out); int rend_cache_lookup_v2_desc_as_dir(const char *query, const char **desc); -int rend_cache_store(const char *desc, size_t desc_len, int published, - const char *service_id); -int rend_cache_store_v2_desc_as_client(const char *desc, +/** Return value from rend_cache_store_v2_desc_as_{dir,client}. */ +typedef enum { + RCS_NOTDIR = -2, /**< We're not a directory */ + RCS_BADDESC = -1, /**< This descriptor is no good. */ + RCS_OKAY = 0 /**< All worked as expected */ +} rend_cache_store_status_t; + +rend_cache_store_status_t rend_cache_store_v2_desc_as_dir(const char *desc); +rend_cache_store_status_t rend_cache_store_v2_desc_as_client(const char *desc, + const char *desc_id_base32, const rend_data_t *rend_query); -int rend_cache_store_v2_desc_as_dir(const char *desc); int rend_encode_v2_descriptors(smartlist_t *descs_out, rend_service_descriptor_t *desc, time_t now, uint8_t period, rend_auth_type_t auth_type, @@ -61,6 +63,7 @@ int rend_id_is_in_interval(const char *a, const char *b, const char *c); void rend_get_descriptor_id_bytes(char *descriptor_id_out, const char *service_id, const char *secret_id_part); +size_t rend_cache_get_total_allocation(void); #endif diff --git a/src/or/rendmid.c b/src/or/rendmid.c index 0a005a6312..2451acb514 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -94,7 +94,7 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request, /* Close any other intro circuits with the same pk. */ c = NULL; - while ((c = circuit_get_intro_point(pk_digest))) { + while ((c = circuit_get_intro_point((const uint8_t *)pk_digest))) { log_info(LD_REND, "Replacing old circuit for service %s", safe_str(serviceid)); circuit_mark_for_close(TO_CIRCUIT(c), END_CIRC_REASON_FINISHED); @@ -111,7 +111,7 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request, /* Now, set up this circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT); - memcpy(circ->rend_token, pk_digest, DIGEST_LEN); + circuit_set_intro_point_digest(circ, (uint8_t *)pk_digest); log_info(LD_REND, "Established introduction point on circuit %u for service %s", @@ -179,7 +179,7 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request, (char*)request, REND_SERVICE_ID_LEN); /* The first 20 bytes are all we look at: they have a hash of Bob's PK. */ - intro_circ = circuit_get_intro_point((char*)request); + intro_circ = circuit_get_intro_point((const uint8_t*)request); if (!intro_circ) { log_info(LD_REND, "No intro circ found for INTRODUCE1 cell (%s) from circuit %u; " @@ -202,7 +202,7 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request, "Unable to send INTRODUCE2 cell to Tor client."); goto err; } - /* And sent an ack down Alice's circuit. Empty body means succeeded. */ + /* And send an ack down Alice's circuit. Empty body means succeeded. */ if (relay_send_command_from_edge(0,TO_CIRCUIT(circ), RELAY_COMMAND_INTRODUCE_ACK, NULL,0,NULL)) { @@ -213,7 +213,7 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request, return 0; err: - /* Send the client an NACK */ + /* Send the client a NACK */ nak_body[0] = 1; if (relay_send_command_from_edge(0,TO_CIRCUIT(circ), RELAY_COMMAND_INTRODUCE_ACK, @@ -238,18 +238,26 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, log_info(LD_REND, "Received an ESTABLISH_RENDEZVOUS request on circuit %u", (unsigned)circ->p_circ_id); - if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_OR) { log_warn(LD_PROTOCOL, - "Tried to establish rendezvous on non-OR or non-edge circuit."); + "Tried to establish rendezvous on non-OR circuit with purpose %s", + circuit_purpose_to_string(circ->base_.purpose)); + goto err; + } + + if (circ->base_.n_chan) { + log_warn(LD_PROTOCOL, + "Tried to establish rendezvous on non-edge circuit"); goto err; } if (request_len != REND_COOKIE_LEN) { - log_warn(LD_PROTOCOL, "Invalid length on ESTABLISH_RENDEZVOUS."); + log_fn(LOG_PROTOCOL_WARN, + LD_PROTOCOL, "Invalid length on ESTABLISH_RENDEZVOUS."); goto err; } - if (circuit_get_rendezvous((char*)request)) { + if (circuit_get_rendezvous(request)) { log_warn(LD_PROTOCOL, "Duplicate rendezvous cookie in ESTABLISH_RENDEZVOUS."); goto err; @@ -265,7 +273,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, } circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_POINT_WAITING); - memcpy(circ->rend_token, request, REND_COOKIE_LEN); + circuit_set_rendezvous_cookie(circ, request); base16_encode(hexid,9,(char*)request,4); @@ -287,6 +295,7 @@ int rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, size_t request_len) { + const or_options_t *options = get_options(); or_circuit_t *rend_circ; char hexid[9]; int reason = END_CIRC_REASON_INTERNAL; @@ -313,7 +322,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, "Got request for rendezvous from circuit %u to cookie %s.", (unsigned)circ->p_circ_id, hexid); - rend_circ = circuit_get_rendezvous((char*)request); + rend_circ = circuit_get_rendezvous(request); if (!rend_circ) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Rejecting RENDEZVOUS1 cell with unrecognized rendezvous cookie %s.", @@ -322,6 +331,12 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, goto err; } + /* Statistics: Mark this circuit as an RP circuit so that we collect + stats from it. */ + if (options->HiddenServiceStatistics) { + circ->circuit_carries_hs_traffic_stats = 1; + } + /* Send the RENDEZVOUS2 cell to Alice. */ if (relay_send_command_from_edge(0, TO_CIRCUIT(rend_circ), RELAY_COMMAND_RENDEZVOUS2, @@ -341,7 +356,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_ESTABLISHED); circuit_change_purpose(TO_CIRCUIT(rend_circ), CIRCUIT_PURPOSE_REND_ESTABLISHED); - memset(circ->rend_token, 0, REND_COOKIE_LEN); + circuit_set_rendezvous_cookie(circ, NULL); rend_circ->rend_splice = circ; circ->rend_splice = rend_circ; diff --git a/src/or/rendmid.h b/src/or/rendmid.h index 310276ac96..6bd691a740 100644 --- a/src/or/rendmid.h +++ b/src/or/rendmid.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 436f2f4b69..111b369b1c 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,11 +10,13 @@ #define RENDSERVICE_PRIVATE #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" #include "directory.h" +#include "main.h" #include "networkstatus.h" #include "nodelist.h" #include "rendclient.h" @@ -64,9 +66,16 @@ static ssize_t rend_service_parse_intro_for_v3( * a real port on some IP. */ typedef struct rend_service_port_config_t { + /* The incoming HS virtual port we're mapping */ uint16_t virtual_port; + /* Is this an AF_UNIX port? */ + unsigned int is_unix_addr:1; + /* The outgoing TCP port to use, if !is_unix_addr */ uint16_t real_port; + /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ tor_addr_t real_addr; + /* The socket path to connect to, if is_unix_addr */ + char unix_addr[FLEXIBLE_ARRAY_MEMBER]; } rend_service_port_config_t; /** Try to maintain this many intro points per service by default. */ @@ -81,7 +90,7 @@ typedef struct rend_service_port_config_t { #define MAX_INTRO_CIRCS_PER_PERIOD 10 /** How many times will a hidden service operator attempt to connect to * a requested rendezvous point before giving up? */ -#define MAX_REND_FAILURES 30 +#define MAX_REND_FAILURES 1 /** How many seconds should we spend trying to connect to a requested * rendezvous point before giving up? */ #define MAX_REND_TIMEOUT 30 @@ -94,6 +103,8 @@ typedef struct rend_service_port_config_t { typedef struct rend_service_t { /* Fields specified in config file */ char *directory; /**< where in the filesystem it stores it */ + int dir_group_readable; /**< if 1, allow group read + permissions on directory */ smartlist_t *ports; /**< List of rend_service_port_config_t */ rend_auth_type_t auth_type; /**< Client authorization type or 0 if no client * authorization is performed. */ @@ -125,6 +136,9 @@ typedef struct rend_service_t { * when they do, this keeps us from launching multiple simultaneous attempts * to connect to the same rend point. */ replaycache_t *accepted_intro_dh_parts; + /** If true, we don't close circuits for making requests to unsupported + * ports. */ + int allow_unknown_ports; } rend_service_t; /** A list of rend_service_t's for services run on this OP. @@ -272,16 +286,48 @@ rend_add_service(rend_service_t *service) service->directory); for (i = 0; i < smartlist_len(service->ports); ++i) { p = smartlist_get(service->ports, i); - log_debug(LD_REND,"Service maps port %d to %s", - p->virtual_port, fmt_addrport(&p->real_addr, p->real_port)); + if (!(p->is_unix_addr)) { + log_debug(LD_REND, + "Service maps port %d to %s", + p->virtual_port, + fmt_addrport(&p->real_addr, p->real_port)); + } else { +#ifdef HAVE_SYS_UN_H + log_debug(LD_REND, + "Service maps port %d to socket at \"%s\"", + p->virtual_port, p->unix_addr); +#else + log_debug(LD_REND, + "Service maps port %d to an AF_UNIX socket, but we " + "have no AF_UNIX support on this platform. This is " + "probably a bug.", + p->virtual_port); +#endif /* defined(HAVE_SYS_UN_H) */ + } } } } +/** Return a new rend_service_port_config_t with its path set to + * <b>socket_path</b> or empty if <b>socket_path</b> is NULL */ +static rend_service_port_config_t * +rend_service_port_config_new(const char *socket_path) +{ + if (!socket_path) + return tor_malloc_zero(sizeof(rend_service_port_config_t) + 1); + + const size_t pathlen = strlen(socket_path) + 1; + rend_service_port_config_t *conf = + tor_malloc_zero(sizeof(rend_service_port_config_t) + pathlen); + memcpy(conf->unix_addr, socket_path, pathlen); + conf->is_unix_addr = 1; + return conf; +} + /** Parses a real-port to virtual-port mapping and returns a new * rend_service_port_config_t. * - * The format is: VirtualPort (IP|RealPort|IP:RealPort)? + * The format is: VirtualPort (IP|RealPort|IP:RealPort|'socket':path)? * * IP defaults to 127.0.0.1; RealPort defaults to VirtualPort. */ @@ -290,11 +336,13 @@ parse_port_config(const char *string) { smartlist_t *sl; int virtport; - int realport; + int realport = 0; uint16_t p; tor_addr_t addr; const char *addrport; rend_service_port_config_t *result = NULL; + unsigned int is_unix_addr = 0; + char *socket_path = NULL; sl = smartlist_new(); smartlist_split_string(sl, string, " ", @@ -316,8 +364,21 @@ parse_port_config(const char *string) realport = virtport; tor_addr_from_ipv4h(&addr, 0x7F000001u); /* 127.0.0.1 */ } else { + int ret; + addrport = smartlist_get(sl,1); - if (strchr(addrport, ':') || strchr(addrport, '.')) { + ret = config_parse_unix_port(addrport, &socket_path); + if (ret < 0 && ret != -ENOENT) { + if (ret == -EINVAL) { + log_warn(LD_CONFIG, + "Empty socket path in hidden service port configuration."); + } + goto err; + } + if (socket_path) { + is_unix_addr = 1; + } else if (strchr(addrport, ':') || strchr(addrport, '.')) { + /* else try it as an IP:port pair if it has a : or . in it */ if (tor_addr_port_lookup(addrport, &addr, &p)<0) { log_warn(LD_CONFIG,"Unparseable address in hidden service port " "configuration."); @@ -336,13 +397,21 @@ parse_port_config(const char *string) } } - result = tor_malloc(sizeof(rend_service_port_config_t)); + /* Allow room for unix_addr */ + result = rend_service_port_config_new(socket_path); result->virtual_port = virtport; - result->real_port = realport; - tor_addr_copy(&result->real_addr, &addr); + result->is_unix_addr = is_unix_addr; + if (!is_unix_addr) { + result->real_port = realport; + tor_addr_copy(&result->real_addr, &addr); + result->unix_addr[0] = '\0'; + } + err: SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); + if (socket_path) tor_free(socket_path); + return result; } @@ -358,6 +427,7 @@ rend_config_services(const or_options_t *options, int validate_only) rend_service_t *service = NULL; rend_service_port_config_t *portcfg; smartlist_t *old_service_list = NULL; + int ok = 0; if (!validate_only) { old_service_list = rend_service_list; @@ -368,87 +438,114 @@ rend_config_services(const or_options_t *options, int validate_only) if (!strcasecmp(line->key, "HiddenServiceDir")) { if (service) { /* register the one we just finished parsing */ if (validate_only) - rend_service_free(service); - else - rend_add_service(service); - } - service = tor_malloc_zero(sizeof(rend_service_t)); - service->directory = tor_strdup(line->value); - service->ports = smartlist_new(); - service->intro_period_started = time(NULL); - service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT; - continue; - } - if (!service) { - log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", - line->key); - rend_service_free(service); - return -1; - } - if (!strcasecmp(line->key, "HiddenServicePort")) { - portcfg = parse_port_config(line->value); - if (!portcfg) { - rend_service_free(service); - return -1; - } - smartlist_add(service->ports, portcfg); - } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { - /* Parse auth type and comma-separated list of client names and add a - * rend_authorized_client_t for each client to the service's list - * of authorized clients. */ - smartlist_t *type_names_split, *clients; - const char *authname; - int num_clients; - if (service->auth_type != REND_NO_AUTH) { - log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " - "lines for a single service."); - rend_service_free(service); - return -1; - } - type_names_split = smartlist_new(); - smartlist_split_string(type_names_split, line->value, " ", 0, 2); - if (smartlist_len(type_names_split) < 1) { - log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " - "should have been prevented when parsing the " - "configuration."); - smartlist_free(type_names_split); - rend_service_free(service); - return -1; - } - authname = smartlist_get(type_names_split, 0); - if (!strcasecmp(authname, "basic")) { - service->auth_type = REND_BASIC_AUTH; - } else if (!strcasecmp(authname, "stealth")) { - service->auth_type = REND_STEALTH_AUTH; - } else { - log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " - "unrecognized auth-type '%s'. Only 'basic' or 'stealth' " - "are recognized.", - (char *) smartlist_get(type_names_split, 0)); - SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); - smartlist_free(type_names_split); - rend_service_free(service); - return -1; - } - service->clients = smartlist_new(); - if (smartlist_len(type_names_split) < 2) { - log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " - "auth-type '%s', but no client names.", - service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); - SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); - smartlist_free(type_names_split); - continue; - } - clients = smartlist_new(); - smartlist_split_string(clients, smartlist_get(type_names_split, 1), - ",", SPLIT_SKIP_SPACE, 0); - SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); - smartlist_free(type_names_split); - /* Remove duplicate client names. */ - num_clients = smartlist_len(clients); - smartlist_sort_strings(clients); - smartlist_uniq_strings(clients); - if (smartlist_len(clients) < num_clients) { + rend_service_free(service); + else + rend_add_service(service); + } + service = tor_malloc_zero(sizeof(rend_service_t)); + service->directory = tor_strdup(line->value); + service->ports = smartlist_new(); + service->intro_period_started = time(NULL); + service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT; + continue; + } + if (!service) { + log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", + line->key); + rend_service_free(service); + return -1; + } + if (!strcasecmp(line->key, "HiddenServicePort")) { + portcfg = parse_port_config(line->value); + if (!portcfg) { + rend_service_free(service); + return -1; + } + smartlist_add(service->ports, portcfg); + } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { + service->allow_unknown_ports = (int)tor_parse_long(line->value, + 10, 0, 1, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, + "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s", + line->value); + rend_service_free(service); + return -1; + } + log_info(LD_CONFIG, + "HiddenServiceAllowUnknownPorts=%d for %s", + (int)service->allow_unknown_ports, service->directory); + } else if (!strcasecmp(line->key, + "HiddenServiceDirGroupReadable")) { + service->dir_group_readable = (int)tor_parse_long(line->value, + 10, 0, 1, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, + "HiddenServiceDirGroupReadable should be 0 or 1, not %s", + line->value); + rend_service_free(service); + return -1; + } + log_info(LD_CONFIG, + "HiddenServiceDirGroupReadable=%d for %s", + service->dir_group_readable, service->directory); + } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + /* Parse auth type and comma-separated list of client names and add a + * rend_authorized_client_t for each client to the service's list + * of authorized clients. */ + smartlist_t *type_names_split, *clients; + const char *authname; + int num_clients; + if (service->auth_type != REND_NO_AUTH) { + log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " + "lines for a single service."); + rend_service_free(service); + return -1; + } + type_names_split = smartlist_new(); + smartlist_split_string(type_names_split, line->value, " ", 0, 2); + if (smartlist_len(type_names_split) < 1) { + log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " + "should have been prevented when parsing the " + "configuration."); + smartlist_free(type_names_split); + rend_service_free(service); + return -1; + } + authname = smartlist_get(type_names_split, 0); + if (!strcasecmp(authname, "basic")) { + service->auth_type = REND_BASIC_AUTH; + } else if (!strcasecmp(authname, "stealth")) { + service->auth_type = REND_STEALTH_AUTH; + } else { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " + "unrecognized auth-type '%s'. Only 'basic' or 'stealth' " + "are recognized.", + (char *) smartlist_get(type_names_split, 0)); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + rend_service_free(service); + return -1; + } + service->clients = smartlist_new(); + if (smartlist_len(type_names_split) < 2) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " + "auth-type '%s', but no client names.", + service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + continue; + } + clients = smartlist_new(); + smartlist_split_string(clients, smartlist_get(type_names_split, 1), + ",", SPLIT_SKIP_SPACE, 0); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + /* Remove duplicate client names. */ + num_clients = smartlist_len(clients); + smartlist_sort_strings(clients); + smartlist_uniq_strings(clients); + if (smartlist_len(clients) < num_clients) { log_info(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d " "duplicate client name(s); removing.", num_clients - smartlist_len(clients)); @@ -512,10 +609,21 @@ rend_config_services(const or_options_t *options, int validate_only) } } if (service) { - if (validate_only) + cpd_check_t check_opts = CPD_CHECK_MODE_ONLY|CPD_CHECK; + if (service->dir_group_readable) { + check_opts |= CPD_GROUP_READ; + } + + if (check_private_dir(service->directory, check_opts, options->User) < 0) { + rend_service_free(service); + return -1; + } + + if (validate_only) { rend_service_free(service); - else + } else { rend_add_service(service); + } } /* If this is a reload and there were hidden services configured before, @@ -523,7 +631,6 @@ rend_config_services(const or_options_t *options, int validate_only) * other ones. */ if (old_service_list && !validate_only) { smartlist_t *surviving_services = smartlist_new(); - circuit_t *circ; /* Copy introduction points to new services. */ /* XXXX This is O(n^2), but it's only called on reconfigure, so it's @@ -543,7 +650,7 @@ rend_config_services(const or_options_t *options, int validate_only) /* XXXX it would be nicer if we had a nicer abstraction to use here, * so we could just iterate over the list of services to close, but * once again, this isn't critical-path code. */ - for (circ = circuit_get_global_list_(); circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN && (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -568,6 +675,7 @@ rend_config_services(const or_options_t *options, int validate_only) /* XXXX Is there another reason we should use here? */ } } + SMARTLIST_FOREACH_END(circ); smartlist_free(surviving_services); SMARTLIST_FOREACH(old_service_list, rend_service_t *, ptr, rend_service_free(ptr)); @@ -655,6 +763,35 @@ rend_service_load_all_keys(void) return 0; } +/** Add to <b>lst</b> every filename used by <b>s</b>. */ +static void +rend_service_add_filenames_to_list(smartlist_t *lst, const rend_service_t *s) +{ + tor_assert(lst); + tor_assert(s); + smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"private_key", + s->directory); + smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"hostname", + s->directory); + smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"client_keys", + s->directory); +} + +/** Add to <b>open_lst</b> every filename used by a configured hidden service, + * and to <b>stat_lst</b> every directory used by a configured hidden + * service */ +void +rend_services_add_filenames_to_lists(smartlist_t *open_lst, + smartlist_t *stat_lst) +{ + if (!rend_service_list) + return; + SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) { + rend_service_add_filenames_to_list(open_lst, s); + smartlist_add(stat_lst, tor_strdup(s->directory)); + } SMARTLIST_FOREACH_END(s); +} + /** Load and/or generate private keys for the hidden service <b>s</b>, * possibly including keys for client authorization. Return 0 on success, -1 * on failure. */ @@ -663,10 +800,23 @@ rend_service_load_keys(rend_service_t *s) { char fname[512]; char buf[128]; + cpd_check_t check_opts = CPD_CREATE; + if (s->dir_group_readable) { + check_opts |= CPD_GROUP_READ; + } /* Check/create directory */ - if (check_private_dir(s->directory, CPD_CREATE, get_options()->User) < 0) + if (check_private_dir(s->directory, check_opts, get_options()->User) < 0) { return -1; + } +#ifndef _WIN32 + if (s->dir_group_readable) { + /* Only new dirs created get new opts, also enforce group read. */ + if (chmod(s->directory, 0750)) { + log_warn(LD_FS,"Unable to make %s group-readable.", s->directory); + } + } +#endif /* Load key */ if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) || @@ -676,7 +826,7 @@ rend_service_load_keys(rend_service_t *s) s->directory); return -1; } - s->private_key = init_key_from_file(fname, 1, LOG_ERR); + s->private_key = init_key_from_file(fname, 1, LOG_ERR, 0); if (!s->private_key) return -1; @@ -703,6 +853,15 @@ rend_service_load_keys(rend_service_t *s) memwipe(buf, 0, sizeof(buf)); return -1; } +#ifndef _WIN32 + if (s->dir_group_readable) { + /* Also verify hostname file created with group read. */ + if (chmod(fname, 0640)) + log_warn(LD_FS,"Unable to make hidden hostname file %s group-readable.", + fname); + } +#endif + memwipe(buf, 0, sizeof(buf)); /* If client authorization is configured, load or generate keys. */ @@ -939,11 +1098,13 @@ rend_service_requires_uptime(rend_service_t *service) return 0; } -/** Check client authorization of a given <b>descriptor_cookie</b> for - * <b>service</b>. Return 1 for success and 0 for failure. */ +/** Check client authorization of a given <b>descriptor_cookie</b> of + * length <b>cookie_len</b> for <b>service</b>. Return 1 for success + * and 0 for failure. */ static int rend_check_authorization(rend_service_t *service, - const char *descriptor_cookie) + const char *descriptor_cookie, + size_t cookie_len) { rend_authorized_client_t *auth_client = NULL; tor_assert(service); @@ -954,6 +1115,13 @@ rend_check_authorization(rend_service_t *service, return 0; } + if (cookie_len != REND_DESC_COOKIE_LEN) { + log_info(LD_REND, "Descriptor cookie is %lu bytes, but we expected " + "%lu bytes. Dropping cell.", + (unsigned long)cookie_len, (unsigned long)REND_DESC_COOKIE_LEN); + return 0; + } + /* Look up client authorization by descriptor cookie. */ SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, client, { if (tor_memeq(client->descriptor_cookie, descriptor_cookie, @@ -973,8 +1141,8 @@ rend_check_authorization(rend_service_t *service, } /* Allow the request. */ - log_debug(LD_REND, "Client %s authorized for service %s.", - auth_client->client_name, service->service_id); + log_info(LD_REND, "Client %s authorized for service %s.", + auth_client->client_name, service->service_id); return 1; } @@ -1208,7 +1376,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, /* check for replay of PK-encrypted portion. */ replay = replaycache_add_test_and_elapsed( intro_point->accepted_intro_rsa_parts, - parsed_req->ciphertext, (int)parsed_req->ciphertext_len, + parsed_req->ciphertext, parsed_req->ciphertext_len, &elapsed); if (replay) { @@ -1300,7 +1468,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, if (service->clients) { if (parsed_req->version == 3 && parsed_req->u.v3.auth_len > 0) { if (rend_check_authorization(service, - (const char*)parsed_req->u.v3.auth_data)) { + (const char*)parsed_req->u.v3.auth_data, + parsed_req->u.v3.auth_len)) { log_info(LD_REND, "Authorization data in INTRODUCE2 cell are valid."); } else { log_info(LD_REND, "The authorization data that are contained in " @@ -1416,10 +1585,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, memwipe(hexcookie, 0, sizeof(hexcookie)); /* Free the parsed cell */ - if (parsed_req) { - rend_service_free_intro(parsed_req); - parsed_req = NULL; - } + rend_service_free_intro(parsed_req); /* Free rp if we must */ if (need_rp_free) extend_info_free(rp); @@ -1449,8 +1615,7 @@ find_rp_for_intro(const rend_intro_cell_t *intro, } if (intro->version == 0 || intro->version == 1) { - if (intro->version == 1) rp_nickname = (const char *)(intro->u.v1.rp); - else rp_nickname = (const char *)(intro->u.v0.rp); + rp_nickname = (const char *)(intro->u.v0_v1.rp); node = node_get_by_nickname(rp_nickname, 0); if (!node) { @@ -1502,27 +1667,6 @@ find_rp_for_intro(const rend_intro_cell_t *intro, return rp; } -/** Remove unnecessary parts from a rend_intro_cell_t - the ciphertext if - * already decrypted, the plaintext too if already parsed - */ - -void -rend_service_compact_intro(rend_intro_cell_t *request) -{ - if (!request) return; - - if ((request->plaintext && request->plaintext_len > 0) || - request->parsed) { - tor_free(request->ciphertext); - request->ciphertext_len = 0; - } - - if (request->parsed) { - tor_free(request->plaintext); - request->plaintext_len = 0; - } -} - /** Free a parsed INTRODUCE1 or INTRODUCE2 cell that was allocated by * rend_service_parse_intro(). */ @@ -1530,7 +1674,6 @@ void rend_service_free_intro(rend_intro_cell_t *request) { if (!request) { - log_info(LD_BUG, "rend_service_free_intro() called with NULL request!"); return; } @@ -1639,8 +1782,9 @@ rend_service_begin_parse_intro(const uint8_t *request, goto done; err: - if (rv) rend_service_free_intro(rv); + rend_service_free_intro(rv); rv = NULL; + if (err_msg_out && !err_msg) { tor_asprintf(&err_msg, "unknown INTRODUCE%d error", @@ -1720,11 +1864,7 @@ rend_service_parse_intro_for_v0_or_v1( goto err; } - if (intro->version == 1) { - memcpy(intro->u.v1.rp, rp_nickname, endptr - rp_nickname + 1); - } else { - memcpy(intro->u.v0.rp, rp_nickname, endptr - rp_nickname + 1); - } + memcpy(intro->u.v0_v1.rp, rp_nickname, endptr - rp_nickname + 1); return ver_specific_len; @@ -1748,7 +1888,7 @@ rend_service_parse_intro_for_v2( /* * We accept version 3 too so that the v3 parser can call this with - * and adjusted buffer for the latter part of a v3 cell, which is + * an adjusted buffer for the latter part of a v3 cell, which is * identical to a v2 cell. */ if (!(intro->version == 2 || @@ -1986,7 +2126,7 @@ rend_service_decrypt_intro( char service_id[REND_SERVICE_ID_LEN_BASE32+1]; ssize_t key_len; uint8_t buf[RELAY_PAYLOAD_SIZE]; - int result, status = 0; + int result, status = -1; if (!intro || !key) { if (err_msg_out) { @@ -2065,15 +2205,16 @@ rend_service_decrypt_intro( intro->plaintext = tor_malloc(intro->plaintext_len); memcpy(intro->plaintext, buf, intro->plaintext_len); + status = 0; + goto done; err: if (err_msg_out && !err_msg) { tor_asprintf(&err_msg, "unknown INTRODUCE%d error decrypting encrypted part", - (int)(intro->type)); + intro ? (int)(intro->type) : -1); } - if (status >= 0) status = -1; done: if (err_msg_out) *err_msg_out = err_msg; @@ -2100,7 +2241,7 @@ rend_service_parse_intro_plaintext( char *err_msg = NULL; ssize_t ver_specific_len, ver_invariant_len; uint8_t version; - int status = 0; + int status = -1; if (!intro) { if (err_msg_out) { @@ -2159,6 +2300,7 @@ rend_service_parse_intro_plaintext( (int)(intro->type), (long)(intro->plaintext_len)); status = -6; + goto err; } else { memcpy(intro->rc, intro->plaintext + ver_specific_len, @@ -2171,15 +2313,15 @@ rend_service_parse_intro_plaintext( /* Flag it as being fully parsed */ intro->parsed = 1; + status = 0; goto done; err: if (err_msg_out && !err_msg) { tor_asprintf(&err_msg, "unknown INTRODUCE%d error parsing encrypted part", - (int)(intro->type)); + intro ? (int)(intro->type) : -1); } - if (status >= 0) status = -1; done: if (err_msg_out) *err_msg_out = err_msg; @@ -2385,8 +2527,7 @@ static int count_established_intro_points(const char *query) { int num_ipos = 0; - circuit_t *circ; - for (circ = circuit_get_global_list_(); circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN && (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -2397,6 +2538,7 @@ count_established_intro_points(const char *query) num_ipos++; } } + SMARTLIST_FOREACH_END(circ); return num_ipos; } @@ -3030,15 +3172,20 @@ rend_services_introduce(void) int intro_point_set_changed, prev_intro_nodes; unsigned int n_intro_points_unexpired; unsigned int n_intro_points_to_open; - smartlist_t *intro_nodes; time_t now; const or_options_t *options = get_options(); + /* List of nodes we need to _exclude_ when choosing a new node to establish + * an intro point to. */ + smartlist_t *exclude_nodes; + + if (!have_completed_a_circuit()) + return; - intro_nodes = smartlist_new(); + exclude_nodes = smartlist_new(); now = time(NULL); for (i=0; i < smartlist_len(rend_service_list); ++i) { - smartlist_clear(intro_nodes); + smartlist_clear(exclude_nodes); service = smartlist_get(rend_service_list, i); tor_assert(service); @@ -3137,8 +3284,10 @@ rend_services_introduce(void) if (intro != NULL && intro->time_expiring == -1) ++n_intro_points_unexpired; + /* Add the valid node to the exclusion list so we don't try to establish + * an introduction point to it again. */ if (node) - smartlist_add(intro_nodes, (void*)node); + smartlist_add(exclude_nodes, (void*)node); } SMARTLIST_FOREACH_END(intro); if (!intro_point_set_changed && @@ -3174,7 +3323,7 @@ rend_services_introduce(void) router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC; if (get_options()->AllowInvalid_ & ALLOW_INVALID_INTRODUCTION) flags |= CRN_ALLOW_INVALID; - node = router_choose_random_node(intro_nodes, + node = router_choose_random_node(exclude_nodes, options->ExcludeNodes, flags); if (!node) { log_warn(LD_REND, @@ -3185,7 +3334,9 @@ rend_services_introduce(void) break; } intro_point_set_changed = 1; - smartlist_add(intro_nodes, (void*)node); + /* Add the choosen node to the exclusion list in order to avoid to pick + * it again in the next iteration. */ + smartlist_add(exclude_nodes, (void*)node); intro = tor_malloc_zero(sizeof(rend_intro_point_t)); intro->extend_info = extend_info_from_node(node, 0); intro->intro_key = crypto_pk_new(); @@ -3214,9 +3365,12 @@ rend_services_introduce(void) } } } - smartlist_free(intro_nodes); + smartlist_free(exclude_nodes); } +#define MIN_REND_INITIAL_POST_DELAY (30) +#define MIN_REND_INITIAL_POST_DELAY_TESTING (5) + /** Regenerate and upload rendezvous service descriptors for all * services, if necessary. If the descriptor has been dirty enough * for long enough, definitely upload; else only upload when the @@ -3231,6 +3385,9 @@ rend_consider_services_upload(time_t now) int i; rend_service_t *service; int rendpostperiod = get_options()->RendPostPeriod; + int rendinitialpostdelay = (get_options()->TestingTorNetwork ? + MIN_REND_INITIAL_POST_DELAY_TESTING : + MIN_REND_INITIAL_POST_DELAY); if (!get_options()->PublishHidServDescriptors) return; @@ -3238,17 +3395,17 @@ rend_consider_services_upload(time_t now) for (i=0; i < smartlist_len(rend_service_list); ++i) { service = smartlist_get(rend_service_list, i); if (!service->next_upload_time) { /* never been uploaded yet */ - /* The fixed lower bound of 30 seconds ensures that the descriptor - * is stable before being published. See comment below. */ + /* The fixed lower bound of rendinitialpostdelay seconds ensures that + * the descriptor is stable before being published. See comment below. */ service->next_upload_time = - now + 30 + crypto_rand_int(2*rendpostperiod); + now + rendinitialpostdelay + crypto_rand_int(2*rendpostperiod); } if (service->next_upload_time < now || (service->desc_is_dirty && - service->desc_is_dirty < now-30)) { + service->desc_is_dirty < now-rendinitialpostdelay)) { /* if it's time, or if the directory servers have a wrong service - * descriptor and ours has been stable for 30 seconds, upload a - * new one of each format. */ + * descriptor and ours has been stable for rendinitialpostdelay seconds, + * upload a new one of each format. */ rend_service_update_descriptor(service); upload_service_descriptor(service); } @@ -3327,9 +3484,64 @@ rend_service_dump_stats(int severity) } } +#ifdef HAVE_SYS_UN_H + +/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, + * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success + * else return -ENOSYS if AF_UNIX is not supported (see function in the + * #else statement below). */ +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + tor_assert(ports); + tor_assert(p); + tor_assert(p->is_unix_addr); + + smartlist_add(ports, p); + return 0; +} + +/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 + * on success else return -ENOSYS if AF_UNIX is not supported (see function + * in the #else statement below). */ +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + tor_assert(conn); + tor_assert(p); + tor_assert(p->is_unix_addr); + + conn->base_.socket_family = AF_UNIX; + tor_addr_make_unspec(&conn->base_.addr); + conn->base_.port = 1; + conn->base_.address = tor_strdup(p->unix_addr); + return 0; +} + +#else /* defined(HAVE_SYS_UN_H) */ + +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + (void) conn; + (void) p; + return -ENOSYS; +} + +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + (void) ports; + (void) p; + return -ENOSYS; +} + +#endif /* HAVE_SYS_UN_H */ + /** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for * 'circ', and look up the port and address based on conn-\>port. - * Assign the actual conn-\>addr and conn-\>port. Return -1 if failure, + * Assign the actual conn-\>addr and conn-\>port. Return -2 on failure + * for which the circuit should be closed, -1 on other failure, * or 0 for success. */ int @@ -3340,6 +3552,7 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; smartlist_t *matching_ports; rend_service_port_config_t *chosen_port; + unsigned int warn_once = 0; tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); tor_assert(circ->rend_data); @@ -3352,24 +3565,53 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, log_warn(LD_REND, "Couldn't find any service associated with pk %s on " "rendezvous circuit %u; closing.", serviceid, (unsigned)circ->base_.n_circ_id); - return -1; + return -2; } matching_ports = smartlist_new(); SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, { - if (conn->base_.port == p->virtual_port) { + if (conn->base_.port != p->virtual_port) { + continue; + } + if (!(p->is_unix_addr)) { smartlist_add(matching_ports, p); + } else { + if (add_unix_port(matching_ports, p)) { + if (!warn_once) { + /* Unix port not supported so warn only once. */ + log_warn(LD_REND, + "Saw AF_UNIX virtual port mapping for port %d on service " + "%s, which is unsupported on this platform. Ignoring it.", + conn->base_.port, serviceid); + } + warn_once++; + } } }); chosen_port = smartlist_choose(matching_ports); smartlist_free(matching_ports); if (chosen_port) { - tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr); - conn->base_.port = chosen_port->real_port; + if (!(chosen_port->is_unix_addr)) { + /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ + tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr); + conn->base_.port = chosen_port->real_port; + } else { + if (set_unix_port(conn, chosen_port)) { + /* Simply impossible to end up here else we were able to add a Unix + * port without AF_UNIX support... ? */ + tor_assert(0); + } + } return 0; } - log_info(LD_REND, "No virtual port mapping exists for port %d on service %s", - conn->base_.port,serviceid); - return -1; + + log_info(LD_REND, + "No virtual port mapping exists for port %d on service %s", + conn->base_.port, serviceid); + + if (service->allow_unknown_ports) + return -1; + else + return -2; } diff --git a/src/or/rendservice.h b/src/or/rendservice.h index caf88a3d64..754f7c358c 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -38,13 +38,9 @@ struct rend_intro_cell_s { /* Version-specific parts */ union { struct { - /* Rendezvous point nickname */ - uint8_t rp[20]; - } v0; - struct { /* Rendezvous point nickname or hex-encoded key digest */ uint8_t rp[42]; - } v1; + } v0_v1; struct { /* The extend_info_t struct has everything v2 uses */ extend_info_t *extend_info; @@ -71,6 +67,8 @@ struct rend_intro_cell_s { int num_rend_services(void); int rend_config_services(const or_options_t *options, int validate_only); int rend_service_load_all_keys(void); +void rend_services_add_filenames_to_lists(smartlist_t *open_lst, + smartlist_t *stat_lst); void rend_services_introduce(void); void rend_consider_services_upload(time_t now); void rend_hsdir_routers_changed(void); @@ -83,7 +81,6 @@ int rend_service_intro_established(origin_circuit_t *circuit, void rend_service_rendezvous_has_opened(origin_circuit_t *circuit); int rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, size_t request_len); -void rend_service_compact_intro(rend_intro_cell_t *request); int rend_service_decrypt_intro(rend_intro_cell_t *request, crypto_pk_t *key, char **err_msg_out); diff --git a/src/or/rephist.c b/src/or/rephist.c index 2948bf8f00..fe0997c891 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -879,126 +879,6 @@ rep_hist_record_mtbf_data(time_t now, int missing_means_down) return -1; } -/** Format the current tracked status of the router in <b>hist</b> at time - * <b>now</b> for analysis; return it in a newly allocated string. */ -static char * -rep_hist_format_router_status(or_history_t *hist, time_t now) -{ - char sor_buf[ISO_TIME_LEN+1]; - char sod_buf[ISO_TIME_LEN+1]; - double wfu; - double mtbf; - int up = 0, down = 0; - char *cp = NULL; - - if (hist->start_of_run) { - format_iso_time(sor_buf, hist->start_of_run); - up = 1; - } - if (hist->start_of_downtime) { - format_iso_time(sod_buf, hist->start_of_downtime); - down = 1; - } - - wfu = get_weighted_fractional_uptime(hist, now); - mtbf = get_stability(hist, now); - tor_asprintf(&cp, - "%s%s%s" - "%s%s%s" - "wfu %0.3f\n" - " weighted-time %lu\n" - " weighted-uptime %lu\n" - "mtbf %0.1f\n" - " weighted-run-length %lu\n" - " total-run-weights %f\n", - up?"uptime-started ":"", up?sor_buf:"", up?" UTC\n":"", - down?"downtime-started ":"", down?sod_buf:"", down?" UTC\n":"", - wfu, - hist->total_weighted_time, - hist->weighted_uptime, - mtbf, - hist->weighted_run_length, - hist->total_run_weights - ); - return cp; -} - -/** The last stability analysis document that we created, or NULL if we never - * have created one. */ -static char *last_stability_doc = NULL; -/** The last time we created a stability analysis document, or 0 if we never - * have created one. */ -static time_t built_last_stability_doc_at = 0; -/** Shortest allowable time between building two stability documents. */ -#define MAX_STABILITY_DOC_BUILD_RATE (3*60) - -/** Return a pointer to a NUL-terminated document describing our view of the - * stability of the routers we've been tracking. Return NULL on failure. */ -const char * -rep_hist_get_router_stability_doc(time_t now) -{ - char *result; - smartlist_t *chunks; - if (built_last_stability_doc_at + MAX_STABILITY_DOC_BUILD_RATE > now) - return last_stability_doc; - - if (!history_map) - return NULL; - - tor_free(last_stability_doc); - chunks = smartlist_new(); - - if (rep_hist_have_measured_enough_stability()) { - smartlist_add(chunks, tor_strdup("we-have-enough-measurements\n")); - } else { - smartlist_add(chunks, tor_strdup("we-do-not-have-enough-measurements\n")); - } - - DIGESTMAP_FOREACH(history_map, id, or_history_t *, hist) { - const node_t *node; - char dbuf[BASE64_DIGEST_LEN+1]; - char *info; - digest_to_base64(dbuf, id); - node = node_get_by_id(id); - if (node) { - char ip[INET_NTOA_BUF_LEN+1]; - char tbuf[ISO_TIME_LEN+1]; - 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)); - smartlist_add_asprintf(chunks, - "router %s %s %s\n" - "published %s\n" - "relevant-flags %s%s%s\n" - "declared-uptime %ld\n", - dbuf, node_get_nickname(node), ip, - tbuf, - node->is_running ? "Running " : "", - node->is_valid ? "Valid " : "", - node->ri && node->ri->is_hibernating ? "Hibernating " : "", - node_get_declared_uptime(node)); - } else { - smartlist_add_asprintf(chunks, - "router %s {no descriptor}\n", dbuf); - } - info = rep_hist_format_router_status(hist, now); - if (info) - smartlist_add(chunks, info); - - } DIGESTMAP_FOREACH_END; - - result = smartlist_join_strings(chunks, "", 0, NULL); - SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); - smartlist_free(chunks); - - last_stability_doc = result; - built_last_stability_doc_at = time(NULL); - return result; -} - /** Helper: return the first j >= i such that !strcmpstart(sl[j], prefix) and * such that no line sl[k] with i <= k < j starts with "R ". Return -1 if no * such line exists. */ @@ -1051,7 +931,7 @@ correct_time(time_t t, time_t now, time_t stored_at, time_t started_measuring) return 0; else { long run_length = stored_at - t; - t = now - run_length; + t = (time_t)(now - run_length); if (t < started_measuring) t = started_measuring; return t; @@ -1212,7 +1092,7 @@ rep_hist_load_mtbf_data(time_t now) hist->start_of_run = correct_time(start_of_run, now, stored_at, tracked_since); if (hist->start_of_run < latest_possible_start + wrl) - latest_possible_start = hist->start_of_run - wrl; + latest_possible_start = (time_t)(hist->start_of_run - wrl); hist->weighted_run_length = wrl; hist->total_run_weights = trw; @@ -1251,9 +1131,7 @@ rep_hist_load_mtbf_data(time_t now) * totals? */ #define NUM_SECS_ROLLING_MEASURE 10 /** How large are the intervals for which we track and report bandwidth use? */ -/* XXXX Watch out! Before Tor 0.2.2.21-alpha, using any other value here would - * generate an unparseable state file. */ -#define NUM_SECS_BW_SUM_INTERVAL (15*60) +#define NUM_SECS_BW_SUM_INTERVAL (4*60*60) /** How far in the past do we remember and publish bandwidth use? */ #define NUM_SECS_BW_SUM_IS_VALID (24*60*60) /** How many bandwidth usage intervals do we remember? (derived) */ @@ -1690,7 +1568,7 @@ rep_hist_load_bwhist_state_section(bw_array_t *b, time_t start; uint64_t v, mv; - int i,ok,ok_m; + int i,ok,ok_m = 0; int have_maxima = s_maxima && s_values && (smartlist_len(s_values) == smartlist_len(s_maxima)); @@ -1862,22 +1740,20 @@ rep_hist_note_used_port(time_t now, uint16_t port) add_predicted_port(now, port); } -/** For this long after we've seen a request for a given port, assume that - * we'll want to make connections to the same port in the future. */ -#define PREDICTED_CIRCS_RELEVANCE_TIME (60*60) - /** Return a newly allocated pointer to a list of uint16_t * for ports that * are likely to be asked for in the near future. */ smartlist_t * rep_hist_get_predicted_ports(time_t now) { + int predicted_circs_relevance_time; smartlist_t *out = smartlist_new(); tor_assert(predicted_ports_list); + predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime; /* clean out obsolete entries */ SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) { - if (pp->time + PREDICTED_CIRCS_RELEVANCE_TIME < now) { + 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); @@ -1944,14 +1820,17 @@ int rep_hist_get_predicted_internal(time_t now, int *need_uptime, int *need_capacity) { + int predicted_circs_relevance_time; + predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime; + if (!predicted_internal_time) { /* initialize it */ predicted_internal_time = now; predicted_internal_uptime_time = now; predicted_internal_capacity_time = now; } - if (predicted_internal_time + PREDICTED_CIRCS_RELEVANCE_TIME < now) + if (predicted_internal_time + predicted_circs_relevance_time < now) return 0; /* too long ago */ - if (predicted_internal_uptime_time + PREDICTED_CIRCS_RELEVANCE_TIME >= now) + if (predicted_internal_uptime_time + predicted_circs_relevance_time >= now) *need_uptime = 1; // Always predict that we need capacity. *need_capacity = 1; @@ -1963,8 +1842,11 @@ rep_hist_get_predicted_internal(time_t now, int *need_uptime, int any_predicted_circuits(time_t now) { + int predicted_circs_relevance_time; + predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime; + return smartlist_len(predicted_ports_list) || - predicted_internal_time + PREDICTED_CIRCS_RELEVANCE_TIME >= now; + predicted_internal_time + predicted_circs_relevance_time >= now; } /** Return 1 if we have no need for circuits currently, else return 0. */ @@ -2114,12 +1996,9 @@ void rep_hist_exit_stats_init(time_t now) { start_of_exit_stats_interval = now; - exit_bytes_read = tor_malloc_zero(EXIT_STATS_NUM_PORTS * - sizeof(uint64_t)); - exit_bytes_written = tor_malloc_zero(EXIT_STATS_NUM_PORTS * - sizeof(uint64_t)); - exit_streams = tor_malloc_zero(EXIT_STATS_NUM_PORTS * - sizeof(uint32_t)); + exit_bytes_read = tor_calloc(EXIT_STATS_NUM_PORTS, sizeof(uint64_t)); + exit_bytes_written = tor_calloc(EXIT_STATS_NUM_PORTS, sizeof(uint64_t)); + exit_streams = tor_calloc(EXIT_STATS_NUM_PORTS, sizeof(uint32_t)); } /** Reset counters for exit port statistics. */ @@ -2313,7 +2192,7 @@ rep_hist_format_exit_stats(time_t now) time_t rep_hist_exit_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_exit_stats_interval) return 0; /* Not initialized. */ @@ -2329,19 +2208,12 @@ rep_hist_exit_stats_write(time_t now) rep_hist_reset_exit_stats(now); /* Try to write to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "exit-stats", str, "exit port statistics"); } - filename = get_datadir_fname2("stats", "exit-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write exit port statistics to disk!"); done: tor_free(str); - tor_free(statsdir); - tor_free(filename); return start_of_exit_stats_interval + WRITE_STATS_INTERVAL; } @@ -2434,7 +2306,7 @@ rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t end_of_interval) return; start_of_interval = (circ->timestamp_created.tv_sec > start_of_buffer_stats_interval) ? - circ->timestamp_created.tv_sec : + (time_t)circ->timestamp_created.tv_sec : start_of_buffer_stats_interval; interval_length = (int) (end_of_interval - start_of_interval); if (interval_length <= 0) @@ -2597,8 +2469,7 @@ rep_hist_format_buffer_stats(time_t now) time_t rep_hist_buffer_stats_write(time_t now) { - circuit_t *circ; - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_buffer_stats_interval) return 0; /* Not initialized. */ @@ -2606,9 +2477,10 @@ rep_hist_buffer_stats_write(time_t now) goto done; /* Not ready to write */ /* Add open circuits to the history. */ - for (circ = circuit_get_global_list_(); circ; circ = circ->next) { + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { rep_hist_buffer_stats_add_circ(circ, now); } + SMARTLIST_FOREACH_END(circ); /* Generate history string. */ str = rep_hist_format_buffer_stats(now); @@ -2617,19 +2489,12 @@ rep_hist_buffer_stats_write(time_t now) rep_hist_reset_buffer_stats(now); /* Try to write to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "buffer-stats", str, "buffer statistics"); } - filename = get_datadir_fname2("stats", "buffer-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write buffer stats to disk!"); done: tor_free(str); - tor_free(filename); - tor_free(statsdir); return start_of_buffer_stats_interval + WRITE_STATS_INTERVAL; } @@ -2702,7 +2567,7 @@ rep_hist_format_desc_stats(time_t now) size = digestmap_size(served_descs); if (size > 0) { - vals = tor_malloc(size * sizeof(int)); + vals = tor_calloc(size, sizeof(int)); for (iter = digestmap_iter_init(served_descs); !digestmap_iter_done(iter); iter = digestmap_iter_next(served_descs, iter)) { @@ -2741,7 +2606,7 @@ rep_hist_format_desc_stats(time_t now) time_t rep_hist_desc_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *filename = NULL, *str = NULL; if (!start_of_served_descs_stats_interval) return 0; /* We're not collecting stats. */ @@ -2751,10 +2616,8 @@ rep_hist_desc_stats_write(time_t now) str = rep_hist_format_desc_stats(now); tor_assert(str != NULL); - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (check_or_create_data_subdir("stats") < 0) { + goto done; } filename = get_datadir_fname2("stats", "served-desc-stats"); if (append_bytes_to_file(filename, str, strlen(str), 0) < 0) @@ -2763,7 +2626,6 @@ rep_hist_desc_stats_write(time_t now) rep_hist_reset_desc_stats(now); done: - tor_free(statsdir); tor_free(filename); tor_free(str); return start_of_served_descs_stats_interval + WRITE_STATS_INTERVAL; @@ -2860,8 +2722,8 @@ bidi_map_ent_hash(const bidi_map_entry_t *entry) 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); +HT_GENERATE2(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, + bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_) /* DOCDOC bidi_map_free */ static void @@ -2981,7 +2843,7 @@ rep_hist_format_conn_stats(time_t now) time_t rep_hist_conn_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL, *str = NULL; + char *str = NULL; if (!start_of_conn_stats_interval) return 0; /* Not initialized. */ @@ -2995,28 +2857,21 @@ rep_hist_conn_stats_write(time_t now) rep_hist_reset_conn_stats(now); /* Try to write to disk. */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { - log_warn(LD_HIST, "Unable to create stats/ directory!"); - goto done; + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "conn-stats", str, "connection statistics"); } - filename = get_datadir_fname2("stats", "conn-stats"); - if (write_str_to_file(filename, str, 0) < 0) - log_warn(LD_HIST, "Unable to write conn stats to disk!"); done: tor_free(str); - tor_free(filename); - tor_free(statsdir); return start_of_conn_stats_interval + WRITE_STATS_INTERVAL; } /** Internal statistics to track how many requests of each type of - * handshake we've received, and how many we've completed. Useful for - * seeing trends in cpu load. + * handshake we've received, and how many we've assigned to cpuworkers. + * Useful for seeing trends in cpu load. * @{ */ -static int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1] = {0}; -static int onion_handshakes_completed[MAX_ONION_HANDSHAKE_TYPE+1] = {0}; +STATIC int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1] = {0}; +STATIC int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1] = {0}; /**@}*/ /** A new onionskin (using the <b>type</b> handshake) has arrived. */ @@ -3030,10 +2885,10 @@ rep_hist_note_circuit_handshake_requested(uint16_t type) /** We've sent an onionskin (using the <b>type</b> handshake) to a * cpuworker. */ void -rep_hist_note_circuit_handshake_completed(uint16_t type) +rep_hist_note_circuit_handshake_assigned(uint16_t type) { if (type <= MAX_ONION_HANDSHAKE_TYPE) - onion_handshakes_completed[type]++; + onion_handshakes_assigned[type]++; } /** Log our onionskin statistics since the last time we were called. */ @@ -3041,31 +2896,289 @@ void rep_hist_log_circuit_handshake_stats(time_t now) { (void)now; - log_notice(LD_HIST, "Circuit handshake stats since last time: " + log_notice(LD_HEARTBEAT, "Circuit handshake stats since last time: " "%d/%d TAP, %d/%d NTor.", - onion_handshakes_completed[ONION_HANDSHAKE_TYPE_TAP], + onion_handshakes_assigned[ONION_HANDSHAKE_TYPE_TAP], onion_handshakes_requested[ONION_HANDSHAKE_TYPE_TAP], - onion_handshakes_completed[ONION_HANDSHAKE_TYPE_NTOR], + onion_handshakes_assigned[ONION_HANDSHAKE_TYPE_NTOR], onion_handshakes_requested[ONION_HANDSHAKE_TYPE_NTOR]); - memset(onion_handshakes_completed, 0, sizeof(onion_handshakes_completed)); + memset(onion_handshakes_assigned, 0, sizeof(onion_handshakes_assigned)); memset(onion_handshakes_requested, 0, sizeof(onion_handshakes_requested)); } +/* Hidden service statistics section */ + +/** Start of the current hidden service stats interval or 0 if we're + * not collecting hidden service statistics. */ +static time_t start_of_hs_stats_interval; + +/** Carries the various hidden service statistics, and any other + * information needed. */ +typedef struct hs_stats_t { + /** How many relay cells have we seen as rendezvous points? */ + int64_t rp_relay_cells_seen; + + /** Set of unique public key digests we've seen this stat period + * (could also be implemented as sorted smartlist). */ + digestmap_t *onions_seen_this_period; +} hs_stats_t; + +/** Our statistics structure singleton. */ +static hs_stats_t *hs_stats = NULL; + +/** Allocate, initialize and return an hs_stats_t structure. */ +static hs_stats_t * +hs_stats_new(void) +{ + hs_stats_t * hs_stats = tor_malloc_zero(sizeof(hs_stats_t)); + hs_stats->onions_seen_this_period = digestmap_new(); + + return hs_stats; +} + +/** Free an hs_stats_t structure. */ +static void +hs_stats_free(hs_stats_t *hs_stats) +{ + if (!hs_stats) { + return; + } + + digestmap_free(hs_stats->onions_seen_this_period, NULL); + tor_free(hs_stats); +} + +/** Initialize hidden service statistics. */ +void +rep_hist_hs_stats_init(time_t now) +{ + if (!hs_stats) { + hs_stats = hs_stats_new(); + } + + start_of_hs_stats_interval = now; +} + +/** Clear history of hidden service statistics and set the measurement + * interval start to <b>now</b>. */ +static void +rep_hist_reset_hs_stats(time_t now) +{ + if (!hs_stats) { + hs_stats = hs_stats_new(); + } + + hs_stats->rp_relay_cells_seen = 0; + + digestmap_free(hs_stats->onions_seen_this_period, NULL); + hs_stats->onions_seen_this_period = digestmap_new(); + + start_of_hs_stats_interval = now; +} + +/** Stop collecting hidden service stats in a way that we can re-start + * doing so in rep_hist_buffer_stats_init(). */ +void +rep_hist_hs_stats_term(void) +{ + rep_hist_reset_hs_stats(0); +} + +/** We saw a new HS relay cell, Count it! */ +void +rep_hist_seen_new_rp_cell(void) +{ + if (!hs_stats) { + return; // We're not collecting stats + } + + hs_stats->rp_relay_cells_seen++; +} + +/** As HSDirs, we saw another hidden service with public key + * <b>pubkey</b>. Check whether we have counted it before, if not + * count it now! */ +void +rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey) +{ + char pubkey_hash[DIGEST_LEN]; + + if (!hs_stats) { + return; // We're not collecting stats + } + + /* Get the digest of the pubkey which will be used to detect whether + we've seen this hidden service before or not. */ + if (crypto_pk_get_digest(pubkey, pubkey_hash) < 0) { + /* This fail should not happen; key has been validated by + descriptor parsing code first. */ + return; + } + + /* Check if this is the first time we've seen this hidden + service. If it is, count it as new. */ + if (!digestmap_get(hs_stats->onions_seen_this_period, + pubkey_hash)) { + digestmap_set(hs_stats->onions_seen_this_period, + pubkey_hash, (void*)(uintptr_t)1); + } +} + +/* The number of cells that are supposed to be hidden from the adversary + * by adding noise from the Laplace distribution. This value, divided by + * EPSILON, is Laplace parameter b. */ +#define REND_CELLS_DELTA_F 2048 +/* Security parameter for obfuscating number of cells with a value between + * 0 and 1. Smaller values obfuscate observations more, but at the same + * time make statistics less usable. */ +#define REND_CELLS_EPSILON 0.3 +/* The number of cells that are supposed to be hidden from the adversary + * by rounding up to the next multiple of this number. */ +#define REND_CELLS_BIN_SIZE 1024 +/* The number of service identities that are supposed to be hidden from + * the adversary by adding noise from the Laplace distribution. This + * value, divided by EPSILON, is Laplace parameter b. */ +#define ONIONS_SEEN_DELTA_F 8 +/* Security parameter for obfuscating number of service identities with a + * value between 0 and 1. Smaller values obfuscate observations more, but + * at the same time make statistics less usable. */ +#define ONIONS_SEEN_EPSILON 0.3 +/* The number of service identities that are supposed to be hidden from + * the adversary by rounding up to the next multiple of this number. */ +#define ONIONS_SEEN_BIN_SIZE 8 + +/** Allocate and return a string containing hidden service stats that + * are meant to be placed in the extra-info descriptor. */ +static char * +rep_hist_format_hs_stats(time_t now) +{ + char t[ISO_TIME_LEN+1]; + char *hs_stats_string; + int64_t obfuscated_cells_seen; + int64_t obfuscated_onions_seen; + + obfuscated_cells_seen = round_int64_to_next_multiple_of( + hs_stats->rp_relay_cells_seen, + REND_CELLS_BIN_SIZE); + obfuscated_cells_seen = add_laplace_noise(obfuscated_cells_seen, + crypto_rand_double(), + REND_CELLS_DELTA_F, REND_CELLS_EPSILON); + obfuscated_onions_seen = round_int64_to_next_multiple_of(digestmap_size( + hs_stats->onions_seen_this_period), + ONIONS_SEEN_BIN_SIZE); + obfuscated_onions_seen = add_laplace_noise(obfuscated_onions_seen, + crypto_rand_double(), ONIONS_SEEN_DELTA_F, + ONIONS_SEEN_EPSILON); + + format_iso_time(t, now); + tor_asprintf(&hs_stats_string, "hidserv-stats-end %s (%d s)\n" + "hidserv-rend-relayed-cells "I64_FORMAT" delta_f=%d " + "epsilon=%.2f bin_size=%d\n" + "hidserv-dir-onions-seen "I64_FORMAT" delta_f=%d " + "epsilon=%.2f bin_size=%d\n", + t, (unsigned) (now - start_of_hs_stats_interval), + I64_PRINTF_ARG(obfuscated_cells_seen), REND_CELLS_DELTA_F, + REND_CELLS_EPSILON, REND_CELLS_BIN_SIZE, + I64_PRINTF_ARG(obfuscated_onions_seen), + ONIONS_SEEN_DELTA_F, + ONIONS_SEEN_EPSILON, ONIONS_SEEN_BIN_SIZE); + + return hs_stats_string; +} + +/** If 24 hours have passed since the beginning of the current HS + * stats period, write buffer stats to $DATADIR/stats/hidserv-stats + * (possibly overwriting an existing file) and reset counters. Return + * when we would next want to write buffer stats or 0 if we never want to + * write. */ +time_t +rep_hist_hs_stats_write(time_t now) +{ + char *str = NULL; + + if (!start_of_hs_stats_interval) { + return 0; /* Not initialized. */ + } + + if (start_of_hs_stats_interval + WRITE_STATS_INTERVAL > now) { + goto done; /* Not ready to write */ + } + + /* Generate history string. */ + str = rep_hist_format_hs_stats(now); + + /* Reset HS history. */ + rep_hist_reset_hs_stats(now); + + /* Try to write to disk. */ + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "hidserv-stats", str, + "hidden service stats"); + } + + done: + tor_free(str); + return start_of_hs_stats_interval + WRITE_STATS_INTERVAL; +} + +#define MAX_LINK_PROTO_TO_LOG 4 +static uint64_t link_proto_count[MAX_LINK_PROTO_TO_LOG+1][2]; + +/** Note that we negotiated link protocol version <b>link_proto</b>, on + * a connection that started here iff <b>started_here</b> is true. + */ +void +rep_hist_note_negotiated_link_proto(unsigned link_proto, int started_here) +{ + started_here = !!started_here; /* force to 0 or 1 */ + if (link_proto > MAX_LINK_PROTO_TO_LOG) { + log_warn(LD_BUG, "Can't log link protocol %u", link_proto); + return; + } + + link_proto_count[link_proto][started_here]++; +} + +/** Log a heartbeat message explaining how many connections of each link + * protocol version we have used. + */ +void +rep_hist_log_link_protocol_counts(void) +{ + log_notice(LD_HEARTBEAT, + "Since startup, we have initiated " + U64_FORMAT" v1 connections, " + U64_FORMAT" v2 connections, " + U64_FORMAT" v3 connections, and " + U64_FORMAT" v4 connections; and received " + U64_FORMAT" v1 connections, " + U64_FORMAT" v2 connections, " + U64_FORMAT" v3 connections, and " + U64_FORMAT" v4 connections.", + U64_PRINTF_ARG(link_proto_count[1][1]), + U64_PRINTF_ARG(link_proto_count[2][1]), + U64_PRINTF_ARG(link_proto_count[3][1]), + U64_PRINTF_ARG(link_proto_count[4][1]), + U64_PRINTF_ARG(link_proto_count[1][0]), + U64_PRINTF_ARG(link_proto_count[2][0]), + U64_PRINTF_ARG(link_proto_count[3][0]), + U64_PRINTF_ARG(link_proto_count[4][0])); +} + /** Free all storage held by the OR/link history caches, by the * bandwidth history arrays, by the port history, or by statistics . */ void rep_hist_free_all(void) { + hs_stats_free(hs_stats); digestmap_free(history_map, free_or_history); tor_free(read_array); tor_free(write_array); tor_free(dir_read_array); tor_free(dir_write_array); - tor_free(last_stability_doc); tor_free(exit_bytes_read); tor_free(exit_bytes_written); 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 de824749b4..f94b4e8ff1 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -47,7 +47,6 @@ double rep_hist_get_stability(const char *id, time_t when); double rep_hist_get_weighted_fractional_uptime(const char *id, time_t when); long rep_hist_get_weighted_time_known(const char *id, time_t when); int rep_hist_have_measured_enough_stability(void); -const char *rep_hist_get_router_stability_doc(time_t now); void rep_hist_note_used_port(time_t now, uint16_t port); smartlist_t *rep_hist_get_predicted_ports(time_t now); @@ -97,10 +96,21 @@ time_t rep_hist_conn_stats_write(time_t now); void rep_hist_conn_stats_term(void); void rep_hist_note_circuit_handshake_requested(uint16_t type); -void rep_hist_note_circuit_handshake_completed(uint16_t type); +void rep_hist_note_circuit_handshake_assigned(uint16_t type); void rep_hist_log_circuit_handshake_stats(time_t now); +void rep_hist_hs_stats_init(time_t now); +void rep_hist_hs_stats_term(void); +time_t rep_hist_hs_stats_write(time_t now); +char *rep_hist_get_hs_stats_string(void); +void rep_hist_seen_new_rp_cell(void); +void rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey); + void rep_hist_free_all(void); +void rep_hist_note_negotiated_link_proto(unsigned link_proto, + int started_here); +void rep_hist_log_link_protocol_counts(void); + #endif diff --git a/src/or/replaycache.c b/src/or/replaycache.c index 59b98489b7..569e0736cb 100644 --- a/src/or/replaycache.c +++ b/src/or/replaycache.c @@ -1,4 +1,4 @@ - /* Copyright (c) 2012-2013, The Tor Project, Inc. */ + /* Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /* @@ -63,9 +63,9 @@ replaycache_new(time_t horizon, time_t interval) /** See documentation for replaycache_add_and_test() */ -int +STATIC int replaycache_add_and_test_internal( - time_t present, replaycache_t *r, const void *data, int len, + time_t present, replaycache_t *r, const void *data, size_t len, time_t *elapsed) { int rv = 0; @@ -73,7 +73,7 @@ replaycache_add_and_test_internal( time_t *access_time; /* sanity check */ - if (present <= 0 || !r || !data || len <= 0) { + if (present <= 0 || !r || !data || len == 0) { log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid" " parameters; please fix this."); goto done; @@ -127,14 +127,13 @@ replaycache_add_and_test_internal( /** See documentation for replaycache_scrub_if_needed() */ -void +STATIC void replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) { digestmap_iter_t *itr = NULL; const char *digest; void *valp; time_t *access_time; - char scrub_this; /* sanity check */ if (!r || !(r->digests_seen)) { @@ -152,20 +151,10 @@ replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) /* okay, scrub time */ itr = digestmap_iter_init(r->digests_seen); while (!digestmap_iter_done(itr)) { - scrub_this = 0; digestmap_iter_get(itr, &digest, &valp); access_time = (time_t *)valp; - if (access_time) { - /* aged out yet? */ - if (*access_time < present - r->horizon) scrub_this = 1; - } else { - /* Buh? Get rid of it, anyway */ - log_info(LD_BUG, "replaycache_scrub_if_needed_internal() saw a NULL" - " entry in the digestmap."); - scrub_this = 1; - } - - if (scrub_this) { + /* aged out yet? */ + if (*access_time < present - r->horizon) { /* Advance the iterator and remove this one */ itr = digestmap_iter_next_rmv(r->digests_seen, itr); /* Free the value removed */ @@ -187,7 +176,7 @@ replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) */ int -replaycache_add_and_test(replaycache_t *r, const void *data, int len) +replaycache_add_and_test(replaycache_t *r, const void *data, size_t len) { return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL); } @@ -198,7 +187,7 @@ replaycache_add_and_test(replaycache_t *r, const void *data, int len) int replaycache_add_test_and_elapsed( - replaycache_t *r, const void *data, int len, time_t *elapsed) + replaycache_t *r, const void *data, size_t len, time_t *elapsed) { return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed); } diff --git a/src/or/replaycache.h b/src/or/replaycache.h index de20cab627..9b9daf3831 100644 --- a/src/or/replaycache.h +++ b/src/or/replaycache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2013, The Tor Project, Inc. */ +/* Copyright (c) 2012-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -45,10 +45,10 @@ replaycache_t * replaycache_new(time_t horizon, time_t interval); * testing. For everything else, use the wrappers below instead. */ -int replaycache_add_and_test_internal( - time_t present, replaycache_t *r, const void *data, int len, +STATIC int replaycache_add_and_test_internal( + time_t present, replaycache_t *r, const void *data, size_t len, time_t *elapsed); -void replaycache_scrub_if_needed_internal( +STATIC void replaycache_scrub_if_needed_internal( time_t present, replaycache_t *r); #endif /* REPLAYCACHE_PRIVATE */ @@ -57,9 +57,9 @@ void replaycache_scrub_if_needed_internal( * replaycache_t methods */ -int replaycache_add_and_test(replaycache_t *r, const void *data, int len); +int replaycache_add_and_test(replaycache_t *r, const void *data, size_t len); int replaycache_add_test_and_elapsed( - replaycache_t *r, const void *data, int len, time_t *elapsed); + replaycache_t *r, const void *data, size_t len, time_t *elapsed); void replaycache_scrub_if_needed(replaycache_t *r); #endif diff --git a/src/or/router.c b/src/or/router.c index eabd9c3f59..2ddaa895fc 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTER_PRIVATE @@ -55,13 +55,11 @@ static crypto_pk_t *onionkey=NULL; /** Previous private onionskin decryption key: used to decode CREATE cells * generated by clients that have an older version of our descriptor. */ static crypto_pk_t *lastonionkey=NULL; -#ifdef CURVE25519_ENABLED /** Current private ntor secret key: used to perform the ntor handshake. */ static curve25519_keypair_t curve25519_onion_key; /** Previous private ntor secret key: used to perform the ntor handshake * with clients that have an older version of our descriptor. */ static curve25519_keypair_t last_curve25519_onion_key; -#endif /** Private server "identity key": used to sign directory info and TLS * certificates. Never changes. */ static crypto_pk_t *server_identitykey=NULL; @@ -134,7 +132,6 @@ dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last) tor_mutex_release(key_lock); } -#ifdef CURVE25519_ENABLED /** Return the current secret onion key for the ntor handshake. Must only * be called from the main thread. */ static const curve25519_keypair_t * @@ -181,7 +178,6 @@ ntor_key_map_free(di_digest256_map_t *map) return; dimap_free(map, ntor_key_map_free_helper); } -#endif /** Return the time when the onion key was last set. This is either the time * when the process launched, or the time of the most recent key rotation since @@ -232,7 +228,8 @@ get_server_identity_key(void) return server_identitykey; } -/** Return true iff the server identity key has been set. */ +/** Return true iff we are a server and the server identity key + * has been set. */ int server_identity_key_is_set(void) { @@ -312,12 +309,11 @@ rotate_onion_key(void) char *fname, *fname_prev; crypto_pk_t *prkey = NULL; or_state_t *state = get_or_state(); -#ifdef CURVE25519_ENABLED curve25519_keypair_t new_curve25519_keypair; -#endif time_t now; fname = get_datadir_fname2("keys", "secret_onion_key"); fname_prev = get_datadir_fname2("keys", "secret_onion_key.old"); + /* There isn't much point replacing an old key with an empty file */ if (file_status(fname) == FN_FILE) { if (replace_file(fname, fname_prev)) goto error; @@ -334,13 +330,13 @@ rotate_onion_key(void) log_err(LD_FS,"Couldn't write generated onion key to \"%s\".", fname); goto error; } -#ifdef CURVE25519_ENABLED tor_free(fname); tor_free(fname_prev); fname = get_datadir_fname2("keys", "secret_onion_key_ntor"); fname_prev = get_datadir_fname2("keys", "secret_onion_key_ntor.old"); if (curve25519_keypair_generate(&new_curve25519_keypair, 1) < 0) goto error; + /* There isn't much point replacing an old key with an empty file */ if (file_status(fname) == FN_FILE) { if (replace_file(fname, fname_prev)) goto error; @@ -350,18 +346,15 @@ rotate_onion_key(void) log_err(LD_FS,"Couldn't write curve25519 onion key to \"%s\".",fname); goto error; } -#endif log_info(LD_GENERAL, "Rotating onion key"); tor_mutex_acquire(key_lock); crypto_pk_free(lastonionkey); lastonionkey = onionkey; onionkey = prkey; -#ifdef CURVE25519_ENABLED memcpy(&last_curve25519_onion_key, &curve25519_onion_key, sizeof(curve25519_keypair_t)); memcpy(&curve25519_onion_key, &new_curve25519_keypair, sizeof(curve25519_keypair_t)); -#endif now = time(NULL); state->LastRotatedOnionKey = onionkey_set_at = now; tor_mutex_release(key_lock); @@ -373,20 +366,40 @@ rotate_onion_key(void) if (prkey) crypto_pk_free(prkey); done: -#ifdef CURVE25519_ENABLED memwipe(&new_curve25519_keypair, 0, sizeof(new_curve25519_keypair)); -#endif tor_free(fname); tor_free(fname_prev); } +/** Log greeting message that points to new relay lifecycle document the + * first time this function has been called. + */ +static void +log_new_relay_greeting(void) +{ + static int already_logged = 0; + + if (already_logged) + return; + + tor_log(LOG_NOTICE, LD_GENERAL, "You are running a new relay. " + "Thanks for helping the Tor network! If you wish to know " + "what will happen in the upcoming weeks regarding its usage, " + "have a look at https://blog.torproject.org/blog/lifecycle-of" + "-a-new-relay"); + + already_logged = 1; +} + /** Try to read an RSA key from <b>fname</b>. If <b>fname</b> doesn't exist * and <b>generate</b> is true, create a new RSA key and save it in * <b>fname</b>. Return the read/created key, or NULL on error. Log all - * errors at level <b>severity</b>. + * errors at level <b>severity</b>. If <b>log_greeting</b> is non-zero and a + * new key was created, log_new_relay_greeting() is called. */ crypto_pk_t * -init_key_from_file(const char *fname, int generate, int severity) +init_key_from_file(const char *fname, int generate, int severity, + int log_greeting) { crypto_pk_t *prkey = NULL; @@ -400,7 +413,11 @@ init_key_from_file(const char *fname, int generate, int severity) case FN_ERROR: tor_log(severity, LD_FS,"Can't read key from \"%s\"", fname); goto error; + /* treat empty key files as if the file doesn't exist, and, + * if generate is set, replace the empty file in + * crypto_pk_write_private_key_to_filename() */ case FN_NOENT: + case FN_EMPTY: if (generate) { if (!have_lockfile()) { if (try_locking(get_options(), 0)<0) { @@ -424,6 +441,9 @@ init_key_from_file(const char *fname, int generate, int severity) goto error; } log_info(LD_GENERAL, "Generated key seems valid"); + if (log_greeting) { + log_new_relay_greeting(); + } if (crypto_pk_write_private_key_to_filename(prkey, fname)) { tor_log(severity, LD_FS, "Couldn't write generated key to \"%s\".", fname); @@ -449,12 +469,11 @@ init_key_from_file(const char *fname, int generate, int severity) return NULL; } -#ifdef CURVE25519_ENABLED /** Load a curve25519 keypair from the file <b>fname</b>, writing it into - * <b>keys_out</b>. If the file isn't found and <b>generate</b> is true, - * create a new keypair and write it into the file. If there are errors, log - * them at level <b>severity</b>. Generate files using <b>tag</b> in their - * ASCII wrapper. */ + * <b>keys_out</b>. If the file isn't found, or is empty, and <b>generate</b> + * is true, create a new keypair and write it into the file. If there are + * errors, log them at level <b>severity</b>. Generate files using <b>tag</b> + * in their ASCII wrapper. */ static int init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out, const char *fname, @@ -467,7 +486,10 @@ init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out, case FN_ERROR: tor_log(severity, LD_FS,"Can't read key from \"%s\"", fname); goto error; + /* treat empty key files as if the file doesn't exist, and, if generate + * is set, replace the empty file in curve25519_keypair_write_to_file() */ case FN_NOENT: + case FN_EMPTY: if (generate) { if (!have_lockfile()) { if (try_locking(get_options(), 0)<0) { @@ -487,7 +509,7 @@ init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out, if (curve25519_keypair_write_to_file(keys_out, fname, tag)<0) { tor_log(severity, LD_FS, "Couldn't write generated key to \"%s\".", fname); - memset(keys_out, 0, sizeof(*keys_out)); + memwipe(keys_out, 0, sizeof(*keys_out)); goto error; } } else { @@ -518,7 +540,6 @@ init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out, error: return -1; } -#endif /** Try to load the vote-signing private key and certificate for being a v3 * directory authority, and make sure they match. If <b>legacy</b>, load a @@ -537,7 +558,7 @@ load_authority_keyset(int legacy, crypto_pk_t **key_out, fname = get_datadir_fname2("keys", legacy ? "legacy_signing_key" : "authority_signing_key"); - signing_key = init_key_from_file(fname, 0, LOG_INFO); + signing_key = init_key_from_file(fname, 0, LOG_INFO, 0); if (!signing_key) { log_warn(LD_DIR, "No version 3 directory key found in %s", fname); goto done; @@ -683,6 +704,63 @@ router_initialize_tls_context(void) (unsigned int)lifetime); } +/** Compute fingerprint (or hashed fingerprint if hashed is 1) and write + * it to 'fingerprint' (or 'hashed-fingerprint'). Return 0 on success, or + * -1 if Tor should die, + */ +STATIC int +router_write_fingerprint(int hashed) +{ + char *keydir = NULL, *cp = NULL; + const char *fname = hashed ? "hashed-fingerprint" : + "fingerprint"; + char fingerprint[FINGERPRINT_LEN+1]; + const or_options_t *options = get_options(); + char *fingerprint_line = NULL; + int result = -1; + + keydir = get_datadir_fname(fname); + log_info(LD_GENERAL,"Dumping %sfingerprint to \"%s\"...", + hashed ? "hashed " : "", keydir); + if (!hashed) { + if (crypto_pk_get_fingerprint(get_server_identity_key(), + fingerprint, 0) < 0) { + log_err(LD_GENERAL,"Error computing fingerprint"); + goto done; + } + } else { + if (crypto_pk_get_hashed_fingerprint(get_server_identity_key(), + fingerprint) < 0) { + log_err(LD_GENERAL,"Error computing hashed fingerprint"); + goto done; + } + } + + tor_asprintf(&fingerprint_line, "%s %s\n", options->Nickname, fingerprint); + + /* Check whether we need to write the (hashed-)fingerprint file. */ + + cp = read_file_to_str(keydir, RFTS_IGNORE_MISSING, NULL); + if (!cp || strcmp(cp, fingerprint_line)) { + if (write_str_to_file(keydir, fingerprint_line, 0)) { + log_err(LD_FS, "Error writing %sfingerprint line to file", + hashed ? "hashed " : ""); + goto done; + } + } + + log_notice(LD_GENERAL, "Your Tor %s identity key fingerprint is '%s %s'", + hashed ? "bridge's hashed" : "server's", options->Nickname, + fingerprint); + + result = 0; + done: + tor_free(cp); + tor_free(keydir); + tor_free(fingerprint_line); + return result; +} + /** Initialize all OR private keys, and the TLS context, as necessary. * On OPs, this only initializes the tls context. Return 0 on success, * or -1 if Tor should die. @@ -691,14 +769,10 @@ int init_keys(void) { char *keydir; - char fingerprint[FINGERPRINT_LEN+1]; - /*nickname<space>fp\n\0 */ - char fingerprint_line[MAX_NICKNAME_LEN+FINGERPRINT_LEN+3]; const char *mydesc; crypto_pk_t *prkey; char digest[DIGEST_LEN]; char v3_digest[DIGEST_LEN]; - char *cp; const or_options_t *options = get_options(); dirinfo_type_t type; time_t now = time(NULL); @@ -767,7 +841,7 @@ init_keys(void) /* 1b. Read identity key. Make it if none is found. */ keydir = get_datadir_fname2("keys", "secret_id_key"); log_info(LD_GENERAL,"Reading/making identity key \"%s\"...",keydir); - prkey = init_key_from_file(keydir, 1, LOG_ERR); + prkey = init_key_from_file(keydir, 1, LOG_ERR, 1); tor_free(keydir); if (!prkey) return -1; set_server_identity_key(prkey); @@ -790,7 +864,7 @@ init_keys(void) /* 2. Read onion key. Make it if none is found. */ keydir = get_datadir_fname2("keys", "secret_onion_key"); log_info(LD_GENERAL,"Reading/making onion key \"%s\"...",keydir); - prkey = init_key_from_file(keydir, 1, LOG_ERR); + prkey = init_key_from_file(keydir, 1, LOG_ERR, 1); tor_free(keydir); if (!prkey) return -1; set_onion_key(prkey); @@ -815,13 +889,14 @@ init_keys(void) keydir = get_datadir_fname2("keys", "secret_onion_key.old"); if (!lastonionkey && file_status(keydir) == FN_FILE) { - prkey = init_key_from_file(keydir, 1, LOG_ERR); /* XXXX Why 1? */ + /* Load keys from non-empty files only. + * Missing old keys won't be replaced with freshly generated keys. */ + prkey = init_key_from_file(keydir, 0, LOG_ERR, 0); if (prkey) lastonionkey = prkey; } tor_free(keydir); -#ifdef CURVE25519_ENABLED { /* 2b. Load curve25519 onion keys. */ int r; @@ -837,12 +912,13 @@ init_keys(void) last_curve25519_onion_key.pubkey.public_key, CURVE25519_PUBKEY_LEN) && file_status(keydir) == FN_FILE) { + /* Load keys from non-empty files only. + * Missing old keys won't be replaced with freshly generated keys. */ init_curve25519_keypair_from_file(&last_curve25519_onion_key, keydir, 0, LOG_ERR, "onion"); } tor_free(keydir); } -#endif /* 3. Initialize link key and TLS context. */ if (router_initialize_tls_context() < 0) { @@ -857,14 +933,13 @@ init_keys(void) const char *m = NULL; routerinfo_t *ri; /* We need to add our own fingerprint so it gets recognized. */ - if (dirserv_add_own_fingerprint(options->Nickname, - get_server_identity_key())) { - log_err(LD_GENERAL,"Error adding own fingerprint to approved set"); + if (dirserv_add_own_fingerprint(get_server_identity_key())) { + log_err(LD_GENERAL,"Error adding own fingerprint to set of relays"); return -1; } if (mydesc) { was_router_added_t added; - ri = router_parse_entry_from_string(mydesc, NULL, 1, 0, NULL); + ri = router_parse_entry_from_string(mydesc, NULL, 1, 0, NULL, NULL); if (!ri) { log_err(LD_GENERAL,"Generated a routerinfo we couldn't parse."); return -1; @@ -888,40 +963,16 @@ init_keys(void) } } - /* 5. Dump fingerprint to 'fingerprint' */ - keydir = get_datadir_fname("fingerprint"); - log_info(LD_GENERAL,"Dumping fingerprint to \"%s\"...",keydir); - if (crypto_pk_get_fingerprint(get_server_identity_key(), - fingerprint, 0) < 0) { - log_err(LD_GENERAL,"Error computing fingerprint"); - tor_free(keydir); + /* 5. Dump fingerprint and possibly hashed fingerprint to files. */ + if (router_write_fingerprint(0)) { + log_err(LD_FS, "Error writing fingerprint to file"); return -1; } - tor_assert(strlen(options->Nickname) <= MAX_NICKNAME_LEN); - if (tor_snprintf(fingerprint_line, sizeof(fingerprint_line), - "%s %s\n",options->Nickname, fingerprint) < 0) { - log_err(LD_GENERAL,"Error writing fingerprint line"); - tor_free(keydir); + if (!public_server_mode(options) && router_write_fingerprint(1)) { + log_err(LD_FS, "Error writing hashed fingerprint to file"); return -1; } - /* Check whether we need to write the fingerprint file. */ - cp = NULL; - if (file_status(keydir) == FN_FILE) - cp = read_file_to_str(keydir, 0, NULL); - if (!cp || strcmp(cp, fingerprint_line)) { - if (write_str_to_file(keydir, fingerprint_line, 0)) { - log_err(LD_FS, "Error writing fingerprint line to file"); - tor_free(keydir); - tor_free(cp); - return -1; - } - } - tor_free(cp); - tor_free(keydir); - log_notice(LD_GENERAL, - "Your Tor server's identity key fingerprint is '%s %s'", - options->Nickname, fingerprint); if (!authdir_mode(options)) return 0; /* 6. [authdirserver only] load approved-routers file */ @@ -931,12 +982,9 @@ init_keys(void) } /* 6b. [authdirserver only] add own key to approved directories. */ crypto_pk_get_digest(get_server_identity_key(), digest); - type = ((options->V1AuthoritativeDir ? V1_DIRINFO : NO_DIRINFO) | - (options->V2AuthoritativeDir ? V2_DIRINFO : NO_DIRINFO) | - (options->V3AuthoritativeDir ? + type = ((options->V3AuthoritativeDir ? (V3_DIRINFO|MICRODESC_DIRINFO|EXTRAINFO_DIRINFO) : NO_DIRINFO) | - (options->BridgeAuthoritativeDir ? BRIDGE_DIRINFO : NO_DIRINFO) | - (options->HSAuthoritativeDir ? HIDSERV_DIRINFO : NO_DIRINFO)); + (options->BridgeAuthoritativeDir ? BRIDGE_DIRINFO : NO_DIRINFO)); ds = router_get_trusteddirserver_by_digest(digest); if (!ds) { @@ -1054,6 +1102,7 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) * they're confused or to get statistics. */ int interval_length = accounting_get_interval_length(); uint32_t effective_bw = get_effective_bwrate(options); + uint64_t acc_bytes; if (!interval_length) { log_warn(LD_BUG, "An accounting interval is not allowed to be zero " "seconds long. Raising to 1."); @@ -1064,8 +1113,12 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) "accounting interval length %d", effective_bw, U64_PRINTF_ARG(options->AccountingMax), interval_length); + + acc_bytes = options->AccountingMax; + if (get_options()->AccountingRule == ACCT_SUM) + acc_bytes /= 2; if (effective_bw >= - options->AccountingMax / interval_length) { + acc_bytes / interval_length) { new_choice = 0; reason = "AccountingMax enabled"; } @@ -1149,7 +1202,7 @@ consider_testing_reachability(int test_or, int test_dir) /* XXX IPv6 self testing */ log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.", !orport_reachable ? "reachability" : "bandwidth", - me->address, me->or_port); + fmt_addr32(me->addr), me->or_port); circuit_launch_by_extend_info(CIRCUIT_PURPOSE_TESTING, ei, CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); extend_info_free(ei); @@ -1161,7 +1214,7 @@ consider_testing_reachability(int test_or, int test_dir) CONN_TYPE_DIR, &addr, me->dir_port, DIR_PURPOSE_FETCH_SERVERDESC)) { /* ask myself, via tor, for my server descriptor. */ - directory_initiate_command(me->address, &addr, + directory_initiate_command(&addr, me->or_port, me->dir_port, me->cache_info.identity_digest, DIR_PURPOSE_FETCH_SERVERDESC, @@ -1176,15 +1229,22 @@ router_orport_found_reachable(void) { const routerinfo_t *me = router_get_my_routerinfo(); if (!can_reach_or_port && me) { + char *address = tor_dup_ip(me->addr); log_notice(LD_OR,"Self-testing indicates your ORPort is reachable from " "the outside. Excellent.%s", get_options()->PublishServerDescriptor_ != NO_DIRINFO ? " Publishing server descriptor." : ""); can_reach_or_port = 1; mark_my_descriptor_dirty("ORPort found reachable"); + /* This is a significant enough change to upload immediately, + * at least in a test network */ + if (get_options()->TestingTorNetwork == 1) { + reschedule_descriptor_update_check(); + } control_event_server_status(LOG_NOTICE, "REACHABILITY_SUCCEEDED ORADDRESS=%s:%d", - me->address, me->or_port); + address, me->or_port); + tor_free(address); } } @@ -1194,14 +1254,22 @@ router_dirport_found_reachable(void) { const routerinfo_t *me = router_get_my_routerinfo(); if (!can_reach_dir_port && me) { + char *address = tor_dup_ip(me->addr); log_notice(LD_DIRSERV,"Self-testing indicates your DirPort is reachable " "from the outside. Excellent."); can_reach_dir_port = 1; - if (decide_to_advertise_dirport(get_options(), me->dir_port)) + if (decide_to_advertise_dirport(get_options(), me->dir_port)) { mark_my_descriptor_dirty("DirPort found reachable"); + /* This is a significant enough change to upload immediately, + * at least in a test network */ + if (get_options()->TestingTorNetwork == 1) { + reschedule_descriptor_update_check(); + } + } control_event_server_status(LOG_NOTICE, "REACHABILITY_SUCCEEDED DIRADDRESS=%s:%d", - me->address, me->dir_port); + address, me->dir_port); + tor_free(address); } } @@ -1236,7 +1304,8 @@ router_perform_bandwidth_test(int num_circs, time_t now) } /** Return true iff our network is in some sense disabled: either we're - * hibernating, entering hibernation, or */ + * hibernating, entering hibernation, or the network is turned off with + * DisableNetwork. */ int net_is_disabled(void) { @@ -1251,22 +1320,6 @@ authdir_mode(const or_options_t *options) { return options->AuthoritativeDir != 0; } -/** Return true iff we believe ourselves to be a v1 authoritative - * directory server. - */ -int -authdir_mode_v1(const or_options_t *options) -{ - return authdir_mode(options) && options->V1AuthoritativeDir != 0; -} -/** Return true iff we believe ourselves to be a v2 authoritative - * directory server. - */ -int -authdir_mode_v2(const or_options_t *options) -{ - return authdir_mode(options) && options->V2AuthoritativeDir != 0; -} /** Return true iff we believe ourselves to be a v3 authoritative * directory server. */ @@ -1275,13 +1328,11 @@ authdir_mode_v3(const or_options_t *options) { return authdir_mode(options) && options->V3AuthoritativeDir != 0; } -/** Return true iff we are a v1, v2, or v3 directory authority. */ +/** Return true iff we are a v3 directory authority. */ int authdir_mode_any_main(const or_options_t *options) { - return options->V1AuthoritativeDir || - options->V2AuthoritativeDir || - options->V3AuthoritativeDir; + return options->V3AuthoritativeDir; } /** Return true if we believe ourselves to be any kind of * authoritative directory beyond just a hidserv authority. */ @@ -1335,8 +1386,8 @@ authdir_mode_bridge(const or_options_t *options) /** Return true iff we are trying to be a server. */ -int -server_mode(const or_options_t *options) +MOCK_IMPL(int, +server_mode,(const or_options_t *options)) { if (options->ClientOnly) return 0; /* XXXX024 I believe we can kill off ORListenAddress here.*/ @@ -1345,8 +1396,8 @@ server_mode(const or_options_t *options) /** Return true iff we are trying to be a non-bridge server. */ -int -public_server_mode(const or_options_t *options) +MOCK_IMPL(int, +public_server_mode,(const or_options_t *options)) { if (!server_mode(options)) return 0; return (!options->BridgeRelay); @@ -1674,22 +1725,10 @@ router_is_me(const routerinfo_t *router) return router_digest_is_me(router->cache_info.identity_digest); } -/** Return true iff <b>fp</b> is a hex fingerprint of my identity digest. */ -int -router_fingerprint_is_me(const char *fp) -{ - char digest[DIGEST_LEN]; - if (strlen(fp) == HEX_DIGEST_LEN && - base16_decode(digest, sizeof(digest), fp, HEX_DIGEST_LEN) == 0) - return router_digest_is_me(digest); - - return 0; -} - /** Return a routerinfo for this OR, rebuilding a fresh one if * necessary. Return NULL on error, or if called on an OP. */ -const routerinfo_t * -router_get_my_routerinfo(void) +MOCK_IMPL(const routerinfo_t *, +router_get_my_routerinfo,(void)) { if (!server_mode(get_options())) return NULL; @@ -1793,7 +1832,6 @@ router_rebuild_descriptor(int force) ri = tor_malloc_zero(sizeof(routerinfo_t)); ri->cache_info.routerlist_index = -1; - ri->address = tor_dup_ip(addr); ri->nickname = tor_strdup(options->Nickname); ri->addr = addr; ri->or_port = router_get_advertised_or_port(options); @@ -1801,19 +1839,17 @@ router_rebuild_descriptor(int force) ri->cache_info.published_on = time(NULL); ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from * main thread */ -#ifdef CURVE25519_ENABLED ri->onion_curve25519_pkey = tor_memdup(&get_current_curve25519_keypair()->pubkey, sizeof(curve25519_public_key_t)); -#endif /* For now, at most one IPv6 or-address is being advertised. */ { const port_cfg_t *ipv6_orport = NULL; SMARTLIST_FOREACH_BEGIN(get_configured_ports(), const port_cfg_t *, p) { if (p->type == CONN_TYPE_OR_LISTENER && - ! p->no_advertise && - ! p->bind_ipv4_only && + ! p->server_cfg.no_advertise && + ! p->server_cfg.bind_ipv4_only && tor_addr_family(&p->addr) == AF_INET6) { if (! tor_addr_is_internal(&p->addr, 0)) { ipv6_orport = p; @@ -1855,10 +1891,8 @@ router_rebuild_descriptor(int force) /* DNS is screwed up; don't claim to be an exit. */ policies_exit_policy_append_reject_star(&ri->exit_policy); } else { - policies_parse_exit_policy(options->ExitPolicy, &ri->exit_policy, - options->IPv6Exit, - options->ExitPolicyRejectPrivate, - ri->address, !options->BridgeRelay); + policies_parse_exit_policy_from_options(options,ri->addr, + &ri->exit_policy); } ri->policy_is_reject_star = policy_is_reject_star(ri->exit_policy, AF_INET) && @@ -1871,12 +1905,6 @@ router_rebuild_descriptor(int force) tor_free(p_tmp); } -#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 && ! options->BridgeRelay) { smartlist_t *family; if (!warned_nonexistent_family) @@ -1884,7 +1912,7 @@ router_rebuild_descriptor(int force) family = smartlist_new(); ri->declared_family = smartlist_new(); smartlist_split_string(family, options->MyFamily, ",", - SPLIT_SKIP_SPACE|SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0); SMARTLIST_FOREACH_BEGIN(family, char *, name) { const node_t *member; if (!strcasecmp(name, options->Nickname)) @@ -2068,7 +2096,8 @@ mark_my_descriptor_dirty(const char *reason) } /** How frequently will we republish our descriptor because of large (factor - * of 2) shifts in estimated bandwidth? */ + * of 2) shifts in estimated bandwidth? Note: We don't use this constant + * if our previous bandwidth estimate was exactly 0. */ #define MAX_BANDWIDTH_CHANGE_FREQ (20*60) /** Check whether bandwidth has changed a lot since the last time we announced @@ -2086,7 +2115,7 @@ check_descriptor_bandwidth_changed(time_t now) if ((prev != cur && (!prev || !cur)) || cur > prev*2 || cur < prev/2) { - if (last_changed+MAX_BANDWIDTH_CHANGE_FREQ < now) { + if (last_changed+MAX_BANDWIDTH_CHANGE_FREQ < now || !prev) { log_info(LD_GENERAL, "Measured bandwidth has changed; rebuilding descriptor."); mark_my_descriptor_dirty("bandwidth has changed"); @@ -2249,7 +2278,7 @@ router_guess_address_from_dir_headers(uint32_t *guess) * string describing the version of Tor and the operating system we're * currently running on. */ -void +STATIC void get_platform_str(char *platform, size_t len) { tor_snprintf(platform, len, "Tor %s on %s", @@ -2270,8 +2299,7 @@ char * router_dump_router_to_string(routerinfo_t *router, crypto_pk_t *ident_key) { - /* XXXX025 Make this look entirely at its arguments, and not at globals. - */ + char *address = NULL; char *onion_pkey = NULL; /* Onion key, PEM-encoded. */ char *identity_pkey = NULL; /* Identity key, PEM-encoded. */ char digest[DIGEST_LEN]; @@ -2345,7 +2373,9 @@ router_dump_router_to_string(routerinfo_t *router, } } + address = tor_dup_ip(router->addr); chunks = smartlist_new(); + /* Generate the easy portion of the router descriptor. */ smartlist_add_asprintf(chunks, "router %s %s %d 0 %d\n" @@ -2361,7 +2391,7 @@ router_dump_router_to_string(routerinfo_t *router, "signing-key\n%s" "%s%s%s%s", router->nickname, - router->address, + address, router->or_port, decide_to_advertise_dirport(options, router->dir_port), extra_or_address ? extra_or_address : "", @@ -2375,7 +2405,8 @@ router_dump_router_to_string(routerinfo_t *router, has_extra_info_digest ? "extra-info-digest " : "", has_extra_info_digest ? extra_info_digest : "", has_extra_info_digest ? "\n" : "", - options->DownloadExtraInfo ? "caches-extra-info\n" : "", + (options->DownloadExtraInfo || options->V3AuthoritativeDir) ? + "caches-extra-info\n" : "", onion_pkey, identity_pkey, family_line, we_are_hibernating() ? "hibernating 1\n" : "", @@ -2389,7 +2420,6 @@ router_dump_router_to_string(routerinfo_t *router, smartlist_add_asprintf(chunks, "contact %s\n", ci); } -#ifdef CURVE25519_ENABLED if (router->onion_curve25519_pkey) { char kbuf[128]; base64_encode(kbuf, sizeof(kbuf), @@ -2397,26 +2427,18 @@ router_dump_router_to_string(routerinfo_t *router, CURVE25519_PUBKEY_LEN); smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf); } -#endif /* Write the exit policy to the end of 's'. */ if (!router->exit_policy || !smartlist_len(router->exit_policy)) { smartlist_add(chunks, tor_strdup("reject *:*\n")); } else if (router->exit_policy) { - int i; - for (i = 0; i < smartlist_len(router->exit_policy); ++i) { - char pbuf[POLICY_BUF_LEN]; - addr_policy_t *tmpe = smartlist_get(router->exit_policy, i); - int result; - if (tor_addr_family(&tmpe->addr) == AF_INET6) - continue; /* Don't include IPv6 parts of address policy */ - result = policy_write_item(pbuf, POLICY_BUF_LEN, tmpe, 1); - if (result < 0) { - log_warn(LD_BUG,"descriptor policy_write_item ran out of room!"); - goto err; - } - smartlist_add_asprintf(chunks, "%s\n", pbuf); - } + char *exit_policy = router_dump_exit_policy_to_string(router,1,0); + + if (!exit_policy) + goto err; + + smartlist_add_asprintf(chunks, "%s\n", exit_policy); + tor_free(exit_policy); } if (router->ipv6_exit_policy) { @@ -2454,7 +2476,7 @@ router_dump_router_to_string(routerinfo_t *router, const char *cp; routerinfo_t *ri_tmp; cp = s_dup = tor_strdup(output); - ri_tmp = router_parse_entry_from_string(cp, NULL, 1, 0, NULL); + ri_tmp = router_parse_entry_from_string(cp, NULL, 1, 0, NULL, NULL); if (!ri_tmp) { log_err(LD_BUG, "We just generated a router descriptor we can't parse."); @@ -2475,6 +2497,7 @@ router_dump_router_to_string(routerinfo_t *router, SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); smartlist_free(chunks); } + tor_free(address); tor_free(family_line); tor_free(onion_pkey); tor_free(identity_pkey); @@ -2483,6 +2506,56 @@ router_dump_router_to_string(routerinfo_t *router, return output; } +/** + * OR only: Given <b>router</b>, produce a string with its exit policy. + * If <b>include_ipv4</b> is true, include IPv4 entries. + * If <b>include_ipv6</b> is true, include IPv6 entries. + */ +char * +router_dump_exit_policy_to_string(const routerinfo_t *router, + int include_ipv4, + int include_ipv6) +{ + smartlist_t *exit_policy_strings; + char *policy_string = NULL; + + if ((!router->exit_policy) || (router->policy_is_reject_star)) { + return tor_strdup("reject *:*"); + } + + exit_policy_strings = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(router->exit_policy, addr_policy_t *, tmpe) { + char *pbuf; + int bytes_written_to_pbuf; + if ((tor_addr_family(&tmpe->addr) == AF_INET6) && (!include_ipv6)) { + continue; /* Don't include IPv6 parts of address policy */ + } + if ((tor_addr_family(&tmpe->addr) == AF_INET) && (!include_ipv4)) { + continue; /* Don't include IPv4 parts of address policy */ + } + + pbuf = tor_malloc(POLICY_BUF_LEN); + bytes_written_to_pbuf = policy_write_item(pbuf,POLICY_BUF_LEN, tmpe, 1); + + if (bytes_written_to_pbuf < 0) { + log_warn(LD_BUG, "router_dump_exit_policy_to_string ran out of room!"); + tor_free(pbuf); + goto done; + } + + smartlist_add(exit_policy_strings,pbuf); + } SMARTLIST_FOREACH_END(tmpe); + + policy_string = smartlist_join_strings(exit_policy_strings, "\n", 0, NULL); + + done: + SMARTLIST_FOREACH(exit_policy_strings, char *, str, tor_free(str)); + smartlist_free(exit_policy_strings); + + return policy_string; +} + /** Copy the primary (IPv4) OR port (IP address and TCP port) for * <b>router</b> into *<b>ap_out</b>. */ void @@ -2517,8 +2590,9 @@ router_has_orport(const routerinfo_t *router, const tor_addr_port_t *orport) * <b>end_line</b>, ensure that its timestamp is not more than 25 hours in * the past or more than 1 hour in the future with respect to <b>now</b>, * and write the file contents starting with that line to *<b>out</b>. - * Return 1 for success, 0 if the file does not exist, or -1 if the file - * does not contain a line matching these criteria or other failure. */ + * Return 1 for success, 0 if the file does not exist or is empty, or -1 + * if the file does not contain a line matching these criteria or other + * failure. */ static int load_stats_file(const char *filename, const char *end_line, time_t now, char **out) @@ -2552,7 +2626,9 @@ load_stats_file(const char *filename, const char *end_line, time_t now, notfound: tor_free(contents); break; + /* treat empty stats files as if the file doesn't exist */ case FN_NOENT: + case FN_EMPTY: r = 0; break; case FN_ERROR: @@ -2609,6 +2685,11 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, "dirreq-stats-end", now, &contents) > 0) { smartlist_add(chunks, contents); } + if (options->HiddenServiceStatistics && + load_stats_file("stats"PATH_SEPARATOR"hidserv-stats", + "hidserv-stats-end", now, &contents) > 0) { + smartlist_add(chunks, contents); + } if (options->EntryStatistics && load_stats_file("stats"PATH_SEPARATOR"entry-stats", "entry-stats-end", now, &contents) > 0) { @@ -2685,7 +2766,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, s = smartlist_join_strings(chunks, "", 0, NULL); cp = s_dup = tor_strdup(s); - ei_tmp = extrainfo_parse_entry_from_string(cp, NULL, 1, NULL); + ei_tmp = extrainfo_parse_entry_from_string(cp, NULL, 1, NULL, NULL); if (!ei_tmp) { if (write_stats_to_extrainfo) { log_warn(LD_GENERAL, "We just generated an extra-info descriptor " @@ -3029,10 +3110,8 @@ router_free_all(void) crypto_pk_free(legacy_signing_key); authority_cert_free(legacy_key_certificate); -#ifdef CURVE25519_ENABLED memwipe(&curve25519_onion_key, 0, sizeof(curve25519_onion_key)); memwipe(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key)); -#endif if (warned_nonexistent_family) { SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp)); diff --git a/src/or/router.h b/src/or/router.h index 60095d087b..8108ffb22f 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,6 +12,8 @@ #ifndef TOR_ROUTER_H #define TOR_ROUTER_H +#include "testsupport.h" + crypto_pk_t *get_onion_key(void); time_t get_onion_key_set_at(void); void set_server_identity_key(crypto_pk_t *k); @@ -27,13 +29,11 @@ crypto_pk_t *get_my_v3_legacy_signing_key(void); void dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last); void rotate_onion_key(void); crypto_pk_t *init_key_from_file(const char *fname, int generate, - int severity); + int severity, int log_greeting); void v3_authority_check_key_expiry(void); -#ifdef CURVE25519_ENABLED di_digest256_map_t *construct_ntor_key_map(void); void ntor_key_map_free(di_digest256_map_t *map); -#endif int router_initialize_tls_context(void); int init_keys(void); @@ -48,8 +48,6 @@ void router_perform_bandwidth_test(int num_circs, time_t now); int net_is_disabled(void); int authdir_mode(const or_options_t *options); -int authdir_mode_v1(const or_options_t *options); -int authdir_mode_v2(const or_options_t *options); int authdir_mode_v3(const or_options_t *options); int authdir_mode_any_main(const or_options_t *options); int authdir_mode_any_nonhidserv(const or_options_t *options); @@ -66,8 +64,8 @@ uint16_t router_get_advertised_or_port_by_af(const or_options_t *options, uint16_t router_get_advertised_dir_port(const or_options_t *options, uint16_t dirport); -int server_mode(const or_options_t *options); -int public_server_mode(const or_options_t *options); +MOCK_DECL(int, server_mode, (const or_options_t *options)); +MOCK_DECL(int, public_server_mode, (const or_options_t *options)); int advertised_server_mode(void); int proxy_mode(const or_options_t *options); void consider_publishable_server(int force); @@ -82,7 +80,7 @@ void router_new_address_suggestion(const char *suggestion, const dir_connection_t *d_conn); int router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port); int router_my_exit_policy_is_reject_star(void); -const routerinfo_t *router_get_my_routerinfo(void); +MOCK_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); extrainfo_t *router_get_my_extrainfo(void); const char *router_get_my_descriptor(void); const char *router_get_descriptor_gen_reason(void); @@ -90,11 +88,13 @@ int router_digest_is_me(const char *digest); const uint8_t *router_get_my_id_digest(void); int router_extrainfo_digest_is_me(const char *digest); int router_is_me(const routerinfo_t *router); -int router_fingerprint_is_me(const char *fp); int router_pick_published_address(const or_options_t *options, uint32_t *addr); int router_rebuild_descriptor(int force); char *router_dump_router_to_string(routerinfo_t *router, crypto_pk_t *ident_key); +char *router_dump_exit_policy_to_string(const routerinfo_t *router, + int include_ipv4, + int include_ipv6); void router_get_prim_orport(const routerinfo_t *router, tor_addr_port_t *addr_port_out); void router_get_pref_orport(const routerinfo_t *router, @@ -146,7 +146,8 @@ smartlist_t *router_get_all_orports(const routerinfo_t *ri); #ifdef ROUTER_PRIVATE /* Used only by router.c and test.c */ -void get_platform_str(char *platform, size_t len); +STATIC void get_platform_str(char *platform, size_t len); +STATIC int router_write_fingerprint(int hashed); #endif #endif diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 9ad763c4d1..af8e68e880 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -37,7 +37,7 @@ #include "routerlist.h" #include "routerparse.h" #include "routerset.h" - +#include "../common/sandbox.h" // #define DEBUG_ROUTERLIST /****************************************************************************/ @@ -65,7 +65,7 @@ static int compute_weighted_bandwidths(const smartlist_t *sl, bandwidth_weight_rule_t rule, u64_dbl_t **bandwidths_out); static const routerstatus_t *router_pick_directory_server_impl( - dirinfo_type_t auth, int flags); + dirinfo_type_t auth, int flags, int *n_busy_out); static const routerstatus_t *router_pick_trusteddirserver_impl( const smartlist_t *sourcelist, dirinfo_type_t auth, int flags, int *n_busy_out); @@ -79,6 +79,7 @@ static const char *signed_descriptor_get_body_impl( const signed_descriptor_t *desc, int with_annotations); static void list_pending_downloads(digestmap_t *result, + digest256map_t *result256, int purpose, const char *prefix); static void list_pending_fpsk_downloads(fp_pair_map_t *result); static void launch_dummy_descriptor_download_as_needed(time_t now, @@ -98,7 +99,8 @@ static smartlist_t *trusted_dir_servers = NULL; * and all fallback directory servers. */ static smartlist_t *fallback_dir_servers = NULL; -/** List of for a given authority, and download status for latest certificate. +/** List of certificates for a single authority, and download status for + * latest certificate. */ struct cert_list_t { /* @@ -130,16 +132,6 @@ static smartlist_t *warned_nicknames = NULL; * download is low. */ static time_t last_descriptor_download_attempted = 0; -/** When we last computed the weights to use for bandwidths on directory - * requests, what were the total weighted bandwidth, and our share of that - * bandwidth? Used to determine what fraction of directory requests we should - * expect to see. - * - * @{ */ -static uint64_t sl_last_total_weighted_bw = 0, - sl_last_weighted_bw_of_me = 0; -/**@}*/ - /** Return the number of directory authorities whose type matches some bit set * in <b>type</b> */ int @@ -220,8 +212,6 @@ download_status_is_ready_by_sk_in_cl(cert_list_t *cl, return rv; } -#define get_n_v2_authorities() get_n_authorities(V2_DIRINFO) - /** Helper: Return the cert_list_t for an authority whose authority ID is * <b>id_digest</b>, allocating a new list if necessary. */ static cert_list_t * @@ -449,7 +439,7 @@ trusted_dirs_flush_certs_to_disk(void) } DIGESTMAP_FOREACH_END; filename = get_datadir_fname("cached-certs"); - if (write_chunks_to_file(filename, chunks, 0)) { + if (write_chunks_to_file(filename, chunks, 0, 0)) { log_warn(LD_FS, "Error writing certificates to disk."); } tor_free(filename); @@ -459,46 +449,69 @@ trusted_dirs_flush_certs_to_disk(void) trusted_dir_servers_certs_changed = 0; } -/** Remove all v3 authority certificates that have been superseded for more - * than 48 hours. (If the most recent cert was published more than 48 hours - * ago, then we aren't going to get any consensuses signed with older +static int +compare_certs_by_pubdates(const void **_a, const void **_b) +{ + const authority_cert_t *cert1 = *_a, *cert2=*_b; + + if (cert1->cache_info.published_on < cert2->cache_info.published_on) + return -1; + else if (cert1->cache_info.published_on > cert2->cache_info.published_on) + return 1; + else + return 0; +} + +/** Remove all expired v3 authority certificates that have been superseded for + * more than 48 hours or, if not expired, that were published more than 7 days + * before being superseded. (If the most recent cert was published more than 48 + * hours ago, then we aren't going to get any consensuses signed with older * keys.) */ static void 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 SUPERSEDED_CERT_LIFETIME (2*24*60*60) if (!trusted_dir_certs) return; DIGESTMAP_FOREACH(trusted_dir_certs, key, cert_list_t *, cl) { - authority_cert_t *newest = NULL; - SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, - if (!newest || (cert->cache_info.published_on > - newest->cache_info.published_on)) - newest = cert); - if (newest) { - const time_t newest_published = newest->cache_info.published_on; - SMARTLIST_FOREACH_BEGIN(cl->certs, authority_cert_t *, cert) { - int expired; - time_t cert_published; - if (newest == cert) - continue; - expired = now > cert->expires; - cert_published = cert->cache_info.published_on; - /* Store expired certs for 48 hours after a newer arrives; + /* Sort the list from first-published to last-published */ + smartlist_sort(cl->certs, compare_certs_by_pubdates); + + SMARTLIST_FOREACH_BEGIN(cl->certs, authority_cert_t *, cert) { + if (cert_sl_idx == smartlist_len(cl->certs) - 1) { + /* This is the most recently published cert. Keep it. */ + continue; + } + authority_cert_t *next_cert = smartlist_get(cl->certs, cert_sl_idx+1); + const time_t next_cert_published = next_cert->cache_info.published_on; + if (next_cert_published > now) { + /* All later certs are published in the future. Keep everything + * we didn't discard. */ + break; + } + int should_remove = 0; + if (cert->expires + DEAD_CERT_LIFETIME < now) { + /* Certificate has been expired for at least DEAD_CERT_LIFETIME. + * Remove it. */ + should_remove = 1; + } else if (next_cert_published + SUPERSEDED_CERT_LIFETIME < now) { + /* Certificate has been superseded for OLD_CERT_LIFETIME. + * Remove it. */ - if (expired ? - (newest_published + DEAD_CERT_LIFETIME < now) : - (cert_published + OLD_CERT_LIFETIME < newest_published)) { - SMARTLIST_DEL_CURRENT(cl->certs, cert); - authority_cert_free(cert); - trusted_dir_servers_certs_changed = 1; - } - } SMARTLIST_FOREACH_END(cert); - } + should_remove = 1; + } + if (should_remove) { + SMARTLIST_DEL_CURRENT_KEEPORDER(cl->certs, cert); + authority_cert_free(cert); + trusted_dir_servers_certs_changed = 1; + } + } SMARTLIST_FOREACH_END(cert); + } DIGESTMAP_FOREACH_END; +#undef DEAD_CERT_LIFETIME #undef OLD_CERT_LIFETIME trusted_dirs_flush_certs_to_disk(); @@ -681,9 +694,6 @@ authority_cert_dl_looks_uncertain(const char *id_digest) return n_failures >= N_AUTH_CERT_DL_FAILURES_TO_BUG_USER; } -/** How many times will we try to fetch a certificate before giving up? */ -#define MAX_CERT_DL_FAILURES 8 - /** Try to download any v3 authority certificates that we may be missing. If * <b>status</b> is provided, try to get all the ones that were used to sign * <b>status</b>. Additionally, try to have a non-expired certificate for @@ -715,7 +725,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) char id_digest_str[2*DIGEST_LEN+1]; char sk_digest_str[2*DIGEST_LEN+1]; - if (should_delay_dir_fetches(get_options())) + if (should_delay_dir_fetches(get_options(), NULL)) return; pending_cert = fp_pair_map_new(); @@ -727,7 +737,8 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) * First, we get the lists of already pending downloads so we don't * duplicate effort. */ - list_pending_downloads(pending_id, DIR_PURPOSE_FETCH_CERTIFICATE, "fp/"); + list_pending_downloads(pending_id, NULL, + DIR_PURPOSE_FETCH_CERTIFICATE, "fp/"); list_pending_fpsk_downloads(pending_cert); /* @@ -755,7 +766,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) } SMARTLIST_FOREACH_END(cert); if (!found && download_status_is_ready(&(cl->dl_status_by_id), now, - MAX_CERT_DL_FAILURES) && + get_options()->TestingCertMaxDownloadTries) && !digestmap_get(pending_id, ds->v3_identity_digest)) { log_info(LD_DIR, "No current certificate known for authority %s " @@ -817,7 +828,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) } if (download_status_is_ready_by_sk_in_cl( cl, sig->signing_key_digest, - now, MAX_CERT_DL_FAILURES) && + now, get_options()->TestingCertMaxDownloadTries) && !fp_pair_map_get_by_digests(pending_cert, voter->identity_digest, sig->signing_key_digest)) { @@ -1103,15 +1114,18 @@ router_rebuild_store(int flags, desc_store_t *store) smartlist_add(chunk_list, c); } SMARTLIST_FOREACH_END(sd); - if (write_chunks_to_file(fname_tmp, chunk_list, 1)<0) { + if (write_chunks_to_file(fname_tmp, chunk_list, 1, 1)<0) { log_warn(LD_FS, "Error writing router store to disk."); goto done; } /* Our mmap is now invalid. */ if (store->mmap) { - tor_munmap_file(store->mmap); + int res = tor_munmap_file(store->mmap); store->mmap = NULL; + if (res != 0) { + log_warn(LD_FS, "Unable to munmap route store in %s", fname); + } } if (replace_file(fname_tmp, fname)<0) { @@ -1178,32 +1192,25 @@ router_rebuild_store(int flags, desc_store_t *store) static int router_reload_router_list_impl(desc_store_t *store) { - char *fname = NULL, *altname = NULL, *contents = NULL; + char *fname = NULL, *contents = NULL; struct stat st; - int read_from_old_location = 0; int extrainfo = (store->type == EXTRAINFO_STORE); - time_t now = time(NULL); store->journal_len = store->store_len = 0; fname = get_datadir_fname(store->fname_base); - if (store->fname_alt_base) - altname = get_datadir_fname(store->fname_alt_base); - if (store->mmap) /* get rid of it first */ - tor_munmap_file(store->mmap); - store->mmap = NULL; + if (store->mmap) { + /* get rid of it first */ + int res = tor_munmap_file(store->mmap); + store->mmap = NULL; + if (res != 0) { + log_warn(LD_FS, "Failed to munmap %s", fname); + tor_free(fname); + return -1; + } + } store->mmap = tor_mmap_file(fname); - if (!store->mmap && altname && file_status(altname) == FN_FILE) { - read_from_old_location = 1; - log_notice(LD_DIR, "Couldn't read %s; trying to load routers from old " - "location %s.", fname, altname); - if ((store->mmap = tor_mmap_file(altname))) - read_from_old_location = 1; - } - if (altname && !read_from_old_location) { - remove_file_if_very_old(altname, now); - } if (store->mmap) { store->store_len = store->mmap->size; if (extrainfo) @@ -1218,16 +1225,9 @@ router_reload_router_list_impl(desc_store_t *store) tor_free(fname); fname = get_datadir_fname_suffix(store->fname_base, ".new"); + /* don't load empty files - we wouldn't get any data, even if we tried */ if (file_status(fname) == FN_FILE) contents = read_file_to_str(fname, RFTS_BIN|RFTS_IGNORE_MISSING, &st); - if (read_from_old_location) { - tor_free(altname); - altname = get_datadir_fname_suffix(store->fname_alt_base, ".new"); - if (!contents) - contents = read_file_to_str(altname, RFTS_BIN|RFTS_IGNORE_MISSING, &st); - else - remove_file_if_very_old(altname, now); - } if (contents) { if (extrainfo) router_load_extrainfo_from_string(contents, NULL,SAVED_IN_JOURNAL, @@ -1240,9 +1240,8 @@ router_reload_router_list_impl(desc_store_t *store) } tor_free(fname); - tor_free(altname); - if (store->journal_len || read_from_old_location) { + if (store->journal_len) { /* Always clear the journal on startup.*/ router_rebuild_store(RRS_FORCE, store); } else if (!extrainfo) { @@ -1308,68 +1307,35 @@ router_get_fallback_dir_servers(void) const routerstatus_t * router_pick_directory_server(dirinfo_type_t type, int flags) { + int busy = 0; const routerstatus_t *choice; - if (get_options()->PreferTunneledDirConns) - flags |= PDS_PREFER_TUNNELED_DIR_CONNS_; if (!routerlist) return NULL; - choice = router_pick_directory_server_impl(type, flags); + choice = router_pick_directory_server_impl(type, flags, &busy); if (choice || !(flags & PDS_RETRY_IF_NO_SERVERS)) return choice; + if (busy) { + /* 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| + PDS_NO_EXISTING_MICRODESC_FETCH))); + return NULL; + } + log_info(LD_DIR, "No reachable router entries for dirservers. " "Trying them all again."); /* mark all authdirservers as up again */ mark_all_dirservers_up(fallback_dir_servers); /* try again */ - choice = router_pick_directory_server_impl(type, flags); + choice = router_pick_directory_server_impl(type, flags, NULL); return choice; } -/** Try to determine which fraction of v2 and v3 directory requests aimed at - * caches will be sent to us. Set *<b>v2_share_out</b> and - * *<b>v3_share_out</b> to the fractions of v2 and v3 protocol shares we - * expect to see, respectively. Return 0 on success, negative on failure. */ -int -router_get_my_share_of_directory_requests(double *v2_share_out, - double *v3_share_out) -{ - 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) - return -1; - rs = router_get_consensus_status_by_id(me->cache_info.identity_digest); - if (!rs) - return -1; - - /* Calling for side effect */ - /* XXXX This is a bit of a kludge */ - if (rs->is_v2_dir) { - sl_last_total_weighted_bw = 0; - router_pick_directory_server(V2_DIRINFO, pds_flags); - if (sl_last_total_weighted_bw != 0) { - *v2_share_out = U64_TO_DBL(sl_last_weighted_bw_of_me) / - U64_TO_DBL(sl_last_total_weighted_bw); - } - } - - { - sl_last_total_weighted_bw = 0; - router_pick_directory_server(V3_DIRINFO, pds_flags); - if (sl_last_total_weighted_bw != 0) { - *v3_share_out = U64_TO_DBL(sl_last_weighted_bw_of_me) / - U64_TO_DBL(sl_last_total_weighted_bw); - } - } - - return 0; -} - /** Return the dir_server_t for the directory authority whose identity * key hashes to <b>digest</b>, or NULL if no such authority is known. */ @@ -1435,7 +1401,7 @@ router_pick_trusteddirserver(dirinfo_type_t type, int flags) return router_pick_dirserver_generic(trusted_dir_servers, type, flags); } -/** Try to find a running fallback directory Flags are as for +/** Try to find a running fallback directory. Flags are as for * router_pick_directory_server. */ const routerstatus_t * @@ -1444,7 +1410,7 @@ router_pick_fallback_dirserver(dirinfo_type_t type, int flags) return router_pick_dirserver_generic(fallback_dir_servers, type, flags); } -/** Try to find a running fallback directory Flags are as for +/** Try to find a running fallback directory. Flags are as for * router_pick_directory_server. */ static const routerstatus_t * @@ -1453,8 +1419,6 @@ router_pick_dirserver_generic(smartlist_t *sourcelist, { const routerstatus_t *choice; int busy = 0; - if (get_options()->PreferTunneledDirConns) - flags |= PDS_PREFER_TUNNELED_DIR_CONNS_; choice = router_pick_trusteddirserver_impl(sourcelist, type, flags, &busy); if (choice || !(flags & PDS_RETRY_IF_NO_SERVERS)) @@ -1478,14 +1442,15 @@ router_pick_dirserver_generic(smartlist_t *sourcelist, #define DIR_503_TIMEOUT (60*60) /** Pick a random running valid directory server/mirror from our - * routerlist. Arguments are as for router_pick_directory_server(), except - * that RETRY_IF_NO_SERVERS is ignored, and: + * routerlist. Arguments are as for router_pick_directory_server(), except: * - * If the PDS_PREFER_TUNNELED_DIR_CONNS_ flag is set, prefer directory servers - * that we can use with BEGINDIR. + * If <b>n_busy_out</b> is provided, set *<b>n_busy_out</b> to the number of + * directories that we excluded for no other reason than + * PDS_NO_EXISTING_SERVERDESC_FETCH or PDS_NO_EXISTING_MICRODESC_FETCH. */ static const routerstatus_t * -router_pick_directory_server_impl(dirinfo_type_t type, int flags) +router_pick_directory_server_impl(dirinfo_type_t type, int flags, + int *n_busy_out) { const or_options_t *options = get_options(); const node_t *result; @@ -1494,11 +1459,12 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) smartlist_t *overloaded_direct, *overloaded_tunnel; time_t now = time(NULL); const networkstatus_t *consensus = networkstatus_get_latest_consensus(); - int requireother = ! (flags & PDS_ALLOW_SELF); - int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL); - int prefer_tunnel = (flags & PDS_PREFER_TUNNELED_DIR_CONNS_); - int for_guard = (flags & PDS_FOR_GUARD); - int try_excluding = 1, n_excluded = 0; + const int requireother = ! (flags & PDS_ALLOW_SELF); + const int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL); + const int no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH); + const int no_microdesc_fetching = (flags & PDS_NO_EXISTING_MICRODESC_FETCH); + const int for_guard = (flags & PDS_FOR_GUARD); + int try_excluding = 1, n_excluded = 0, n_busy = 0; if (!consensus) return NULL; @@ -1514,7 +1480,7 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) /* Find all the running dirservers we know about. */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { - int is_trusted; + int is_trusted, is_trusted_extrainfo; int is_overloaded; tor_addr_t addr; const routerstatus_t *status = node->rs; @@ -1524,15 +1490,13 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) if (!node->is_running || !status->dir_port || !node->is_valid) continue; - if (node->is_bad_directory) - continue; if (requireother && router_digest_is_me(node->identity)) continue; is_trusted = router_digest_is_trusted_dir(node->identity); - if ((type & V2_DIRINFO) && !(node->rs->is_v2_dir || is_trusted)) - continue; + is_trusted_extrainfo = router_digest_is_trusted_dir_type( + node->identity, EXTRAINFO_DIRINFO); if ((type & EXTRAINFO_DIRINFO) && - !router_supports_extrainfo(node->identity, 0)) + !router_supports_extrainfo(node->identity, is_trusted_extrainfo)) continue; if ((type & MICRODESC_DIRINFO) && !is_trusted && !node->rs->version_supports_microdesc_cache) @@ -1553,12 +1517,28 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) } /* XXXX IP6 proposal 118 */ - tor_addr_from_ipv4h(&addr, node->rs->addr); + tor_addr_from_ipv4h(&addr, status->addr); + + if (no_serverdesc_fetching && ( + connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &addr, status->dir_port, DIR_PURPOSE_FETCH_SERVERDESC) + || connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &addr, status->dir_port, DIR_PURPOSE_FETCH_EXTRAINFO) + )) { + ++n_busy; + continue; + } + + if (no_microdesc_fetching && connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &addr, status->dir_port, DIR_PURPOSE_FETCH_MICRODESC) + ) { + ++n_busy; + continue; + } is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now; - if (prefer_tunnel && - (!fascistfirewall || + if ((!fascistfirewall || fascist_firewall_allows_address_or(&addr, status->or_port))) smartlist_add(is_trusted ? trusted_tunnel : is_overloaded ? overloaded_tunnel : tunnel, (void*)node); @@ -1594,14 +1574,19 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) smartlist_free(overloaded_direct); smartlist_free(overloaded_tunnel); - if (result == NULL && try_excluding && !options->StrictNodes && n_excluded) { + if (result == NULL && try_excluding && !options->StrictNodes && n_excluded + && !n_busy) { /* If we got no result, and we are excluding nodes, and StrictNodes is * not set, try again without excluding nodes. */ try_excluding = 0; n_excluded = 0; + n_busy = 0; goto retry_without_exclude; } + if (n_busy_out) + *n_busy_out = n_busy; + return result ? result->rs : NULL; } @@ -1615,7 +1600,7 @@ dirserver_choose_by_weight(const smartlist_t *servers, double authority_weight) u64_dbl_t *weights; const dir_server_t *ds; - weights = tor_malloc(sizeof(u64_dbl_t) * n); + weights = tor_calloc(n, sizeof(u64_dbl_t)); for (i = 0; i < n; ++i) { ds = smartlist_get(servers, i); weights[i].dbl = ds->weight; @@ -1645,7 +1630,6 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, 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); const double auth_weight = (sourcelist == fallback_dir_servers) ? @@ -1706,8 +1690,7 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, } } - if (prefer_tunnel && - d->or_port && + if (d->or_port && (!fascistfirewall || fascist_firewall_allows_address_or(&addr, d->or_port))) smartlist_add(is_overloaded ? overloaded_tunnel : tunnel, (void*)d); @@ -1763,7 +1746,6 @@ mark_all_dirservers_up(smartlist_t *server_list) routerstatus_t *rs; node_t *node; dir->is_running = 1; - download_status_reset(&dir->v2_ns_dl_status); node = node_get_mutable_by_id(dir->digest); if (node) node->is_running = 1; @@ -1818,7 +1800,7 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) /** Add every suitable node from our nodelist to <b>sl</b>, so that * we can pick a node for a circuit. */ -static void +void router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, int need_uptime, int need_capacity, int need_guard, int need_desc) @@ -1885,20 +1867,21 @@ router_get_advertised_bandwidth_capped(const routerinfo_t *router) * doubles, convert them to uint64_t, and try to scale them linearly so as to * much of the range of uint64_t. If <b>total_out</b> is provided, set it to * the sum of all elements in the array _before_ scaling. */ -/* private */ void +STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, uint64_t *total_out) { double total = 0.0; - double scale_factor; + double scale_factor = 0.0; int i; /* big, but far away from overflowing an int64_t */ -#define SCALE_TO_U64_MAX (INT64_MAX / 4) +#define SCALE_TO_U64_MAX ((int64_t) (INT64_MAX / 4)) for (i = 0; i < n_entries; ++i) total += entries[i].dbl; - scale_factor = SCALE_TO_U64_MAX / total; + if (total > 0.0) + scale_factor = SCALE_TO_U64_MAX / total; for (i = 0; i < n_entries; ++i) entries[i].u64 = tor_llround(entries[i].dbl * scale_factor); @@ -1928,7 +1911,7 @@ gt_i64_timei(uint64_t a, uint64_t b) * value, and return the index of that element. If all elements are 0, choose * an index at random. Return -1 on error. */ -/* private */ int +STATIC int choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries) { int i, i_chosen=-1, n_chosen=0; @@ -2021,8 +2004,7 @@ smartlist_choose_node_by_bandwidth_weights(const smartlist_t *sl, if (compute_weighted_bandwidths(sl, rule, &bandwidths) < 0) return NULL; - scale_array_elements_to_u64(bandwidths, smartlist_len(sl), - &sl_last_total_weighted_bw); + scale_array_elements_to_u64(bandwidths, smartlist_len(sl), NULL); { int idx = choose_array_element_by_weight(bandwidths, @@ -2046,6 +2028,7 @@ compute_weighted_bandwidths(const smartlist_t *sl, double Wg = -1, Wm = -1, We = -1, Wd = -1; double Wgb = -1, Wmb = -1, Web = -1, Wdb = -1; uint64_t weighted_bw = 0; + guardfraction_bandwidth_t guardfraction_bw; u64_dbl_t *bandwidths; /* Can't choose exit and guard at same time */ @@ -2112,9 +2095,10 @@ compute_weighted_bandwidths(const smartlist_t *sl, if (Wg < 0 || Wm < 0 || We < 0 || Wd < 0 || Wgb < 0 || Wmb < 0 || Wdb < 0 || Web < 0) { log_debug(LD_CIRC, - "Got negative bandwidth weights. Defaulting to old selection" + "Got negative bandwidth weights. Defaulting to naive selection" " algorithm."); - return -1; // Use old algorithm. + Wg = Wm = We = Wd = weight_scale; + Wgb = Wmb = Web = Wdb = weight_scale; } Wg /= weight_scale; @@ -2127,26 +2111,32 @@ compute_weighted_bandwidths(const smartlist_t *sl, Web /= weight_scale; Wdb /= weight_scale; - bandwidths = tor_malloc_zero(sizeof(u64_dbl_t)*smartlist_len(sl)); + bandwidths = tor_calloc(smartlist_len(sl), sizeof(u64_dbl_t)); // Cycle through smartlist and total the bandwidth. + static int warned_missing_bw = 0; SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { - int is_exit = 0, is_guard = 0, is_dir = 0, this_bw = 0, is_me = 0; + int is_exit = 0, is_guard = 0, is_dir = 0, this_bw = 0; double weight = 1; + double weight_without_guard_flag = 0; /* Used for guardfraction */ + double final_weight = 0; is_exit = node->is_exit && ! node->is_bad_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. */ - log_warn(LD_BUG, - "Consensus is not listing bandwidths. Defaulting back to " - "old router selection algorithm."); - return -1; + if (! warned_missing_bw) { + log_warn(LD_BUG, + "Consensus is missing some bandwidths. Using a naive " + "router selection algorithm"); + warned_missing_bw = 1; + } + this_bw = 30000; /* Chosen arbitrarily */ + } else { + this_bw = kb_to_bytes(node->rs->bandwidth_kb); } - this_bw = kb_to_bytes(node->rs->bandwidth_kb); } else if (node->ri) { /* bridge or other descriptor not in our consensus */ this_bw = bridge_get_advertised_bandwidth_bounded(node->ri); @@ -2154,12 +2144,13 @@ compute_weighted_bandwidths(const smartlist_t *sl, /* 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); + weight_without_guard_flag = (is_dir ? Web*We : We); } else if (is_guard) { weight = (is_dir ? Wgb*Wg : Wg); + weight_without_guard_flag = (is_dir ? Wmb*Wm : Wm); } else if (is_exit) { weight = (is_dir ? Web*We : We); } else { // middle @@ -2171,10 +2162,43 @@ compute_weighted_bandwidths(const smartlist_t *sl, this_bw = 0; if (weight < 0.0) weight = 0.0; + if (weight_without_guard_flag < 0.0) + weight_without_guard_flag = 0.0; + + /* If guardfraction information is available in the consensus, we + * want to calculate this router's bandwidth according to its + * guardfraction. Quoting from proposal236: + * + * Let Wpf denote the weight from the 'bandwidth-weights' line a + * client would apply to N for position p if it had the guard + * flag, Wpn the weight if it did not have the guard flag, and B the + * measured bandwidth of N in the consensus. Then instead of choosing + * N for position p proportionally to Wpf*B or Wpn*B, clients should + * choose N proportionally to F*Wpf*B + (1-F)*Wpn*B. + */ + if (node->rs && node->rs->has_guardfraction && rule != WEIGHT_FOR_GUARD) { + /* XXX The assert should actually check for is_guard. However, + * that crashes dirauths because of #13297. This should be + * equivalent: */ + tor_assert(node->rs->is_possible_guard); + + guard_get_guardfraction_bandwidth(&guardfraction_bw, + this_bw, + node->rs->guardfraction_percentage); + + /* Calculate final_weight = F*Wpf*B + (1-F)*Wpn*B */ + final_weight = + guardfraction_bw.guard_bw * weight + + guardfraction_bw.non_guard_bw * weight_without_guard_flag; + + log_debug(LD_GENERAL, "%s: Guardfraction weight %f instead of %f (%s)", + node->rs->nickname, final_weight, weight*this_bw, + bandwidth_weight_rule_to_string(rule)); + } else { /* no guardfraction information. calculate the weight normally. */ + final_weight = weight*this_bw; + } - bandwidths[node_sl_idx].dbl = weight*this_bw + 0.5; - if (is_me) - sl_last_weighted_bw_of_me = (uint64_t) bandwidths[node_sl_idx].dbl; + bandwidths[node_sl_idx].dbl = final_weight + 0.5; } SMARTLIST_FOREACH_END(node); log_debug(LD_CIRC, "Generated weighted bandwidths for rule %s based " @@ -2226,235 +2250,13 @@ frac_nodes_with_descriptors(const smartlist_t *sl, return present / total; } -/** Helper function: - * choose a random node_t element of smartlist <b>sl</b>, weighted by - * the advertised bandwidth of each element. - * - * 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 - * <b>rule</b>==NO_WEIGHTING, we're picking a non-exit node: weight - * exit-node's bandwidth less depending on the smallness of the fraction of - * Exit-to-total bandwidth. If <b>rule</b>==WEIGHT_FOR_GUARD, we're picking a - * guard node: consider all guard's bandwidth equally. Otherwise, weight - * guards proportionally less. - */ -static const node_t * -smartlist_choose_node_by_bandwidth(const smartlist_t *sl, - bandwidth_weight_rule_t rule) -{ - unsigned int i; - u64_dbl_t *bandwidths; - int is_exit; - int is_guard; - int is_fast; - double total_nonexit_bw = 0, total_exit_bw = 0; - double total_nonguard_bw = 0, total_guard_bw = 0; - double exit_weight; - double guard_weight; - int n_unknown = 0; - bitarray_t *fast_bits; - bitarray_t *exit_bits; - bitarray_t *guard_bits; - int me_idx = -1; - - // This function does not support WEIGHT_FOR_DIR - // or WEIGHT_FOR_MID - if (rule == WEIGHT_FOR_DIR || rule == WEIGHT_FOR_MID) { - rule = NO_WEIGHTING; - } - - /* Can't choose exit and guard at same time */ - tor_assert(rule == NO_WEIGHTING || - rule == WEIGHT_FOR_EXIT || - rule == WEIGHT_FOR_GUARD); - - if (smartlist_len(sl) == 0) { - log_info(LD_CIRC, - "Empty routerlist passed in to old node selection for rule %s", - bandwidth_weight_rule_to_string(rule)); - return NULL; - } - - /* First count the total bandwidth weight, and make a list - * of each value. We use UINT64_MAX to indicate "unknown". */ - bandwidths = tor_malloc_zero(sizeof(u64_dbl_t)*smartlist_len(sl)); - fast_bits = bitarray_init_zero(smartlist_len(sl)); - exit_bits = bitarray_init_zero(smartlist_len(sl)); - guard_bits = bitarray_init_zero(smartlist_len(sl)); - - /* Iterate over all the routerinfo_t or routerstatus_t, and */ - SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { - /* first, learn what bandwidth we think i has */ - int is_known = 1; - uint32_t this_bw = 0; - 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_kb); - } else { /* guess */ - is_known = 0; - } - } 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) - bitarray_set(guard_bits, i); - if (node->is_fast) - bitarray_set(fast_bits, i); - - if (is_known) { - bandwidths[i].dbl = this_bw; - if (is_guard) - total_guard_bw += this_bw; - else - total_nonguard_bw += this_bw; - if (is_exit) - total_exit_bw += this_bw; - else - total_nonexit_bw += this_bw; - } else { - ++n_unknown; - bandwidths[i].dbl = -1.0; - } - } SMARTLIST_FOREACH_END(node); - -#define EPSILON .1 - - /* Now, fill in the unknown values. */ - if (n_unknown) { - int32_t avg_fast, avg_slow; - if (total_exit_bw+total_nonexit_bw < EPSILON) { - /* if there's some bandwidth, there's at least one known router, - * so no worries about div by 0 here */ - int n_known = smartlist_len(sl)-n_unknown; - avg_fast = avg_slow = (int32_t) - ((total_exit_bw+total_nonexit_bw)/((uint64_t) n_known)); - } else { - avg_fast = 40000; - avg_slow = 20000; - } - for (i=0; i<(unsigned)smartlist_len(sl); ++i) { - if (bandwidths[i].dbl >= 0.0) - continue; - is_fast = bitarray_is_set(fast_bits, i); - is_exit = bitarray_is_set(exit_bits, i); - is_guard = bitarray_is_set(guard_bits, i); - bandwidths[i].dbl = is_fast ? avg_fast : avg_slow; - if (is_exit) - total_exit_bw += bandwidths[i].dbl; - else - total_nonexit_bw += bandwidths[i].dbl; - if (is_guard) - total_guard_bw += bandwidths[i].dbl; - else - total_nonguard_bw += bandwidths[i].dbl; - } - } - - /* If there's no bandwidth at all, pick at random. */ - if (total_exit_bw+total_nonexit_bw < EPSILON) { - tor_free(bandwidths); - tor_free(fast_bits); - tor_free(exit_bits); - tor_free(guard_bits); - return smartlist_choose(sl); - } - - /* Figure out how to weight exits and guards */ - { - double all_bw = U64_TO_DBL(total_exit_bw+total_nonexit_bw); - double exit_bw = U64_TO_DBL(total_exit_bw); - double guard_bw = U64_TO_DBL(total_guard_bw); - /* - * For detailed derivation of this formula, see - * http://archives.seul.org/or/dev/Jul-2007/msg00056.html - */ - if (rule == WEIGHT_FOR_EXIT || total_exit_bw<EPSILON) - exit_weight = 1.0; - else - exit_weight = 1.0 - all_bw/(3.0*exit_bw); - - if (rule == WEIGHT_FOR_GUARD || total_guard_bw<EPSILON) - guard_weight = 1.0; - else - guard_weight = 1.0 - all_bw/(3.0*guard_bw); - - if (exit_weight <= 0.0) - exit_weight = 0.0; - - if (guard_weight <= 0.0) - guard_weight = 0.0; - - sl_last_weighted_bw_of_me = 0; - for (i=0; i < (unsigned)smartlist_len(sl); i++) { - tor_assert(bandwidths[i].dbl >= 0.0); - - is_exit = bitarray_is_set(exit_bits, i); - is_guard = bitarray_is_set(guard_bits, i); - if (is_exit && is_guard) - bandwidths[i].dbl *= exit_weight * guard_weight; - else if (is_guard) - bandwidths[i].dbl *= guard_weight; - else if (is_exit) - bandwidths[i].dbl *= exit_weight; - - if (i == (unsigned) me_idx) - sl_last_weighted_bw_of_me = (uint64_t) bandwidths[i].dbl; - } - } - -#if 0 - log_debug(LD_CIRC, "Total weighted bw = "U64_FORMAT - ", exit bw = "U64_FORMAT - ", nonexit bw = "U64_FORMAT", exit weight = %f " - "(for exit == %d)" - ", guard bw = "U64_FORMAT - ", nonguard bw = "U64_FORMAT", guard weight = %f " - "(for guard == %d)", - U64_PRINTF_ARG(total_bw), - U64_PRINTF_ARG(total_exit_bw), U64_PRINTF_ARG(total_nonexit_bw), - exit_weight, (int)(rule == WEIGHT_FOR_EXIT), - U64_PRINTF_ARG(total_guard_bw), U64_PRINTF_ARG(total_nonguard_bw), - guard_weight, (int)(rule == WEIGHT_FOR_GUARD)); -#endif - - scale_array_elements_to_u64(bandwidths, smartlist_len(sl), - &sl_last_total_weighted_bw); - - { - int idx = choose_array_element_by_weight(bandwidths, - smartlist_len(sl)); - tor_free(bandwidths); - tor_free(fast_bits); - tor_free(exit_bits); - tor_free(guard_bits); - return idx < 0 ? NULL : smartlist_get(sl, idx); - } -} - /** Choose a random element of status list <b>sl</b>, weighted by * the advertised bandwidth of each node */ const node_t * node_sl_choose_by_bandwidth(const 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_node_by_bandwidth(sl, rule); - } + return smartlist_choose_node_by_bandwidth_weights(sl, rule); } /** Return a random running node from the nodelist. Never @@ -2512,11 +2314,29 @@ router_choose_random_node(smartlist_t *excludedsmartlist, router_add_running_nodes_to_smartlist(sl, allow_invalid, need_uptime, need_capacity, need_guard, need_desc); + log_debug(LD_CIRC, + "We found %d running nodes.", + smartlist_len(sl)); + smartlist_subtract(sl,excludednodes); - if (excludedsmartlist) + log_debug(LD_CIRC, + "We removed %d excludednodes, leaving %d nodes.", + smartlist_len(excludednodes), + smartlist_len(sl)); + + if (excludedsmartlist) { smartlist_subtract(sl,excludedsmartlist); - if (excludedset) + log_debug(LD_CIRC, + "We removed %d excludedsmartlist, leaving %d nodes.", + smartlist_len(excludedsmartlist), + smartlist_len(sl)); + } + if (excludedset) { routerset_subtract_nodes(sl,excludedset); + log_debug(LD_CIRC, + "We removed excludedset, leaving %d nodes.", + smartlist_len(sl)); + } // Always weight by bandwidth choice = node_sl_choose_by_bandwidth(sl, rule); @@ -2630,7 +2450,7 @@ router_is_named(const routerinfo_t *router) /** Return true iff <b>digest</b> is the digest of the identity key of a * trusted directory matching at least one bit of <b>type</b>. If <b>type</b> - * is zero, any authority is okay. */ + * is zero (NO_DIRINFO), or ALL_DIRINFO, any authority is okay. */ int router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type) { @@ -2711,8 +2531,8 @@ router_get_by_descriptor_digest(const char *digest) /** Return the signed descriptor for the router in our routerlist whose * 20-byte extra-info digest is <b>digest</b>. Return NULL if no such router * is known. */ -signed_descriptor_t * -router_get_by_extrainfo_digest(const char *digest) +MOCK_IMPL(signed_descriptor_t *, +router_get_by_extrainfo_digest,(const char *digest)) { tor_assert(digest); @@ -2822,7 +2642,6 @@ router_get_routerlist(void) routerlist->extra_info_map = eimap_new(); routerlist->desc_store.fname_base = "cached-descriptors"; - routerlist->desc_store.fname_alt_base = "cached-routers"; routerlist->extrainfo_store.fname_base = "cached-extrainfo"; routerlist->desc_store.type = ROUTER_STORE; @@ -2842,7 +2661,6 @@ routerinfo_free(routerinfo_t *router) return; tor_free(router->cache_info.signed_descriptor_body); - tor_free(router->address); tor_free(router->nickname); tor_free(router->platform); tor_free(router->contact_info); @@ -2928,10 +2746,18 @@ routerlist_free(routerlist_t *rl) signed_descriptor_free(sd)); smartlist_free(rl->routers); smartlist_free(rl->old_routers); - if (routerlist->desc_store.mmap) - tor_munmap_file(routerlist->desc_store.mmap); - if (routerlist->extrainfo_store.mmap) - tor_munmap_file(routerlist->extrainfo_store.mmap); + if (rl->desc_store.mmap) { + int res = tor_munmap_file(routerlist->desc_store.mmap); + if (res != 0) { + log_warn(LD_FS, "Failed to munmap routerlist->desc_store.mmap"); + } + } + if (rl->extrainfo_store.mmap) { + int res = tor_munmap_file(routerlist->extrainfo_store.mmap); + if (res != 0) { + log_warn(LD_FS, "Failed to munmap routerlist->extrainfo_store.mmap"); + } + } tor_free(rl); router_dir_info_changed(); @@ -3027,17 +2853,19 @@ routerlist_insert(routerlist_t *rl, routerinfo_t *ri) } /** Adds the extrainfo_t <b>ei</b> to the routerlist <b>rl</b>, if there is a - * corresponding router in rl-\>routers or rl-\>old_routers. Return true iff - * we actually inserted <b>ei</b>. Free <b>ei</b> if it isn't inserted. */ -static int -extrainfo_insert(routerlist_t *rl, extrainfo_t *ei) + * corresponding router in rl-\>routers or rl-\>old_routers. Return the status + * of inserting <b>ei</b>. Free <b>ei</b> if it isn't inserted. */ +MOCK_IMPL(STATIC was_router_added_t, +extrainfo_insert,(routerlist_t *rl, extrainfo_t *ei, int warn_if_incompatible)) { - int r = 0; + was_router_added_t r; + const char *compatibility_error_msg; routerinfo_t *ri = rimap_get(rl->identity_map, ei->cache_info.identity_digest); signed_descriptor_t *sd = sdmap_get(rl->desc_by_eid_map, ei->cache_info.signed_descriptor_digest); extrainfo_t *ei_tmp; + const int severity = warn_if_incompatible ? LOG_WARN : LOG_INFO; { extrainfo_t *ei_generated = router_get_my_extrainfo(); @@ -3046,9 +2874,41 @@ extrainfo_insert(routerlist_t *rl, extrainfo_t *ei) if (!ri) { /* This router is unknown; we can't even verify the signature. Give up.*/ + r = ROUTER_NOT_IN_CONSENSUS; goto done; } - if (routerinfo_incompatible_with_extrainfo(ri, ei, sd, NULL)) { + if (! sd) { + /* The extrainfo router doesn't have a known routerdesc to attach it to. + * This just won't work. */; + static ratelim_t no_sd_ratelim = RATELIM_INIT(1800); + r = ROUTER_BAD_EI; + log_fn_ratelim(&no_sd_ratelim, severity, LD_BUG, + "No entry found in extrainfo map."); + goto done; + } + if (tor_memneq(ei->cache_info.signed_descriptor_digest, + sd->extra_info_digest, DIGEST_LEN)) { + static ratelim_t digest_mismatch_ratelim = RATELIM_INIT(1800); + /* The sd we got from the map doesn't match the digest we used to look + * it up. This makes no sense. */ + r = ROUTER_BAD_EI; + log_fn_ratelim(&digest_mismatch_ratelim, severity, LD_BUG, + "Mismatch in digest in extrainfo map."); + goto done; + } + if (routerinfo_incompatible_with_extrainfo(ri, ei, sd, + &compatibility_error_msg)) { + char d1[HEX_DIGEST_LEN+1], d2[HEX_DIGEST_LEN+1]; + r = (ri->cache_info.extrainfo_is_bogus) ? + ROUTER_BAD_EI : ROUTER_NOT_IN_CONSENSUS; + + base16_encode(d1, sizeof(d1), ri->cache_info.identity_digest, DIGEST_LEN); + base16_encode(d2, sizeof(d2), ei->cache_info.identity_digest, DIGEST_LEN); + + log_fn(severity,LD_DIR, + "router info incompatible with extra info (ri id: %s, ei id %s, " + "reason: %s)", d1, d2, compatibility_error_msg); + goto done; } @@ -3058,7 +2918,7 @@ extrainfo_insert(routerlist_t *rl, extrainfo_t *ei) ei_tmp = eimap_set(rl->extra_info_map, ei->cache_info.signed_descriptor_digest, ei); - r = 1; + r = ROUTER_ADDED_SUCCESSFULLY; if (ei_tmp) { rl->extrainfo_store.bytes_dropped += ei_tmp->cache_info.signed_descriptor_len; @@ -3066,7 +2926,7 @@ extrainfo_insert(routerlist_t *rl, extrainfo_t *ei) } done: - if (r == 0) + if (r != ROUTER_ADDED_SUCCESSFULLY) extrainfo_free(ei); #ifdef DEBUG_ROUTERLIST @@ -3341,7 +3201,7 @@ routerlist_reparse_old(routerlist_t *rl, signed_descriptor_t *sd) ri = router_parse_entry_from_string(body, body+sd->signed_descriptor_len+sd->annotations_len, - 0, 1, NULL); + 0, 1, NULL, NULL); if (!ri) return NULL; memcpy(&ri->cache_info, sd, sizeof(signed_descriptor_t)); @@ -3387,6 +3247,14 @@ routerlist_reset_warnings(void) networkstatus_reset_warnings(); } +/** Return 1 if the signed descriptor of this router is older than + * <b>seconds</b> seconds. Otherwise return 0. */ +MOCK_IMPL(int, +router_descriptor_is_older_than,(const routerinfo_t *router, int seconds)) +{ + return router->cache_info.published_on < approx_time() - seconds; +} + /** Add <b>router</b> to the routerlist, if we don't already have it. Replace * older entries (if any) with the same key. Note: Callers should not hold * their pointers to <b>router</b> if this function fails; <b>router</b> @@ -3418,7 +3286,6 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, routerinfo_t *old_router; networkstatus_t *consensus = networkstatus_get_latest_consensus_by_flavor(FLAV_NS); - const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); int in_consensus = 0; tor_assert(msg); @@ -3454,7 +3321,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, router_describe(router)); *msg = "Router descriptor was not new."; routerinfo_free(router); - return ROUTER_WAS_NOT_NEW; + return ROUTER_IS_ALREADY_KNOWN; } } @@ -3489,15 +3356,6 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, } /* We no longer need a router with this descriptor digest. */ - SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, - { - routerstatus_t *rs = - networkstatus_v2_find_mutable_entry(ns, id_digest); - if (rs && tor_memeq(rs->descriptor_digest, - router->cache_info.signed_descriptor_digest, - DIGEST_LEN)) - rs->need_to_mirror = 0; - }); if (consensus) { routerstatus_t *rs = networkstatus_vote_find_mutable_entry( consensus, id_digest); @@ -3505,7 +3363,6 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, router->cache_info.signed_descriptor_digest, DIGEST_LEN)) { in_consensus = 1; - rs->need_to_mirror = 0; } } @@ -3549,7 +3406,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, &routerlist->desc_store); routerlist_insert_old(routerlist, router); *msg = "Router descriptor was not new."; - return ROUTER_WAS_NOT_NEW; + return ROUTER_IS_ALREADY_KNOWN; } else { /* Same key, and either new, or listed in the consensus. */ log_debug(LD_DIR, "Replacing entry for router %s", @@ -3559,7 +3416,6 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, signed_desc_append_to_journal(&router->cache_info, &routerlist->desc_store); } - directory_set_dirty(); *msg = authdir_believes_valid ? "Valid server updated" : ("Invalid server updated. (This dirserver is marking your " "server as unapproved.)"); @@ -3568,10 +3424,10 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, } if (!in_consensus && from_cache && - router->cache_info.published_on < time(NULL) - OLD_ROUTER_DESC_MAX_AGE) { + router_descriptor_is_older_than(router, OLD_ROUTER_DESC_MAX_AGE)) { *msg = "Router descriptor was really old."; routerinfo_free(router); - return ROUTER_WAS_NOT_NEW; + return ROUTER_WAS_TOO_OLD; } /* We haven't seen a router with this identity before. Add it to the end of @@ -3581,7 +3437,6 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, signed_desc_append_to_journal(&router->cache_info, &routerlist->desc_store); } - directory_set_dirty(); return ROUTER_ADDED_SUCCESSFULLY; } @@ -3593,21 +3448,18 @@ was_router_added_t router_add_extrainfo_to_routerlist(extrainfo_t *ei, const char **msg, int from_cache, int from_fetch) { - int inserted; + was_router_added_t inserted; (void)from_fetch; if (msg) *msg = NULL; /*XXXX023 Do something with msg */ - inserted = extrainfo_insert(router_get_routerlist(), ei); + inserted = extrainfo_insert(router_get_routerlist(), ei, !from_cache); - if (inserted && !from_cache) + if (WRA_WAS_ADDED(inserted) && !from_cache) signed_desc_append_to_journal(&ei->cache_info, &routerlist->extrainfo_store); - if (inserted) - return ROUTER_ADDED_SUCCESSFULLY; - else - return ROUTER_BAD_EI; + return inserted; } /** Sorting helper: return <0, 0, or >0 depending on whether the @@ -3677,9 +3529,9 @@ routerlist_remove_old_cached_routers_with_id(time_t now, n_extra = n - mdpr; } - lifespans = tor_malloc_zero(sizeof(struct duration_idx_t)*n); - rmv = tor_malloc_zero(sizeof(uint8_t)*n); - must_keep = tor_malloc_zero(sizeof(uint8_t)*n); + lifespans = tor_calloc(n, sizeof(struct duration_idx_t)); + rmv = tor_calloc(n, sizeof(uint8_t)); + must_keep = tor_calloc(n, sizeof(uint8_t)); /* Set lifespans to contain the lifespan and index of each server. */ /* Set rmv[i-lo]=1 if we're going to remove a server for being too old. */ for (i = lo; i <= hi; ++i) { @@ -3744,11 +3596,7 @@ routerlist_remove_old_routers(void) routerinfo_t *router; signed_descriptor_t *sd; digestset_t *retain; - int caches = directory_caches_dir_info(get_options()); const networkstatus_t *consensus = networkstatus_get_latest_consensus(); - const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); - int have_enough_v2; - const or_options_t *options = get_options(); trusted_dirs_remove_old_certs(); @@ -3764,38 +3612,10 @@ routerlist_remove_old_routers(void) { /* We'll probably retain everything in the consensus. */ int n_max_retain = smartlist_len(consensus->routerstatus_list); - if (caches && networkstatus_v2_list) { - /* If we care about v2 statuses, we'll retain at most as many as are - listed any of the v2 statues. This will be at least the length of - the largest v2 networkstatus, and in the worst case, this set will be - equal to the sum of the lengths of all v2 consensuses. Take the - worst case. - */ - SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, - n_max_retain += smartlist_len(ns->entries)); - } retain = digestset_new(n_max_retain); } cutoff = now - OLD_ROUTER_DESC_MAX_AGE; - /* Build a list of all the descriptors that _anybody_ lists. */ - if (caches && networkstatus_v2_list) { - SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - /* XXXX The inner loop here gets pretty expensive, and actually shows up - * on some profiles. It may be the reason digestmap_set shows up in - * profiles too. If instead we kept a per-descriptor digest count of - * how many networkstatuses recommended each descriptor, and changed - * that only when the networkstatuses changed, that would be a speed - * improvement, possibly 1-4% if it also removes digestmap_set from the - * profile. Not worth it for 0.1.2.x, though. The new directory - * system will obsolete this whole thing in 0.2.0.x. */ - SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) { - if (rs->published_on >= cutoff) - digestset_add(retain, rs->descriptor_digest); - } SMARTLIST_FOREACH_END(rs); - } SMARTLIST_FOREACH_END(ns); - } - /* Retain anything listed in the consensus. */ if (consensus) { SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs, @@ -3803,18 +3623,11 @@ routerlist_remove_old_routers(void) digestset_add(retain, rs->descriptor_digest)); } - /* If we have a consensus, and nearly as many v2 networkstatuses as we want, - * we should consider pruning current routers that are too old and that - * nobody recommends. (If we don't have a consensus or enough v2 - * networkstatuses, then we should get more before we decide to kill - * routers.) */ - /* we set this to true iff we don't care about v2 info, or we have enough. */ - have_enough_v2 = !caches || - !(authdir_mode_any_main(options) || options->FetchV2Networkstatus) || - (networkstatus_v2_list && - smartlist_len(networkstatus_v2_list) > get_n_v2_authorities() / 2); - - if (have_enough_v2 && consensus) { + /* If we have a consensus, we should consider pruning current routers that + * are too old and that nobody recommends. (If we don't have a consensus, + * then we should get one before we decide to kill routers.) */ + + if (consensus) { cutoff = now - ROUTER_MAX_AGE; /* Remove too-old unrecommended members of routerlist->routers. */ for (i = 0; i < smartlist_len(routerlist->routers); ++i) { @@ -3941,7 +3754,8 @@ router_load_single_router(const char *s, uint8_t purpose, int cache, "@source controller\n" "@purpose %s\n", router_purpose_to_string(purpose)); - if (!(ri = router_parse_entry_from_string(s, NULL, 1, 0, annotation_buf))) { + if (!(ri = router_parse_entry_from_string(s, NULL, 1, 0, + annotation_buf, NULL))) { log_warn(LD_DIR, "Error parsing router descriptor; dropping."); *msg = "Couldn't parse router descriptor."; return -1; @@ -4005,9 +3819,11 @@ router_load_routers_from_string(const char *s, const char *eos, int from_cache = (saved_location != SAVED_NOWHERE); int allow_annotations = (saved_location != SAVED_NOWHERE); int any_changed = 0; + smartlist_t *invalid_digests = smartlist_new(); router_parse_list_from_string(&s, eos, routers, saved_location, 0, - allow_annotations, prepend_annotations); + allow_annotations, prepend_annotations, + invalid_digests); routers_update_status_from_consensus_networkstatus(routers, !from_cache); @@ -4043,7 +3859,7 @@ router_load_routers_from_string(const char *s, const char *eos, smartlist_add(changed, ri); routerlist_descriptors_added(changed, from_cache); smartlist_clear(changed); - } else if (WRA_WAS_REJECTED(r)) { + } else if (WRA_NEVER_DOWNLOADABLE(r)) { download_status_t *dl_status; dl_status = router_get_dl_status_by_descriptor_digest(d); if (dl_status) { @@ -4054,6 +3870,27 @@ router_load_routers_from_string(const char *s, const char *eos, } } SMARTLIST_FOREACH_END(ri); + SMARTLIST_FOREACH_BEGIN(invalid_digests, const uint8_t *, bad_digest) { + /* This digest is never going to be parseable. */ + base16_encode(fp, sizeof(fp), (char*)bad_digest, DIGEST_LEN); + if (requested_fingerprints && descriptor_digests) { + if (! smartlist_contains_string(requested_fingerprints, fp)) { + /* But we didn't ask for it, so we should assume shennanegans. */ + continue; + } + smartlist_string_remove(requested_fingerprints, fp); + } + download_status_t *dls; + dls = router_get_dl_status_by_descriptor_digest((char*)bad_digest); + if (dls) { + log_info(LD_GENERAL, "Marking router with descriptor %s as unparseable, " + "and therefore undownloadable", fp); + download_status_mark_impossible(dls); + } + } SMARTLIST_FOREACH_END(bad_digest); + SMARTLIST_FOREACH(invalid_digests, uint8_t *, d, tor_free(d)); + smartlist_free(invalid_digests); + routerlist_assert_ok(routerlist); if (any_changed) @@ -4077,13 +3914,16 @@ router_load_extrainfo_from_string(const char *s, const char *eos, smartlist_t *extrainfo_list = smartlist_new(); const char *msg; int from_cache = (saved_location != SAVED_NOWHERE); + smartlist_t *invalid_digests = smartlist_new(); router_parse_list_from_string(&s, eos, extrainfo_list, saved_location, 1, 0, - NULL); + NULL, invalid_digests); log_info(LD_DIR, "%d elements to add", smartlist_len(extrainfo_list)); SMARTLIST_FOREACH_BEGIN(extrainfo_list, extrainfo_t *, ei) { + uint8_t d[DIGEST_LEN]; + memcpy(d, ei->cache_info.signed_descriptor_digest, DIGEST_LEN); was_router_added_t added = router_add_extrainfo_to_routerlist(ei, &msg, from_cache, !from_cache); if (WRA_WAS_ADDED(added) && requested_fingerprints) { @@ -4097,9 +3937,39 @@ router_load_extrainfo_from_string(const char *s, const char *eos, * so long as we would have wanted them anyway. Since we always fetch * all the extrainfos we want, and we never actually act on them * inside Tor, this should be harmless. */ + } else if (WRA_NEVER_DOWNLOADABLE(added)) { + signed_descriptor_t *sd = router_get_by_extrainfo_digest((char*)d); + if (sd) { + log_info(LD_GENERAL, "Marking extrainfo with descriptor %s as " + "unparseable, and therefore undownloadable", + hex_str((char*)d,DIGEST_LEN)); + download_status_mark_impossible(&sd->ei_dl_status); + } } } SMARTLIST_FOREACH_END(ei); + SMARTLIST_FOREACH_BEGIN(invalid_digests, const uint8_t *, bad_digest) { + /* This digest is never going to be parseable. */ + char fp[HEX_DIGEST_LEN+1]; + base16_encode(fp, sizeof(fp), (char*)bad_digest, DIGEST_LEN); + if (requested_fingerprints) { + if (! smartlist_contains_string(requested_fingerprints, fp)) { + /* But we didn't ask for it, so we should assume shennanegans. */ + continue; + } + smartlist_string_remove(requested_fingerprints, fp); + } + signed_descriptor_t *sd = + router_get_by_extrainfo_digest((char*)bad_digest); + if (sd) { + log_info(LD_GENERAL, "Marking extrainfo with descriptor %s as " + "unparseable, and therefore undownloadable", fp); + download_status_mark_impossible(&sd->ei_dl_status); + } + } SMARTLIST_FOREACH_END(bad_digest); + SMARTLIST_FOREACH(invalid_digests, uint8_t *, d, tor_free(d)); + smartlist_free(invalid_digests); + routerlist_assert_ok(routerlist); router_rebuild_store(0, &router_get_routerlist()->extrainfo_store); @@ -4113,8 +3983,6 @@ signed_desc_digest_is_recognized(signed_descriptor_t *desc) { 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(); if (consensus) { rs = networkstatus_vote_find_entry(consensus, desc->identity_digest); @@ -4122,16 +3990,6 @@ signed_desc_digest_is_recognized(signed_descriptor_t *desc) desc->signed_descriptor_digest, DIGEST_LEN)) return 1; } - if (caches && networkstatus_v2_list) { - SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, - { - if (!(rs = networkstatus_v2_find_entry(ns, desc->identity_digest))) - continue; - if (tor_memeq(rs->descriptor_digest, - desc->signed_descriptor_digest, DIGEST_LEN)) - return 1; - }); - } return 0; } @@ -4147,7 +4005,7 @@ update_all_descriptor_downloads(time_t now) launch_dummy_descriptor_download_as_needed(now, get_options()); } -/** Clear all our timeouts for fetching v2 and v3 directory stuff, and then +/** Clear all our timeouts for fetching v3 directory stuff, and then * give it all a try again. */ void routerlist_retry_directory_downloads(time_t now) @@ -4357,7 +4215,7 @@ clear_dir_servers(void) * corresponding elements of <b>result</b> to a nonzero value. */ static void -list_pending_downloads(digestmap_t *result, +list_pending_downloads(digestmap_t *result, digest256map_t *result256, int purpose, const char *prefix) { const size_t p_len = strlen(prefix); @@ -4367,7 +4225,7 @@ list_pending_downloads(digestmap_t *result, if (purpose == DIR_PURPOSE_FETCH_MICRODESC) flags = DSR_DIGEST256|DSR_BASE64; - tor_assert(result); + tor_assert(result || result256); SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (conn->type == CONN_TYPE_DIR && @@ -4380,11 +4238,19 @@ list_pending_downloads(digestmap_t *result, } } SMARTLIST_FOREACH_END(conn); - SMARTLIST_FOREACH(tmp, char *, d, + if (result) { + SMARTLIST_FOREACH(tmp, char *, d, { digestmap_set(result, d, (void*)1); tor_free(d); }); + } else if (result256) { + SMARTLIST_FOREACH(tmp, uint8_t *, d, + { + digest256map_set(result256, d, (void*)1); + tor_free(d); + }); + } smartlist_free(tmp); } @@ -4396,20 +4262,16 @@ list_pending_descriptor_downloads(digestmap_t *result, int extrainfo) { int purpose = extrainfo ? DIR_PURPOSE_FETCH_EXTRAINFO : DIR_PURPOSE_FETCH_SERVERDESC; - list_pending_downloads(result, purpose, "d/"); + list_pending_downloads(result, NULL, purpose, "d/"); } /** For every microdescriptor we are currently downloading by descriptor - * digest, set result[d] to (void*)1. (Note that microdescriptor digests - * are 256-bit, and digestmap_t only holds 160-bit digests, so we're only - * getting the first 20 bytes of each digest here.) - * - * XXXX Let there be a digestmap256_t, and use that instead. + * digest, set result[d] to (void*)1. */ void -list_pending_microdesc_downloads(digestmap_t *result) +list_pending_microdesc_downloads(digest256map_t *result) { - list_pending_downloads(result, DIR_PURPOSE_FETCH_MICRODESC, "d/"); + list_pending_downloads(NULL, result, DIR_PURPOSE_FETCH_MICRODESC, "d/"); } /** For every certificate we are currently downloading by (identity digest, @@ -4452,51 +4314,57 @@ list_pending_fpsk_downloads(fp_pair_map_t *result) * range.) If <b>source</b> is given, download from <b>source</b>; * otherwise, download from an appropriate random directory server. */ -static void -initiate_descriptor_downloads(const routerstatus_t *source, - int purpose, - smartlist_t *digests, - int lo, int hi, int pds_flags) +MOCK_IMPL(STATIC void, initiate_descriptor_downloads, + (const routerstatus_t *source, int purpose, smartlist_t *digests, + int lo, int hi, int pds_flags)) { - 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; + int digest_len, enc_digest_len; + const char *sep; + int b64_256; + smartlist_t *tmp; 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 = '-'; + enc_digest_len = BASE64_DIGEST256_LEN + 1; + sep = "-"; b64_256 = 1; + } else { + digest_len = DIGEST_LEN; + enc_digest_len = HEX_DIGEST_LEN + 1; + sep = "+"; + b64_256 = 0; } - if (n <= 0) - return; if (lo < 0) lo = 0; if (hi > smartlist_len(digests)) hi = smartlist_len(digests); - 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) { + if (hi-lo <= 0) + return; + + tmp = smartlist_new(); + + for (; lo < hi; ++lo) { + cp = tor_malloc(enc_digest_len); if (b64_256) { - digest256_to_base64(cp, smartlist_get(digests, i)); + digest256_to_base64(cp, smartlist_get(digests, lo)); } else { - base16_encode(cp, r_len-(cp-resource), - smartlist_get(digests,i), digest_len); + base16_encode(cp, enc_digest_len, smartlist_get(digests, lo), + digest_len); } - cp += enc_digest_len; - *cp++ = sep; + smartlist_add(tmp, cp); } - memcpy(cp-1, ".z", 3); + + cp = smartlist_join_strings(tmp, sep, 0, NULL); + tor_asprintf(&resource, "d/%s.z", cp); + + SMARTLIST_FOREACH(tmp, char *, cp1, tor_free(cp1)); + smartlist_free(tmp); + tor_free(cp); if (source) { /* We know which authority we want. */ @@ -4511,14 +4379,28 @@ initiate_descriptor_downloads(const routerstatus_t *source, tor_free(resource); } -/** Max amount of hashes to download per request. - * Since squid does not like URLs >= 4096 bytes we limit it to 96. - * 4096 - strlen(http://255.255.255.255/tor/server/d/.z) == 4058 - * 4058/41 (40 for the hash and 1 for the + that separates them) => 98 - * So use 96 because it's a nice number. +/** Return the max number of hashes to put in a URL for a given request. */ -#define MAX_DL_PER_REQUEST 96 -#define MAX_MICRODESC_DL_PER_REQUEST 92 +static int +max_dl_per_request(const or_options_t *options, int purpose) +{ + /* Since squid does not like URLs >= 4096 bytes we limit it to 96. + * 4096 - strlen(http://255.255.255.255/tor/server/d/.z) == 4058 + * 4058/41 (40 for the hash and 1 for the + that separates them) => 98 + * So use 96 because it's a nice number. + */ + int max = 96; + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { + max = 92; + } + /* If we're going to tunnel our connections, we can ask for a lot more + * in a request. */ + if (!directory_fetches_from_authorities(options)) { + max = 500; + } + return max; +} + /** Don't split our requests so finely that we are requesting fewer than * this number per server. */ #define MIN_DL_PER_REQUEST 4 @@ -4526,12 +4408,8 @@ initiate_descriptor_downloads(const routerstatus_t *source, * try to split our requests into at least this many requests. */ #define MIN_REQUESTS 3 /** If we want fewer than this many descriptors, wait until we - * want more, or until MAX_CLIENT_INTERVAL_WITHOUT_REQUEST has - * passed. */ + * want more, or until TestingClientMaxIntervalWithoutRequest has passed. */ #define MAX_DL_TO_DELAY 16 -/** When directory clients have only a few servers to request, they batch - * them until they have more, or until this amount of time has passed. */ -#define MAX_CLIENT_INTERVAL_WITHOUT_REQUEST (10*60) /** Given a <b>purpose</b> (FETCH_MICRODESC or FETCH_SERVERDESC) and a list of * router descriptor digests or microdescriptor digest256s in @@ -4544,238 +4422,89 @@ launch_descriptor_downloads(int purpose, smartlist_t *downloadable, const routerstatus_t *source, time_t now) { - int should_delay = 0, n_downloadable; const or_options_t *options = get_options(); const char *descname; + const int fetch_microdesc = (purpose == DIR_PURPOSE_FETCH_MICRODESC); + int n_downloadable = smartlist_len(downloadable); + + int i, n_per_request, max_dl_per_req; + const char *req_plural = "", *rtr_plural = ""; + int pds_flags = PDS_RETRY_IF_NO_SERVERS; - tor_assert(purpose == DIR_PURPOSE_FETCH_SERVERDESC || - purpose == DIR_PURPOSE_FETCH_MICRODESC); + tor_assert(fetch_microdesc || purpose == DIR_PURPOSE_FETCH_SERVERDESC); + descname = fetch_microdesc ? "microdesc" : "routerdesc"; - descname = (purpose == DIR_PURPOSE_FETCH_SERVERDESC) ? - "routerdesc" : "microdesc"; + if (!n_downloadable) + return; - 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 %ss to launch requests.", descname); - should_delay = 0; } else { - should_delay = (last_descriptor_download_attempted + - MAX_CLIENT_INTERVAL_WITHOUT_REQUEST) > now; - if (!should_delay && n_downloadable) { - if (last_descriptor_download_attempted) { - log_info(LD_DIR, - "There are not many downloadable %ss, but we've " - "been waiting long enough (%d seconds). Downloading.", - descname, - (int)(now-last_descriptor_download_attempted)); - } else { - log_info(LD_DIR, - "There are not many downloadable %ss, but we haven't " - "tried downloading descriptors recently. Downloading.", - descname); - } - } - } - } - if (! should_delay && n_downloadable) { - int i, n_per_request; - const char *req_plural = "", *rtr_plural = ""; - int pds_flags = PDS_RETRY_IF_NO_SERVERS; - if (! authdir_mode_any_nonhidserv(options)) { - /* If we wind up going to the authorities, we want to only open one - * connection to each authority at a time, so that we don't overload - * them. We do this by setting PDS_NO_EXISTING_SERVERDESC_FETCH - * regardless of whether we're a cache or not; it gets ignored if we're - * not calling router_pick_trusteddirserver. - * - * Setting this flag can make initiate_descriptor_downloads() ignore - * requests. We need to make sure that we do in fact call - * update_router_descriptor_downloads() later on, once the connections - * have succeeded or failed. - */ - pds_flags |= (purpose == DIR_PURPOSE_FETCH_MICRODESC) ? - PDS_NO_EXISTING_MICRODESC_FETCH : - PDS_NO_EXISTING_SERVERDESC_FETCH; - } + /* should delay */ + if ((last_descriptor_download_attempted + + options->TestingClientMaxIntervalWithoutRequest) > now) + return; - n_per_request = CEIL_DIV(n_downloadable, MIN_REQUESTS); - 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; - - if (n_downloadable > n_per_request) - req_plural = rtr_plural = "s"; - else if (n_downloadable > 1) - rtr_plural = "s"; - - log_info(LD_DIR, - "Launching %d request%s for %d %s%s, %d at a time", - CEIL_DIV(n_downloadable, n_per_request), req_plural, - n_downloadable, descname, rtr_plural, n_per_request); - smartlist_sort_digests(downloadable); - for (i=0; i < n_downloadable; i += n_per_request) { - initiate_descriptor_downloads(source, purpose, - downloadable, i, i+n_per_request, - pds_flags); + if (last_descriptor_download_attempted) { + log_info(LD_DIR, + "There are not many downloadable %ss, but we've " + "been waiting long enough (%d seconds). Downloading.", + descname, + (int)(now-last_descriptor_download_attempted)); + } else { + log_info(LD_DIR, + "There are not many downloadable %ss, but we haven't " + "tried downloading descriptors recently. Downloading.", + descname); + } } - last_descriptor_download_attempted = now; } -} -/** Launch downloads for router status as needed, using the strategy used by - * authorities and caches: based on the v2 networkstatuses we have, download - * every descriptor we don't have but would serve, from a random authority - * that lists it. */ -static void -update_router_descriptor_cache_downloads_v2(time_t now) -{ - smartlist_t **downloadable; /* For each authority, what can we dl from it? */ - smartlist_t **download_from; /* ... and, what will we dl from it? */ - digestmap_t *map; /* Which descs are in progress, or assigned? */ - int i, j, n; - int n_download; - const or_options_t *options = get_options(); - const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); - - if (! directory_fetches_dir_info_early(options)) { - log_warn(LD_BUG, "Called update_router_descriptor_cache_downloads_v2() " - "on a non-dir-mirror?"); + if (!authdir_mode_any_nonhidserv(options)) { + /* If we wind up going to the authorities, we want to only open one + * connection to each authority at a time, so that we don't overload + * them. We do this by setting PDS_NO_EXISTING_SERVERDESC_FETCH + * regardless of whether we're a cache or not. + * + * Setting this flag can make initiate_descriptor_downloads() ignore + * requests. We need to make sure that we do in fact call + * update_router_descriptor_downloads() later on, once the connections + * have succeeded or failed. + */ + pds_flags |= fetch_microdesc ? + PDS_NO_EXISTING_MICRODESC_FETCH : + PDS_NO_EXISTING_SERVERDESC_FETCH; } - if (!networkstatus_v2_list || !smartlist_len(networkstatus_v2_list)) - return; + n_per_request = CEIL_DIV(n_downloadable, MIN_REQUESTS); + max_dl_per_req = max_dl_per_request(options, purpose); - map = digestmap_new(); - n = smartlist_len(networkstatus_v2_list); + if (n_per_request > max_dl_per_req) + n_per_request = max_dl_per_req; - downloadable = tor_malloc_zero(sizeof(smartlist_t*) * n); - download_from = tor_malloc_zero(sizeof(smartlist_t*) * n); - - /* Set map[d]=1 for the digest of every descriptor that we are currently - * downloading. */ - list_pending_descriptor_downloads(map, 0); - - /* For the digest of every descriptor that we don't have, and that we aren't - * downloading, add d to downloadable[i] if the i'th networkstatus knows - * about that descriptor, and we haven't already failed to get that - * descriptor from the corresponding authority. - */ - n_download = 0; - SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - dir_server_t *ds; - smartlist_t *dl; - dl = downloadable[ns_sl_idx] = smartlist_new(); - download_from[ns_sl_idx] = smartlist_new(); - if (ns->published_on + MAX_NETWORKSTATUS_AGE+10*60 < now) { - /* Don't download if the networkstatus is almost ancient. */ - /* Actually, I suspect what's happening here is that we ask - * for the descriptor when we have a given networkstatus, - * and then we get a newer networkstatus, and then we receive - * the descriptor. Having a networkstatus actually expire is - * probably a rare event, and we'll probably be happiest if - * we take this clause out. -RD */ - continue; - } - - /* Don't try dirservers that we think are down -- we might have - * just tried them and just marked them as down. */ - ds = router_get_trusteddirserver_by_digest(ns->identity_digest); - if (ds && !ds->is_running) - continue; - - SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t * , rs) { - if (!rs->need_to_mirror) - continue; - if (router_get_by_descriptor_digest(rs->descriptor_digest)) { - log_warn(LD_BUG, - "We have a router descriptor, but need_to_mirror=1."); - rs->need_to_mirror = 0; - continue; - } - if (authdir_mode(options) && dirserv_would_reject_router(rs)) { - rs->need_to_mirror = 0; - continue; - } - if (digestmap_get(map, rs->descriptor_digest)) { - /* We're downloading it already. */ - continue; - } else { - /* We could download it from this guy. */ - smartlist_add(dl, rs->descriptor_digest); - ++n_download; - } - } SMARTLIST_FOREACH_END(rs); - } SMARTLIST_FOREACH_END(ns); - - /* At random, assign descriptors to authorities such that: - * - if d is a member of some downloadable[x], d is a member of some - * download_from[y]. (Everything we want to download, we try to download - * from somebody.) - * - If d is a member of download_from[y], d is a member of downloadable[y]. - * (We only try to download descriptors from authorities who claim to have - * them.) - * - No d is a member of download_from[x] and download_from[y] s.t. x != y. - * (We don't try to download anything from two authorities concurrently.) - */ - while (n_download) { - int which_ns = crypto_rand_int(n); - smartlist_t *dl = downloadable[which_ns]; - int idx; - char *d; - if (!smartlist_len(dl)) - continue; - idx = crypto_rand_int(smartlist_len(dl)); - d = smartlist_get(dl, idx); - if (! digestmap_get(map, d)) { - smartlist_add(download_from[which_ns], d); - digestmap_set(map, d, (void*) 1); - } - smartlist_del(dl, idx); - --n_download; - } + if (n_per_request < MIN_DL_PER_REQUEST) + n_per_request = MIN_DL_PER_REQUEST; - /* Now, we can actually launch our requests. */ - for (i=0; i<n; ++i) { - networkstatus_v2_t *ns = smartlist_get(networkstatus_v2_list, i); - dir_server_t *ds = - router_get_trusteddirserver_by_digest(ns->identity_digest); - smartlist_t *dl = download_from[i]; - int pds_flags = PDS_RETRY_IF_NO_SERVERS; - if (! authdir_mode_any_nonhidserv(options)) - pds_flags |= PDS_NO_EXISTING_SERVERDESC_FETCH; /* XXXX ignored*/ - - if (!ds) { - log_info(LD_DIR, "Networkstatus with no corresponding authority!"); - continue; - } - if (! smartlist_len(dl)) - continue; - log_info(LD_DIR, "Requesting %d descriptors from authority \"%s\"", - smartlist_len(dl), ds->nickname); - for (j=0; j < smartlist_len(dl); j += MAX_DL_PER_REQUEST) { - initiate_descriptor_downloads(&(ds->fake_status), - DIR_PURPOSE_FETCH_SERVERDESC, dl, j, - j+MAX_DL_PER_REQUEST, pds_flags); - } - } + if (n_downloadable > n_per_request) + req_plural = rtr_plural = "s"; + else if (n_downloadable > 1) + rtr_plural = "s"; - for (i=0; i<n; ++i) { - smartlist_free(download_from[i]); - smartlist_free(downloadable[i]); + log_info(LD_DIR, + "Launching %d request%s for %d %s%s, %d at a time", + CEIL_DIV(n_downloadable, n_per_request), req_plural, + n_downloadable, descname, rtr_plural, n_per_request); + smartlist_sort_digests(downloadable); + for (i=0; i < n_downloadable; i += n_per_request) { + initiate_descriptor_downloads(source, purpose, + downloadable, i, i+n_per_request, + pds_flags); } - tor_free(download_from); - tor_free(downloadable); - digestmap_free(map,NULL); + last_descriptor_download_attempted = now; } /** For any descriptor that we want that's currently listed in @@ -4836,7 +4565,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, continue; /* We have an in-progress download. */ } if (!download_status_is_ready(&rs->dl_status, now, - MAX_ROUTERDESC_DOWNLOAD_FAILURES)) { + options->TestingDescriptorMaxDownloadTries)) { ++n_delayed; /* Not ready for retry. */ continue; } @@ -4938,13 +4667,10 @@ void update_router_descriptor_downloads(time_t now) { const or_options_t *options = get_options(); - if (should_delay_dir_fetches(options)) + if (should_delay_dir_fetches(options, NULL)) return; if (!we_fetch_router_descriptors(options)) return; - 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, FLAV_NS)); @@ -4958,11 +4684,11 @@ update_extrainfo_downloads(time_t now) routerlist_t *rl; smartlist_t *wanted; digestmap_t *pending; - int old_routers, i; - int n_no_ei = 0, n_pending = 0, n_have = 0, n_delay = 0; + int old_routers, i, max_dl_per_req; + int n_no_ei = 0, n_pending = 0, n_have = 0, n_delay = 0, n_bogus[2] = {0,0}; if (! options->DownloadExtraInfo) return; - if (should_delay_dir_fetches(options)) + if (should_delay_dir_fetches(options, NULL)) return; if (!router_have_minimum_dir_info()) return; @@ -4996,7 +4722,7 @@ update_extrainfo_downloads(time_t now) continue; } if (!download_status_is_ready(&sd->ei_dl_status, now, - MAX_ROUTERDESC_DOWNLOAD_FAILURES)) { + options->TestingDescriptorMaxDownloadTries)) { ++n_delay; continue; } @@ -5004,19 +4730,54 @@ update_extrainfo_downloads(time_t now) ++n_pending; continue; } + + const signed_descriptor_t *sd2 = router_get_by_extrainfo_digest(d); + if (sd2 != sd) { + if (sd2 != NULL) { + char d1[HEX_DIGEST_LEN+1], d2[HEX_DIGEST_LEN+1]; + char d3[HEX_DIGEST_LEN+1], d4[HEX_DIGEST_LEN+1]; + base16_encode(d1, sizeof(d1), sd->identity_digest, DIGEST_LEN); + base16_encode(d2, sizeof(d2), sd2->identity_digest, DIGEST_LEN); + base16_encode(d3, sizeof(d3), d, DIGEST_LEN); + base16_encode(d4, sizeof(d3), sd2->extra_info_digest, DIGEST_LEN); + + log_info(LD_DIR, "Found an entry in %s with mismatched " + "router_get_by_extrainfo_digest() value. This has ID %s " + "but the entry in the map has ID %s. This has EI digest " + "%s and the entry in the map has EI digest %s.", + old_routers?"old_routers":"routers", + d1, d2, d3, d4); + } else { + char d1[HEX_DIGEST_LEN+1], d2[HEX_DIGEST_LEN+1]; + base16_encode(d1, sizeof(d1), sd->identity_digest, DIGEST_LEN); + base16_encode(d2, sizeof(d2), d, DIGEST_LEN); + + log_info(LD_DIR, "Found an entry in %s with NULL " + "router_get_by_extrainfo_digest() value. This has ID %s " + "and EI digest %s.", + old_routers?"old_routers":"routers", + d1, d2); + } + ++n_bogus[old_routers]; + continue; + } smartlist_add(wanted, d); } } digestmap_free(pending, NULL); log_info(LD_DIR, "Extrainfo download status: %d router with no ei, %d " - "with present ei, %d delaying, %d pending, %d downloadable.", - n_no_ei, n_have, n_delay, n_pending, smartlist_len(wanted)); + "with present ei, %d delaying, %d pending, %d downloadable, %d " + "bogus in routers, %d bogus in old_routers", + n_no_ei, n_have, n_delay, n_pending, smartlist_len(wanted), + n_bogus[0], n_bogus[1]); smartlist_shuffle(wanted); - for (i = 0; i < smartlist_len(wanted); i += MAX_DL_PER_REQUEST) { + + max_dl_per_req = max_dl_per_request(options, DIR_PURPOSE_FETCH_EXTRAINFO); + for (i = 0; i < smartlist_len(wanted); i += max_dl_per_req) { initiate_descriptor_downloads(NULL, DIR_PURPOSE_FETCH_EXTRAINFO, - wanted, i, i + MAX_DL_PER_REQUEST, + wanted, i, i+max_dl_per_req, PDS_RETRY_IF_NO_SERVERS|PDS_NO_EXISTING_SERVERDESC_FETCH); } @@ -5068,7 +4829,7 @@ router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2) } /* If any key fields differ, they're different. */ - if (strcasecmp(r1->address, r2->address) || + if (r1->addr != r2->addr || strcasecmp(r1->nickname, r2->nickname) || r1->or_port != r2->or_port || !tor_addr_eq(&r1->ipv6_addr, &r2->ipv6_addr) || @@ -5250,7 +5011,7 @@ routerlist_assert_ok(const routerlist_t *rl) } SMARTLIST_FOREACH_END(r); SMARTLIST_FOREACH_BEGIN(rl->old_routers, signed_descriptor_t *, sd) { r2 = rimap_get(rl->identity_map, sd->identity_digest); - tor_assert(sd != &(r2->cache_info)); + tor_assert(!r2 || sd != &(r2->cache_info)); sd2 = sdmap_get(rl->desc_digest_map, sd->signed_descriptor_digest); tor_assert(sd == sd2); tor_assert(sd->routerlist_index == sd_sl_idx); diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 505685897f..78c3fbb880 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -11,6 +11,8 @@ #ifndef TOR_ROUTERLIST_H #define TOR_ROUTERLIST_H +#include "testsupport.h" + int get_n_authorities(dirinfo_type_t type); int trusted_dirs_reload_certs(void); @@ -53,10 +55,13 @@ const routerstatus_t *router_pick_trusteddirserver(dirinfo_type_t type, int flags); const routerstatus_t *router_pick_fallback_dirserver(dirinfo_type_t type, int flags); -int router_get_my_share_of_directory_requests(double *v2_share_out, - double *v3_share_out); +int router_get_my_share_of_directory_requests(double *v3_share_out); void router_reset_status_download_failures(void); int routers_have_same_or_addrs(const routerinfo_t *r1, const routerinfo_t *r2); +void router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, + int need_uptime, int need_capacity, + int need_guard, int need_desc); + const routerinfo_t *routerlist_find_my_routerinfo(void); uint32_t router_get_advertised_bandwidth(const routerinfo_t *router); uint32_t router_get_advertised_bandwidth_capped(const routerinfo_t *router); @@ -81,7 +86,8 @@ int hexdigest_to_digest(const char *hexdigest, char *digest); 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); +MOCK_DECL(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(const signed_descriptor_t *desc); const char *signed_descriptor_get_annotations(const signed_descriptor_t *desc); @@ -98,6 +104,7 @@ void routerlist_reset_warnings(void); static int WRA_WAS_ADDED(was_router_added_t s); static int WRA_WAS_OUTDATED(was_router_added_t s); static int WRA_WAS_REJECTED(was_router_added_t s); +static int WRA_NEVER_DOWNLOADABLE(was_router_added_t s); /** Return true iff the outcome code in <b>s</b> indicates that the descriptor * was added. It might still be necessary to check whether the descriptor * generator should be notified. @@ -114,7 +121,8 @@ WRA_WAS_ADDED(was_router_added_t s) { */ static INLINE int WRA_WAS_OUTDATED(was_router_added_t s) { - return (s == ROUTER_WAS_NOT_NEW || + return (s == ROUTER_WAS_TOO_OLD || + s == ROUTER_IS_ALREADY_KNOWN || s == ROUTER_NOT_IN_CONSENSUS || s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS); } @@ -124,6 +132,14 @@ static INLINE int WRA_WAS_REJECTED(was_router_added_t s) { return (s == ROUTER_AUTHDIR_REJECTS); } +/** Return true iff the outcome code in <b>s</b> indicates that the descriptor + * was flat-out rejected. */ +static INLINE int WRA_NEVER_DOWNLOADABLE(was_router_added_t s) +{ + return (s == ROUTER_AUTHDIR_REJECTS || + s == ROUTER_BAD_EI || + s == ROUTER_WAS_TOO_OLD); +} was_router_added_t router_add_to_routerlist(routerinfo_t *router, const char **msg, int from_cache, @@ -184,7 +200,7 @@ 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 list_pending_microdesc_downloads(digest256map_t *result); void launch_descriptor_downloads(int purpose, smartlist_t *downloadable, const routerstatus_t *source, @@ -207,9 +223,20 @@ typedef union u64_dbl_t { double dbl; } u64_dbl_t; -int choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries); -void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, - uint64_t *total_out); +STATIC int choose_array_element_by_weight(const u64_dbl_t *entries, + int n_entries); +STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, + uint64_t *total_out); + +MOCK_DECL(int, router_descriptor_is_older_than, (const routerinfo_t *router, + int seconds)); +MOCK_DECL(STATIC was_router_added_t, extrainfo_insert, + (routerlist_t *rl, extrainfo_t *ei, int warn_if_incompatible)); + +MOCK_DECL(STATIC void, initiate_descriptor_downloads, + (const routerstatus_t *source, int purpose, smartlist_t *digests, + int lo, int hi, int pds_flags)); + #endif #endif diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 176c16f904..dcf419798a 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -1,7 +1,7 @@ -/* Copyright (c) 2001 Matej Pfajfar. + /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -9,6 +9,8 @@ * \brief Code to parse and validate router descriptors and directories. **/ +#define ROUTERPARSE_PRIVATE + #include "or.h" #include "config.h" #include "circuitstats.h" @@ -23,6 +25,7 @@ #include "networkstatus.h" #include "rephist.h" #include "routerparse.h" +#include "entrynodes.h" #undef log #include <math.h> @@ -131,6 +134,7 @@ typedef enum { K_CONSENSUS_METHOD, K_LEGACY_DIR_KEY, K_DIRECTORY_FOOTER, + K_PACKAGE, A_PURPOSE, A_LAST_LISTED, @@ -339,7 +343,7 @@ static token_rule_t extrainfo_token_table[] = { END_OF_TABLE }; -/** List of tokens recognized in the body part of v2 and v3 networkstatus +/** List of tokens recognized in the body part of v3 networkstatus * documents. */ static token_rule_t rtrstatus_token_table[] = { T01("p", K_P, CONCAT_ARGS, NO_OBJ ), @@ -353,31 +357,6 @@ static token_rule_t rtrstatus_token_table[] = { END_OF_TABLE }; -/** List of tokens recognized in the header part of v2 networkstatus documents. - */ -static token_rule_t netstatus_token_table[] = { - T1( "published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), - T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), - T1( "contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ), - T1( "dir-signing-key", K_DIR_SIGNING_KEY, NO_ARGS, NEED_KEY_1024 ), - T1( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), - T1_START("network-status-version", K_NETWORK_STATUS_VERSION, - GE(1), NO_OBJ ), - T1( "dir-source", K_DIR_SOURCE, GE(3), NO_OBJ ), - T01("dir-options", K_DIR_OPTIONS, ARGS, NO_OBJ ), - T01("client-versions", K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ), - T01("server-versions", K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ), - - END_OF_TABLE -}; - -/** List of tokens recognized in the footer of v1/v2 directory/networkstatus - * footers. */ -static token_rule_t dir_footer_token_table[] = { - T1("directory-signature", K_DIRECTORY_SIGNATURE, EQ(1), NEED_OBJ ), - END_OF_TABLE -}; - /** List of tokens common to V3 authority certificates and V3 consensuses. */ #define CERTIFICATE_MEMBERS \ T1("dir-key-certificate-version", K_DIR_KEY_CERTIFICATE_VERSION, \ @@ -386,7 +365,7 @@ static token_rule_t dir_footer_token_table[] = { T1("dir-key-published",K_DIR_KEY_PUBLISHED, CONCAT_ARGS, NO_OBJ), \ T1("dir-key-expires", K_DIR_KEY_EXPIRES, CONCAT_ARGS, NO_OBJ), \ T1("dir-signing-key", K_DIR_SIGNING_KEY, NO_ARGS, NEED_KEY ),\ - T01("dir-key-crosscert", K_DIR_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),\ + T1("dir-key-crosscert", K_DIR_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),\ T1("dir-key-certification", K_DIR_KEY_CERTIFICATION, \ NO_ARGS, NEED_OBJ), \ T01("dir-address", K_DIR_ADDRESS, GE(1), NO_OBJ), @@ -445,6 +424,7 @@ static token_rule_t networkstatus_token_table[] = { T1("known-flags", K_KNOWN_FLAGS, ARGS, NO_OBJ ), T01("params", K_PARAMS, ARGS, NO_OBJ ), T( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), + T0N("package", K_PACKAGE, CONCAT_ARGS, NO_OBJ ), CERTIFICATE_MEMBERS @@ -486,8 +466,7 @@ static token_rule_t networkstatus_consensus_token_table[] = { END_OF_TABLE }; -/** List of tokens recognized in the footer of v1/v2 directory/networkstatus - * footers. */ +/** List of tokens recognized in the footer of v1 directory footers. */ static token_rule_t networkstatus_vote_footer_token_table[] = { T01("directory-footer", K_DIRECTORY_FOOTER, NO_ARGS, NO_OBJ ), T01("bandwidth-weights", K_BW_WEIGHTS, ARGS, NO_OBJ ), @@ -598,7 +577,7 @@ dump_desc(const char *desc, const char *type) char *content = tor_malloc_zero(filelen); tor_snprintf(content, filelen, "Unable to parse descriptor of type " "%s:\n%s", type, desc); - write_str_to_file(debugfile, content, 0); + write_str_to_file(debugfile, content, 1); log_info(LD_DIR, "Unable to parse descriptor of type %s. See file " "unparseable-desc in data directory for details.", type); tor_free(content); @@ -629,28 +608,6 @@ router_get_router_hash(const char *s, size_t s_len, char *digest) DIGEST_SHA1); } -/** Set <b>digest</b> to the SHA-1 digest of the hash of the running-routers - * string in <b>s</b>. Return 0 on success, -1 on failure. - */ -int -router_get_runningrouters_hash(const char *s, char *digest) -{ - return router_get_hash_impl(s, strlen(s), digest, - "network-status","\ndirectory-signature", '\n', - DIGEST_SHA1); -} - -/** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status - * string in <b>s</b>. Return 0 on success, -1 on failure. */ -int -router_get_networkstatus_v2_hash(const char *s, char *digest) -{ - return router_get_hash_impl(s, strlen(s), digest, - "network-status-version","\ndirectory-signature", - '\n', - DIGEST_SHA1); -} - /** Set <b>digests</b> to all the digests of the consensus document in * <b>s</b> */ int @@ -728,7 +685,7 @@ router_get_dirobj_signature(const char *digest, /** Helper: used to generate signatures for routers, directories and * network-status objects. Given a digest in <b>digest</b> and a secret - * <b>private_key</b>, generate an PKCS1-padded signature, BASE64-encode it, + * <b>private_key</b>, generate a PKCS1-padded signature, BASE64-encode it, * surround it with -----BEGIN/END----- pairs, and write it to the * <b>buf_len</b>-byte buffer at <b>buf</b>. Return 0 on success, -1 on * failure. @@ -751,6 +708,7 @@ router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest, return -1; } memcpy(buf+s_len, sig, sig_len+1); + tor_free(sig); return 0; } @@ -958,7 +916,9 @@ find_start_of_next_router_or_extrainfo(const char **s_ptr, * descriptor in the signed_descriptor_body field of each routerinfo_t. If it * isn't SAVED_NOWHERE, remember the offset of each descriptor. * - * Returns 0 on success and -1 on failure. + * Returns 0 on success and -1 on failure. Adds a digest to + * <b>invalid_digests_out</b> for every entry that was unparseable or + * invalid. (This may cause duplicate entries.) */ int router_parse_list_from_string(const char **s, const char *eos, @@ -966,11 +926,12 @@ router_parse_list_from_string(const char **s, const char *eos, saved_location_t saved_location, int want_extrainfo, int allow_annotations, - const char *prepend_annotations) + const char *prepend_annotations, + smartlist_t *invalid_digests_out) { routerinfo_t *router; extrainfo_t *extrainfo; - signed_descriptor_t *signed_desc; + signed_descriptor_t *signed_desc = NULL; void *elt; const char *end, *start; int have_extrainfo; @@ -986,6 +947,9 @@ router_parse_list_from_string(const char **s, const char *eos, tor_assert(eos >= *s); while (1) { + char raw_digest[DIGEST_LEN]; + int have_raw_digest = 0; + int dl_again = 0; if (find_start_of_next_router_or_extrainfo(s, eos, &have_extrainfo) < 0) break; @@ -1002,18 +966,20 @@ router_parse_list_from_string(const char **s, const char *eos, if (have_extrainfo && want_extrainfo) { routerlist_t *rl = router_get_routerlist(); + have_raw_digest = router_get_extrainfo_hash(*s, end-*s, raw_digest) == 0; extrainfo = extrainfo_parse_entry_from_string(*s, end, saved_location != SAVED_IN_CACHE, - rl->identity_map); + rl->identity_map, &dl_again); if (extrainfo) { signed_desc = &extrainfo->cache_info; elt = extrainfo; } } else if (!have_extrainfo && !want_extrainfo) { + have_raw_digest = router_get_router_hash(*s, end-*s, raw_digest) == 0; router = router_parse_entry_from_string(*s, end, saved_location != SAVED_IN_CACHE, allow_annotations, - prepend_annotations); + prepend_annotations, &dl_again); if (router) { log_debug(LD_DIR, "Read router '%s', purpose '%s'", router_describe(router), @@ -1022,11 +988,15 @@ router_parse_list_from_string(const char **s, const char *eos, elt = router; } } + if (! elt && ! dl_again && have_raw_digest && invalid_digests_out) { + smartlist_add(invalid_digests_out, tor_memdup(raw_digest, DIGEST_LEN)); + } if (!elt) { *s = end; continue; } if (saved_location != SAVED_NOWHERE) { + tor_assert(signed_desc); signed_desc->saved_location = saved_location; signed_desc->saved_offset = *s - start; } @@ -1114,11 +1084,17 @@ find_single_ipv6_orport(const smartlist_t *list, * around when caching the router. * * Only one of allow_annotations and prepend_annotations may be set. + * + * If <b>can_dl_again_out</b> is provided, set *<b>can_dl_again_out</b> to 1 + * if it's okay to try to download a descriptor with this same digest again, + * and 0 if it isn't. (It might not be okay to download it again if part of + * the part covered by the digest is invalid.) */ routerinfo_t * router_parse_entry_from_string(const char *s, const char *end, int cache_copy, int allow_annotations, - const char *prepend_annotations) + const char *prepend_annotations, + int *can_dl_again_out) { routerinfo_t *router = NULL; char digest[128]; @@ -1129,6 +1105,9 @@ router_parse_entry_from_string(const char *s, const char *end, size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0; int ok = 1; memarea_t *area = NULL; + /* Do not set this to '1' until we have parsed everything that we intend to + * parse that's covered by the hash. */ + int can_dl_again = 0; tor_assert(!allow_annotations || !prepend_annotations); @@ -1232,8 +1211,7 @@ router_parse_entry_from_string(const char *s, const char *end, log_warn(LD_DIR,"Router nickname is invalid"); goto err; } - router->address = tor_strdup(tok->args[1]); - if (!tor_inet_aton(router->address, &in)) { + if (!tor_inet_aton(tok->args[1], &in)) { log_warn(LD_DIR,"Router address is not an IP address."); goto err; } @@ -1436,19 +1414,21 @@ router_parse_entry_from_string(const char *s, const char *end, verified_digests = digestmap_new(); digestmap_set(verified_digests, signed_digest, (void*)(uintptr_t)1); #endif - if (check_signature_token(digest, DIGEST_LEN, tok, router->identity_pkey, 0, - "router descriptor") < 0) - goto err; if (!router->or_port) { log_warn(LD_DIR,"or_port unreadable or 0. Failing."); goto err; } + /* We've checked everything that's covered by the hash. */ + can_dl_again = 1; + if (check_signature_token(digest, DIGEST_LEN, tok, router->identity_pkey, 0, + "router descriptor") < 0) + goto err; + if (!router->platform) { router->platform = tor_strdup("<unknown>"); } - goto done; err: @@ -1465,6 +1445,8 @@ router_parse_entry_from_string(const char *s, const char *end, DUMP_AREA(area, "routerinfo"); memarea_drop_all(area); } + if (can_dl_again_out) + *can_dl_again_out = can_dl_again; return router; } @@ -1473,10 +1455,16 @@ router_parse_entry_from_string(const char *s, const char *end, * <b>cache_copy</b> is true, make a copy of the extra-info document in the * cache_info fields of the result. If <b>routermap</b> is provided, use it * as a map from router identity to routerinfo_t when looking up signing keys. + * + * If <b>can_dl_again_out</b> is provided, set *<b>can_dl_again_out</b> to 1 + * if it's okay to try to download an extrainfo with this same digest again, + * and 0 if it isn't. (It might not be okay to download it again if part of + * the part covered by the digest is invalid.) */ extrainfo_t * extrainfo_parse_entry_from_string(const char *s, const char *end, - int cache_copy, struct digest_ri_map_t *routermap) + int cache_copy, struct digest_ri_map_t *routermap, + int *can_dl_again_out) { extrainfo_t *extrainfo = NULL; char digest[128]; @@ -1486,6 +1474,9 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, routerinfo_t *router = NULL; memarea_t *area = NULL; const char *s_dup = s; + /* Do not set this to '1' until we have parsed everything that we intend to + * parse that's covered by the hash. */ + int can_dl_again = 0; if (!end) { end = s + strlen(s); @@ -1545,6 +1536,9 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, goto err; } + /* We've checked everything that's covered by the hash. */ + can_dl_again = 1; + if (routermap && (router = digestmap_get((digestmap_t*)routermap, extrainfo->cache_info.identity_digest))) { @@ -1587,6 +1581,8 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, DUMP_AREA(area, "extrainfo"); memarea_drop_all(area); } + if (can_dl_again_out) + *can_dl_again_out = can_dl_again; return extrainfo; } @@ -1728,7 +1724,6 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) log_debug(LD_DIR, "We already checked the signature on this " "certificate; no need to do so again."); found = 1; - cert->is_cross_certified = old_cert->is_cross_certified; } } if (!found) { @@ -1737,18 +1732,14 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) goto err; } - if ((tok = find_opt_by_keyword(tokens, K_DIR_KEY_CROSSCERT))) { - /* XXXX Once all authorities generate cross-certified certificates, - * make this field mandatory. */ - if (check_signature_token(cert->cache_info.identity_digest, - DIGEST_LEN, - tok, - cert->signing_key, - CST_NO_CHECK_OBJTYPE, - "key cross-certification")) { - goto err; - } - cert->is_cross_certified = 1; + tok = find_by_keyword(tokens, K_DIR_KEY_CROSSCERT); + if (check_signature_token(cert->cache_info.identity_digest, + DIGEST_LEN, + tok, + cert->signing_key, + CST_NO_CHECK_OBJTYPE, + "key cross-certification")) { + goto err; } } @@ -1806,6 +1797,63 @@ find_start_of_next_routerstatus(const char *s) return eos; } +/** Parse the GuardFraction string from a consensus or vote. + * + * If <b>vote</b> or <b>vote_rs</b> are set the document getting + * parsed is a vote routerstatus. Otherwise it's a consensus. This is + * the same semantic as in routerstatus_parse_entry_from_string(). */ +STATIC int +routerstatus_parse_guardfraction(const char *guardfraction_str, + networkstatus_t *vote, + vote_routerstatus_t *vote_rs, + routerstatus_t *rs) +{ + int ok; + const char *end_of_header = NULL; + int is_consensus = !vote_rs; + uint32_t guardfraction; + + tor_assert(bool_eq(vote, vote_rs)); + + /* If this info comes from a consensus, but we should't apply + guardfraction, just exit. */ + if (is_consensus && !should_apply_guardfraction(NULL)) { + return 0; + } + + end_of_header = strchr(guardfraction_str, '='); + if (!end_of_header) { + return -1; + } + + guardfraction = (uint32_t)tor_parse_ulong(end_of_header+1, + 10, 0, 100, &ok, NULL); + if (!ok) { + log_warn(LD_DIR, "Invalid GuardFraction %s", escaped(guardfraction_str)); + return -1; + } + + log_debug(LD_GENERAL, "[*] Parsed %s guardfraction '%s' for '%s'.", + is_consensus ? "consensus" : "vote", + guardfraction_str, rs->nickname); + + if (!is_consensus) { /* We are parsing a vote */ + vote_rs->status.guardfraction_percentage = guardfraction; + vote_rs->status.has_guardfraction = 1; + } else { + /* We are parsing a consensus. Only apply guardfraction to guards. */ + if (rs->is_possible_guard) { + rs->guardfraction_percentage = guardfraction; + rs->has_guardfraction = 1; + } else { + log_warn(LD_BUG, "Got GuardFraction for non-guard %s. " + "This is not supposed to happen. Not applying. ", rs->nickname); + } + } + + return 0; +} + /** Given a string at *<b>s</b>, containing a routerstatus object, and an * empty smartlist at <b>tokens</b>, parse and return the first router status * object in the string, and advance *<b>s</b> to just after the end of the @@ -1948,14 +1996,10 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->is_named = 1; else if (!strcmp(tok->args[i], "Valid")) rs->is_valid = 1; - else if (!strcmp(tok->args[i], "V2Dir")) - rs->is_v2_dir = 1; else if (!strcmp(tok->args[i], "Guard")) rs->is_possible_guard = 1; else if (!strcmp(tok->args[i], "BadExit")) rs->is_bad_exit = 1; - else if (!strcmp(tok->args[i], "BadDirectory")) - rs->is_bad_directory = 1; else if (!strcmp(tok->args[i], "Authority")) rs->is_authority = 1; else if (!strcmp(tok->args[i], "Unnamed") && @@ -1972,12 +2016,9 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->version_known = 1; if (strcmpstart(tok->args[0], "Tor ")) { rs->version_supports_microdesc_cache = 1; - rs->version_supports_optimistic_data = 1; } else { rs->version_supports_microdesc_cache = tor_version_supports_microdescriptors(tok->args[0]); - rs->version_supports_optimistic_data = - tor_version_as_new_as(tok->args[0], "0.2.3.1-alpha"); rs->version_supports_extend2_cells = tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha"); } @@ -2015,6 +2056,11 @@ routerstatus_parse_entry_from_string(memarea_t *area, vote->has_measured_bws = 1; } else if (!strcmpstart(tok->args[i], "Unmeasured=1")) { rs->bw_is_unmeasured = 1; + } else if (!strcmpstart(tok->args[i], "GuardFraction=")) { + if (routerstatus_parse_guardfraction(tok->args[i], + vote, vote_rs, rs) < 0) { + goto err; + } } } } @@ -2084,14 +2130,6 @@ routerstatus_parse_entry_from_string(memarea_t *area, return rs; } -/** Helper to sort a smartlist of pointers to routerstatus_t */ -int -compare_routerstatus_entries(const void **_a, const void **_b) -{ - const routerstatus_t *a = *_a, *b = *_b; - return fast_memcmp(a->identity_digest, b->identity_digest, DIGEST_LEN); -} - int compare_vote_routerstatus_entries(const void **_a, const void **_b) { @@ -2100,188 +2138,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b) DIGEST_LEN); } -/** Helper: used in call to _smartlist_uniq to clear out duplicate entries. */ -static void -free_duplicate_routerstatus_entry_(void *e) -{ - log_warn(LD_DIR, - "Network-status has two entries for the same router. " - "Dropping one."); - routerstatus_free(e); -} - -/** Given a v2 network-status object in <b>s</b>, try to - * parse it and return the result. Return NULL on failure. Check the - * signature of the network status, but do not (yet) check the signing key for - * authority. - */ -networkstatus_v2_t * -networkstatus_v2_parse_from_string(const char *s) -{ - const char *eos, *s_dup = s; - smartlist_t *tokens = smartlist_new(); - smartlist_t *footer_tokens = smartlist_new(); - networkstatus_v2_t *ns = NULL; - char ns_digest[DIGEST_LEN]; - char tmp_digest[DIGEST_LEN]; - struct in_addr in; - directory_token_t *tok; - int i; - memarea_t *area = NULL; - - if (router_get_networkstatus_v2_hash(s, ns_digest)) { - log_warn(LD_DIR, "Unable to compute digest of network-status"); - goto err; - } - - area = memarea_new(); - eos = find_start_of_next_routerstatus(s); - if (tokenize_string(area, s, eos, tokens, netstatus_token_table,0)) { - log_warn(LD_DIR, "Error tokenizing network-status header."); - goto err; - } - ns = tor_malloc_zero(sizeof(networkstatus_v2_t)); - memcpy(ns->networkstatus_digest, ns_digest, DIGEST_LEN); - - tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION); - tor_assert(tok->n_args >= 1); - if (strcmp(tok->args[0], "2")) { - log_warn(LD_BUG, "Got a non-v2 networkstatus. Version was " - "%s", escaped(tok->args[0])); - goto err; - } - - tok = find_by_keyword(tokens, K_DIR_SOURCE); - tor_assert(tok->n_args >= 3); - ns->source_address = tor_strdup(tok->args[0]); - if (tor_inet_aton(tok->args[1], &in) == 0) { - log_warn(LD_DIR, "Error parsing network-status source address %s", - escaped(tok->args[1])); - goto err; - } - ns->source_addr = ntohl(in.s_addr); - ns->source_dirport = - (uint16_t) tor_parse_long(tok->args[2],10,0,65535,NULL,NULL); - if (ns->source_dirport == 0) { - log_warn(LD_DIR, "Directory source without dirport; skipping."); - goto err; - } - - tok = find_by_keyword(tokens, K_FINGERPRINT); - tor_assert(tok->n_args); - if (base16_decode(ns->identity_digest, DIGEST_LEN, tok->args[0], - strlen(tok->args[0]))) { - log_warn(LD_DIR, "Couldn't decode networkstatus fingerprint %s", - escaped(tok->args[0])); - goto err; - } - - if ((tok = find_opt_by_keyword(tokens, K_CONTACT))) { - tor_assert(tok->n_args); - ns->contact = tor_strdup(tok->args[0]); - } - - tok = find_by_keyword(tokens, K_DIR_SIGNING_KEY); - tor_assert(tok->key); - ns->signing_key = tok->key; - tok->key = NULL; - - if (crypto_pk_get_digest(ns->signing_key, tmp_digest)<0) { - log_warn(LD_DIR, "Couldn't compute signing key digest"); - goto err; - } - if (tor_memneq(tmp_digest, ns->identity_digest, DIGEST_LEN)) { - log_warn(LD_DIR, - "network-status fingerprint did not match dir-signing-key"); - goto err; - } - - if ((tok = find_opt_by_keyword(tokens, K_DIR_OPTIONS))) { - for (i=0; i < tok->n_args; ++i) { - if (!strcmp(tok->args[i], "Names")) - ns->binds_names = 1; - if (!strcmp(tok->args[i], "Versions")) - ns->recommends_versions = 1; - if (!strcmp(tok->args[i], "BadExits")) - ns->lists_bad_exits = 1; - if (!strcmp(tok->args[i], "BadDirectories")) - ns->lists_bad_directories = 1; - } - } - - if (ns->recommends_versions) { - if (!(tok = find_opt_by_keyword(tokens, K_CLIENT_VERSIONS))) { - log_warn(LD_DIR, "Missing client-versions on versioning directory"); - goto err; - } - ns->client_versions = tor_strdup(tok->args[0]); - - if (!(tok = find_opt_by_keyword(tokens, K_SERVER_VERSIONS)) || - tok->n_args<1) { - log_warn(LD_DIR, "Missing server-versions on versioning directory"); - goto err; - } - ns->server_versions = tor_strdup(tok->args[0]); - } - - tok = find_by_keyword(tokens, K_PUBLISHED); - tor_assert(tok->n_args == 1); - if (parse_iso_time(tok->args[0], &ns->published_on) < 0) { - goto err; - } - - ns->entries = smartlist_new(); - s = eos; - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - smartlist_clear(tokens); - memarea_clear(area); - while (!strcmpstart(s, "r ")) { - routerstatus_t *rs; - if ((rs = routerstatus_parse_entry_from_string(area, &s, tokens, - NULL, NULL, 0, 0))) - smartlist_add(ns->entries, rs); - } - smartlist_sort(ns->entries, compare_routerstatus_entries); - smartlist_uniq(ns->entries, compare_routerstatus_entries, - free_duplicate_routerstatus_entry_); - - if (tokenize_string(area,s, NULL, footer_tokens, dir_footer_token_table,0)) { - log_warn(LD_DIR, "Error tokenizing network-status footer."); - goto err; - } - if (smartlist_len(footer_tokens) < 1) { - log_warn(LD_DIR, "Too few items in network-status footer."); - goto err; - } - tok = smartlist_get(footer_tokens, smartlist_len(footer_tokens)-1); - if (tok->tp != K_DIRECTORY_SIGNATURE) { - log_warn(LD_DIR, - "Expected network-status footer to end with a signature."); - goto err; - } - - note_crypto_pk_op(VERIFY_DIR); - if (check_signature_token(ns_digest, DIGEST_LEN, tok, ns->signing_key, 0, - "network-status") < 0) - goto err; - - goto done; - err: - dump_desc(s_dup, "v2 networkstatus"); - networkstatus_v2_free(ns); - ns = NULL; - done: - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - smartlist_free(tokens); - SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t)); - smartlist_free(footer_tokens); - if (area) { - DUMP_AREA(area, "v2 networkstatus"); - memarea_drop_all(area); - } - return ns; -} - /** Verify the bandwidth weights of a network status document */ int networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) @@ -2292,6 +2148,7 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) double Gtotal=0, Mtotal=0, Etotal=0; const char *casename = NULL; int valid = 1; + (void) consensus_method; weight_scale = networkstatus_get_weight_scale_param(ns); Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1); @@ -2371,12 +2228,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) // Then, gather G, M, E, D, T to determine case SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { int is_exit = 0; - if (consensus_method >= MIN_METHOD_TO_CUT_BADEXIT_WEIGHT) { - /* Bug #2203: Don't count bad exits as exits for balancing */ - is_exit = rs->is_exit && !rs->is_bad_exit; - } else { - is_exit = rs->is_exit; - } + /* Bug #2203: Don't count bad exits as exits for balancing */ + is_exit = rs->is_exit && !rs->is_bad_exit; if (rs->has_bandwidth) { T += rs->bandwidth_kb; if (is_exit && rs->is_possible_guard) { @@ -2812,11 +2665,15 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, (int) tor_parse_long(tok->args[1], 10, 0, INT_MAX, &ok, NULL); if (!ok) goto err; - if (ns->valid_after + MIN_VOTE_INTERVAL > ns->fresh_until) { + if (ns->valid_after + + (get_options()->TestingTorNetwork ? + MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) > ns->fresh_until) { log_warn(LD_DIR, "Vote/consensus freshness interval is too short"); goto err; } - if (ns->valid_after + MIN_VOTE_INTERVAL*2 > ns->valid_until) { + if (ns->valid_after + + (get_options()->TestingTorNetwork ? + MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL)*2 > ns->valid_until) { log_warn(LD_DIR, "Vote/consensus liveness interval is too short"); goto err; } @@ -2836,6 +2693,16 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, ns->server_versions = tor_strdup(tok->args[0]); } + { + smartlist_t *package_lst = find_all_by_keyword(tokens, K_PACKAGE); + ns->package_lines = smartlist_new(); + if (package_lst) { + SMARTLIST_FOREACH(package_lst, directory_token_t *, t, + smartlist_add(ns->package_lines, tor_strdup(t->args[0]))); + } + smartlist_free(package_lst); + } + tok = find_by_keyword(tokens, K_KNOWN_FLAGS); ns->known_flags = smartlist_new(); inorder = 1; @@ -3491,8 +3358,8 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos) * AF_UNSPEC for '*'. Use policy_expand_unspec() to turn this into a pair * of AF_INET and AF_INET6 items. */ -addr_policy_t * -router_parse_addr_policy_item_from_string(const char *s, int assume_action) +MOCK_IMPL(addr_policy_t *, +router_parse_addr_policy_item_from_string,(const char *s, int assume_action)) { directory_token_t *tok = NULL; const char *cp, *eos; @@ -4258,12 +4125,15 @@ find_start_of_next_microdesc(const char *s, const char *eos) * If <b>saved_location</b> isn't SAVED_IN_CACHE, make a local copy of each * descriptor in the body field of each microdesc_t. * - * Return all newly - * parsed microdescriptors in a newly allocated smartlist_t. */ + * Return all newly parsed microdescriptors in a newly allocated + * smartlist_t. If <b>invalid_disgests_out</b> is provided, add a SHA256 + * microdesc digest to it for every microdesc that we found to be badly + * formed. (This may cause duplicates) */ smartlist_t * microdescs_parse_from_string(const char *s, const char *eos, int allow_annotations, - saved_location_t where) + saved_location_t where, + smartlist_t *invalid_digests_out) { smartlist_t *tokens; smartlist_t *result; @@ -4285,21 +4155,20 @@ microdescs_parse_from_string(const char *s, const char *eos, tokens = smartlist_new(); while (s < eos) { + int okay = 0; + start_of_next_microdesc = find_start_of_next_microdesc(s, eos); if (!start_of_next_microdesc) start_of_next_microdesc = eos; - if (tokenize_string(area, s, start_of_next_microdesc, tokens, - microdesc_token_table, flags)) { - log_warn(LD_DIR, "Unparseable microdescriptor"); - goto next; - } - md = tor_malloc_zero(sizeof(microdesc_t)); { const char *cp = tor_memstr(s, start_of_next_microdesc-s, "onion-key"); - tor_assert(cp); + const int no_onion_key = (cp == NULL); + if (no_onion_key) { + cp = s; /* So that we have *some* junk to put in the body */ + } md->bodylen = start_of_next_microdesc - cp; md->saved_location = where; @@ -4308,6 +4177,18 @@ microdescs_parse_from_string(const char *s, const char *eos, else md->body = (char*)cp; md->off = cp - start; + crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); + if (no_onion_key) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Malformed or truncated descriptor"); + goto next; + } + } + + + if (tokenize_string(area, s, start_of_next_microdesc, tokens, + microdesc_token_table, flags)) { + log_warn(LD_DIR, "Unparseable microdescriptor"); + goto next; } if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) { @@ -4365,12 +4246,15 @@ microdescs_parse_from_string(const char *s, const char *eos, md->ipv6_exit_policy = parse_short_policy(tok->args[0]); } - crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); - smartlist_add(result, md); + okay = 1; md = NULL; next: + if (! okay && invalid_digests_out) { + smartlist_add(invalid_digests_out, + tor_memdup(md->digest, DIGEST256_LEN)); + } microdesc_free(md); md = NULL; @@ -4451,40 +4335,50 @@ tor_version_parse(const char *s, tor_version_t *out) char *eos=NULL; const char *cp=NULL; /* Format is: - * "Tor " ? NUM dot NUM dot NUM [ ( pre | rc | dot ) NUM [ - tag ] ] + * "Tor " ? NUM dot NUM [ dot NUM [ ( pre | rc | dot ) NUM ] ] [ - tag ] */ tor_assert(s); tor_assert(out); memset(out, 0, sizeof(tor_version_t)); - + out->status = VER_RELEASE; if (!strcasecmpstart(s, "Tor ")) s += 4; - /* Get major. */ - out->major = (int)strtol(s,&eos,10); - if (!eos || eos==s || *eos != '.') return -1; - cp = eos+1; - - /* Get minor */ - out->minor = (int) strtol(cp,&eos,10); - if (!eos || eos==cp || *eos != '.') return -1; - cp = eos+1; - - /* Get micro */ - out->micro = (int) strtol(cp,&eos,10); - if (!eos || eos==cp) return -1; - if (!*eos) { - out->status = VER_RELEASE; - out->patchlevel = 0; + cp = s; + +#define NUMBER(m) \ + do { \ + out->m = (int)strtol(cp, &eos, 10); \ + if (!eos || eos == cp) \ + return -1; \ + cp = eos; \ + } while (0) + +#define DOT() \ + do { \ + if (*cp != '.') \ + return -1; \ + ++cp; \ + } while (0) + + NUMBER(major); + DOT(); + NUMBER(minor); + if (*cp == 0) return 0; - } - cp = eos; + else if (*cp == '-') + goto status_tag; + DOT(); + NUMBER(micro); /* Get status */ - if (*cp == '.') { - out->status = VER_RELEASE; + if (*cp == 0) { + return 0; + } else if (*cp == '.') { ++cp; + } else if (*cp == '-') { + goto status_tag; } else if (0==strncmp(cp, "pre", 3)) { out->status = VER_PRE; cp += 3; @@ -4495,11 +4389,9 @@ tor_version_parse(const char *s, tor_version_t *out) return -1; } - /* Get patchlevel */ - out->patchlevel = (int) strtol(cp,&eos,10); - if (!eos || eos==cp) return -1; - cp = eos; + NUMBER(patchlevel); + status_tag: /* Get status tag. */ if (*cp == '-' || *cp == '.') ++cp; @@ -4535,6 +4427,8 @@ tor_version_parse(const char *s, tor_version_t *out) } return 0; +#undef NUMBER +#undef DOT } /** Compare two tor versions; Return <0 if a < b; 0 if a ==b, >0 if a > @@ -4622,6 +4516,9 @@ sort_version_list(smartlist_t *versions, int remove_duplicates) * to *<b>encoded_size_out</b>, and a pointer to the possibly next * descriptor to *<b>next_out</b>; return 0 for success (including validation) * and -1 for failure. + * + * If <b>as_hsdir</b> is 1, we're parsing this as an HSDir, and we should + * be strict about time formats. */ int rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, @@ -4629,7 +4526,8 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, char **intro_points_encrypted_out, size_t *intro_points_encrypted_size_out, size_t *encoded_size_out, - const char **next_out, const char *desc) + const char **next_out, const char *desc, + int as_hsdir) { rend_service_descriptor_t *result = tor_malloc_zero(sizeof(rend_service_descriptor_t)); @@ -4643,6 +4541,8 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, char public_key_hash[DIGEST_LEN]; char test_desc_id[DIGEST_LEN]; memarea_t *area = NULL; + const int strict_time_fmt = as_hsdir; + tor_assert(desc); /* Check if desc starts correctly. */ if (strncmp(desc, "rendezvous-service-descriptor ", @@ -4737,7 +4637,7 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, * descriptor. */ tok = find_by_keyword(tokens, R_PUBLICATION_TIME); tor_assert(tok->n_args == 1); - if (parse_iso_time(tok->args[0], &result->timestamp) < 0) { + if (parse_iso_time_(tok->args[0], &result->timestamp, strict_time_fmt) < 0) { log_warn(LD_REND, "Invalid publication time: '%s'", tok->args[0]); goto err; } diff --git a/src/or/routerparse.h b/src/or/routerparse.h index eb2e885cb1..fc21cb1041 100644 --- a/src/or/routerparse.h +++ b/src/or/routerparse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,8 +14,6 @@ int router_get_router_hash(const char *s, size_t s_len, char *digest); int router_get_dir_hash(const char *s, char *digest); -int router_get_runningrouters_hash(const char *s, char *digest); -int router_get_networkstatus_v2_hash(const char *s, char *digest); int router_get_networkstatus_v3_hashes(const char *s, digests_t *digests); int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest); #define DIROBJ_MAX_SIG_LEN 256 @@ -31,16 +29,19 @@ int router_parse_list_from_string(const char **s, const char *eos, saved_location_t saved_location, int is_extrainfo, int allow_annotations, - const char *prepend_annotations); + const char *prepend_annotations, + smartlist_t *invalid_digests_out); routerinfo_t *router_parse_entry_from_string(const char *s, const char *end, int cache_copy, int allow_annotations, - const char *prepend_annotations); + const char *prepend_annotations, + int *can_dl_again_out); extrainfo_t *extrainfo_parse_entry_from_string(const char *s, const char *end, - int cache_copy, struct digest_ri_map_t *routermap); -addr_policy_t *router_parse_addr_policy_item_from_string(const char *s, - int assume_action); + int cache_copy, struct digest_ri_map_t *routermap, + int *can_dl_again_out); +MOCK_DECL(addr_policy_t *, router_parse_addr_policy_item_from_string, + (const char *s, int assume_action)); version_status_t tor_version_is_obsolete(const char *myversion, const char *versionlist); int tor_version_supports_microdescriptors(const char *platform); @@ -52,9 +53,7 @@ void sort_version_list(smartlist_t *lst, int remove_duplicates); void assert_addr_policy_ok(smartlist_t *t); void dump_distinct_digest_count(int severity); -int compare_routerstatus_entries(const void **_a, const void **_b); int compare_vote_routerstatus_entries(const void **_a, const void **_b); -networkstatus_v2_t *networkstatus_v2_parse_from_string(const char *s); int networkstatus_verify_bw_weights(networkstatus_t *ns, int); networkstatus_t *networkstatus_parse_vote_from_string(const char *s, const char **eos_out, @@ -64,7 +63,8 @@ ns_detached_signatures_t *networkstatus_parse_detached_signatures( smartlist_t *microdescs_parse_from_string(const char *s, const char *eos, int allow_annotations, - saved_location_t where); + saved_location_t where, + smartlist_t *invalid_digests_out); authority_cert_t *authority_cert_parse_from_string(const char *s, const char **end_of_string); @@ -73,7 +73,8 @@ int rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, char **intro_points_encrypted_out, size_t *intro_points_encrypted_size_out, size_t *encoded_size_out, - const char **next_out, const char *desc); + const char **next_out, const char *desc, + int as_hsdir); int rend_decrypt_introduction_points(char **ipos_decrypted, size_t *ipos_decrypted_size, const char *descriptor_cookie, @@ -84,5 +85,12 @@ int rend_parse_introduction_points(rend_service_descriptor_t *parsed, size_t intro_points_encoded_size); int rend_parse_client_keys(strmap_t *parsed_clients, const char *str); +#ifdef ROUTERPARSE_PRIVATE +STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str, + networkstatus_t *vote, + vote_routerstatus_t *vote_rs, + routerstatus_t *rs); +#endif + #endif diff --git a/src/or/routerset.c b/src/or/routerset.c index 2e41f7f6c4..99de11ed5e 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -1,9 +1,11 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#define ROUTERSET_PRIVATE + #include "or.h" #include "geoip.h" #include "nodelist.h" @@ -12,39 +14,6 @@ #include "routerparse.h" #include "routerset.h" -/** A routerset specifies constraints on a set of possible routerinfos, based - * on their names, identities, or addresses. It is optimized for determining - * whether a router is a member or not, in O(1+P) time, where P is the number - * of address policy constraints. */ -struct routerset_t { - /** A list of strings for the elements of the policy. Each string is either - * a nickname, a hexadecimal identity fingerprint, or an address policy. A - * router belongs to the set if its nickname OR its identity OR its address - * matches an entry here. */ - smartlist_t *list; - /** A map from lowercase nicknames of routers in the set to (void*)1 */ - strmap_t *names; - /** A map from identity digests routers in the set to (void*)1 */ - digestmap_t *digests; - /** An address policy for routers in the set. For implementation reasons, - * a router belongs to the set if it is _rejected_ by this policy. */ - smartlist_t *policies; - - /** A human-readable description of what this routerset is for. Used in - * log messages. */ - char *description; - - /** A list of the country codes in this set. */ - smartlist_t *country_names; - /** Total number of countries we knew about when we built <b>countries</b>.*/ - int n_countries; - /** Bit array mapping the return value of geoip_get_country() to 1 iff the - * country is a member of this routerset. Note that we MUST call - * routerset_refresh_countries() whenever the geoip country list is - * reloaded. */ - bitarray_t *countries; -}; - /** Return a new empty routerset. */ routerset_t * routerset_new(void) @@ -60,7 +29,7 @@ routerset_new(void) /** If <b>c</b> is a country code in the form {cc}, return a newly allocated * string holding the "cc" part. Else, return NULL. */ -static char * +STATIC char * routerset_get_countryname(const char *c) { char *country; @@ -200,7 +169,7 @@ routerset_is_empty(const routerset_t *set) * * (If country is -1, then we take the country * from addr.) */ -static int +STATIC int routerset_contains(const routerset_t *set, const tor_addr_t *addr, uint16_t orport, const char *nickname, const char *id_digest, @@ -358,39 +327,6 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, } } -#if 0 -/** 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 - * 2) Don't add it if <b>exclude</b> is non-empty and the relay is - * excluded in a more specific fashion by <b>exclude</b>. - * 3) If <b>running_only</b>, don't add non-running routers. - */ -void -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, const node_t *, node, { - int include_result; - if (running_only && !node->is_running) - continue; - if (!routerset_is_empty(include)) - include_result = routerset_contains_node(include, node); - else - include_result = 1; - - if (include_result) { - int exclude_result = routerset_contains_node(exclude, node); - if (include_result >= exclude_result) - smartlist_add(target, (void*)node); - } - }); -} -#endif - /** Remove every node_t from <b>lst</b> that is in <b>routerset</b>. */ void routerset_subtract_nodes(smartlist_t *lst, const routerset_t *routerset) diff --git a/src/or/routerset.h b/src/or/routerset.h index bfa0c59ac1..8d41de8b6b 100644 --- a/src/or/routerset.h +++ b/src/or/routerset.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -32,12 +32,6 @@ void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, const routerset_t *excludeset, int running_only); int routerset_add_unknown_ccs(routerset_t **setp, int only_if_some_cc_set); -#if 0 -void routersets_get_node_disjunction(smartlist_t *target, - const smartlist_t *source, - const routerset_t *include, - const routerset_t *exclude, int running_only); -#endif void routerset_subtract_nodes(smartlist_t *out, const routerset_t *routerset); @@ -45,5 +39,45 @@ char *routerset_to_string(const routerset_t *routerset); int routerset_equal(const routerset_t *old, const routerset_t *new); void routerset_free(routerset_t *routerset); +#ifdef ROUTERSET_PRIVATE +STATIC char * routerset_get_countryname(const char *c); +STATIC int routerset_contains(const routerset_t *set, const tor_addr_t *addr, + uint16_t orport, + const char *nickname, const char *id_digest, + country_t country); + +/** A routerset specifies constraints on a set of possible routerinfos, based + * on their names, identities, or addresses. It is optimized for determining + * whether a router is a member or not, in O(1+P) time, where P is the number + * of address policy constraints. */ +struct routerset_t { + /** A list of strings for the elements of the policy. Each string is either + * a nickname, a hexadecimal identity fingerprint, or an address policy. A + * router belongs to the set if its nickname OR its identity OR its address + * matches an entry here. */ + smartlist_t *list; + /** A map from lowercase nicknames of routers in the set to (void*)1 */ + strmap_t *names; + /** A map from identity digests routers in the set to (void*)1 */ + digestmap_t *digests; + /** An address policy for routers in the set. For implementation reasons, + * a router belongs to the set if it is _rejected_ by this policy. */ + smartlist_t *policies; + + /** A human-readable description of what this routerset is for. Used in + * log messages. */ + char *description; + + /** A list of the country codes in this set. */ + smartlist_t *country_names; + /** Total number of countries we knew about when we built <b>countries</b>.*/ + int n_countries; + /** Bit array mapping the return value of geoip_get_country() to 1 iff the + * country is a member of this routerset. Note that we MUST call + * routerset_refresh_countries() whenever the geoip country list is + * reloaded. */ + bitarray_t *countries; +}; +#endif #endif diff --git a/src/or/scheduler.c b/src/or/scheduler.c new file mode 100644 index 0000000000..931bb6b744 --- /dev/null +++ b/src/or/scheduler.c @@ -0,0 +1,711 @@ +/* * Copyright (c) 2013-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file scheduler.c + * \brief Relay scheduling system + **/ + +#include "or.h" + +#define TOR_CHANNEL_INTERNAL_ /* For channel_flush_some_cells() */ +#include "channel.h" + +#include "compat_libevent.h" +#define SCHEDULER_PRIVATE_ +#include "scheduler.h" + +#ifdef HAVE_EVENT2_EVENT_H +#include <event2/event.h> +#else +#include <event.h> +#endif + +/* + * Scheduler high/low watermarks + */ + +static uint32_t sched_q_low_water = 16384; +static uint32_t sched_q_high_water = 32768; + +/* + * Maximum cells to flush in a single call to channel_flush_some_cells(); + * setting this low means more calls, but too high and we could overshoot + * sched_q_high_water. + */ + +static uint32_t sched_max_flush_cells = 16; + +/* + * Write scheduling works by keeping track of which channels can + * accept cells, and have cells to write. From the scheduler's perspective, + * a channel can be in four possible states: + * + * 1.) Not open for writes, no cells to send + * - Not much to do here, and the channel will have scheduler_state == + * SCHED_CHAN_IDLE + * - Transitions from: + * - Open for writes/has cells by simultaneously draining all circuit + * queues and filling the output buffer. + * - Transitions to: + * - Not open for writes/has cells by arrival of cells on an attached + * circuit (this would be driven from append_cell_to_circuit_queue()) + * - Open for writes/no cells by a channel type specific path; + * driven from connection_or_flushed_some() for channel_tls_t. + * + * 2.) Open for writes, no cells to send + * - Not much here either; this will be the state an idle but open channel + * can be expected to settle in. It will have scheduler_state == + * SCHED_CHAN_WAITING_FOR_CELLS + * - Transitions from: + * - Not open for writes/no cells by flushing some of the output + * buffer. + * - Open for writes/has cells by the scheduler moving cells from + * circuit queues to channel output queue, but not having enough + * to fill the output queue. + * - Transitions to: + * - Open for writes/has cells by arrival of new cells on an attached + * circuit, in append_cell_to_circuit_queue() + * + * 3.) Not open for writes, cells to send + * - This is the state of a busy circuit limited by output bandwidth; + * cells have piled up in the circuit queues waiting to be relayed. + * The channel will have scheduler_state == SCHED_CHAN_WAITING_TO_WRITE. + * - Transitions from: + * - Not open for writes/no cells by arrival of cells on an attached + * circuit + * - Open for writes/has cells by filling an output buffer without + * draining all cells from attached circuits + * - Transitions to: + * - Opens for writes/has cells by draining some of the output buffer + * via the connection_or_flushed_some() path (for channel_tls_t). + * + * 4.) Open for writes, cells to send + * - This connection is ready to relay some cells and waiting for + * the scheduler to choose it. The channel will have scheduler_state == + * SCHED_CHAN_PENDING. + * - Transitions from: + * - Not open for writes/has cells by the connection_or_flushed_some() + * path + * - Open for writes/no cells by the append_cell_to_circuit_queue() + * path + * - Transitions to: + * - Not open for writes/no cells by draining all circuit queues and + * simultaneously filling the output buffer. + * - Not open for writes/has cells by writing enough cells to fill the + * output buffer + * - Open for writes/no cells by draining all attached circuit queues + * without also filling the output buffer + * + * Other event-driven parts of the code move channels between these scheduling + * states by calling scheduler functions; the scheduler only runs on open-for- + * writes/has-cells channels and is the only path for those to transition to + * other states. The scheduler_run() function gives us the opportunity to do + * scheduling work, and is called from other scheduler functions whenever a + * state transition occurs, and periodically from the main event loop. + */ + +/* Scheduler global data structures */ + +/* + * We keep a list of channels that are pending - i.e, have cells to write + * and can accept them to send. The enum scheduler_state in channel_t + * is reserved for our use. + */ + +/* Pqueue of channels that can write and have cells (pending work) */ +STATIC smartlist_t *channels_pending = NULL; + +/* + * This event runs the scheduler from its callback, and is manually + * activated whenever a channel enters open for writes/cells to send. + */ + +STATIC struct event *run_sched_ev = NULL; + +/* + * Queue heuristic; this is not the queue size, but an 'effective queuesize' + * that ages out contributions from stalled channels. + */ + +STATIC uint64_t queue_heuristic = 0; + +/* + * Timestamp for last queue heuristic update + */ + +STATIC time_t queue_heuristic_timestamp = 0; + +/* Scheduler static function declarations */ + +static void scheduler_evt_callback(evutil_socket_t fd, + short events, void *arg); +static int scheduler_more_work(void); +static void scheduler_retrigger(void); +#if 0 +static void scheduler_trigger(void); +#endif + +/* Scheduler function implementations */ + +/** Free everything and shut down the scheduling system */ + +void +scheduler_free_all(void) +{ + log_debug(LD_SCHED, "Shutting down scheduler"); + + if (run_sched_ev) { + if (event_del(run_sched_ev) < 0) { + log_warn(LD_BUG, "Problem deleting run_sched_ev"); + } + tor_event_free(run_sched_ev); + run_sched_ev = NULL; + } + + if (channels_pending) { + smartlist_free(channels_pending); + channels_pending = NULL; + } +} + +/** + * Comparison function to use when sorting pending channels + */ + +MOCK_IMPL(STATIC int, +scheduler_compare_channels, (const void *c1_v, const void *c2_v)) +{ + channel_t *c1 = NULL, *c2 = NULL; + /* These are a workaround for -Wbad-function-cast throwing a fit */ + const circuitmux_policy_t *p1, *p2; + uintptr_t p1_i, p2_i; + + tor_assert(c1_v); + tor_assert(c2_v); + + c1 = (channel_t *)(c1_v); + c2 = (channel_t *)(c2_v); + + tor_assert(c1); + tor_assert(c2); + + if (c1 != c2) { + if (circuitmux_get_policy(c1->cmux) == + circuitmux_get_policy(c2->cmux)) { + /* Same cmux policy, so use the mux comparison */ + return circuitmux_compare_muxes(c1->cmux, c2->cmux); + } else { + /* + * Different policies; not important to get this edge case perfect + * because the current code never actually gives different channels + * different cmux policies anyway. Just use this arbitrary but + * definite choice. + */ + p1 = circuitmux_get_policy(c1->cmux); + p2 = circuitmux_get_policy(c2->cmux); + p1_i = (uintptr_t)p1; + p2_i = (uintptr_t)p2; + + return (p1_i < p2_i) ? -1 : 1; + } + } else { + /* c1 == c2, so always equal */ + return 0; + } +} + +/* + * Scheduler event callback; this should get triggered once per event loop + * if any scheduling work was created during the event loop. + */ + +static void +scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) +{ + (void)fd; + (void)events; + (void)arg; + log_debug(LD_SCHED, "Scheduler event callback called"); + + tor_assert(run_sched_ev); + + /* Run the scheduler */ + scheduler_run(); + + /* Do we have more work to do? */ + if (scheduler_more_work()) scheduler_retrigger(); +} + +/** Mark a channel as no longer ready to accept writes */ + +MOCK_IMPL(void, +scheduler_channel_doesnt_want_writes,(channel_t *chan)) +{ + tor_assert(chan); + + tor_assert(channels_pending); + + /* If it's already in pending, we can put it in waiting_to_write */ + if (chan->scheduler_state == SCHED_CHAN_PENDING) { + /* + * It's in channels_pending, so it shouldn't be in any of + * the other lists. It can't write any more, so it goes to + * channels_waiting_to_write. + */ + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p went from pending " + "to waiting_to_write", + U64_PRINTF_ARG(chan->global_identifier), chan); + } else { + /* + * It's not in pending, so it can't become waiting_to_write; it's + * either not in any of the lists (nothing to do) or it's already in + * waiting_for_cells (remove it, can't write any more). + */ + if (chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS) { + chan->scheduler_state = SCHED_CHAN_IDLE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p left waiting_for_cells", + U64_PRINTF_ARG(chan->global_identifier), chan); + } + } +} + +/** Mark a channel as having waiting cells */ + +MOCK_IMPL(void, +scheduler_channel_has_waiting_cells,(channel_t *chan)) +{ + int became_pending = 0; + + tor_assert(chan); + tor_assert(channels_pending); + + /* First, check if this one also writeable */ + if (chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS) { + /* + * It's in channels_waiting_for_cells, so it shouldn't be in any of + * the other lists. It has waiting cells now, so it goes to + * channels_pending. + */ + chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p went from waiting_for_cells " + "to pending", + U64_PRINTF_ARG(chan->global_identifier), chan); + became_pending = 1; + } else { + /* + * It's not in waiting_for_cells, so it can't become pending; it's + * either not in any of the lists (we add it to waiting_to_write) + * or it's already in waiting_to_write or pending (we do nothing) + */ + if (!(chan->scheduler_state == SCHED_CHAN_WAITING_TO_WRITE || + chan->scheduler_state == SCHED_CHAN_PENDING)) { + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p entered waiting_to_write", + U64_PRINTF_ARG(chan->global_identifier), chan); + } + } + + /* + * If we made a channel pending, we potentially have scheduling work + * to do. + */ + if (became_pending) scheduler_retrigger(); +} + +/** Set up the scheduling system */ + +void +scheduler_init(void) +{ + log_debug(LD_SCHED, "Initting scheduler"); + + tor_assert(!run_sched_ev); + run_sched_ev = tor_event_new(tor_libevent_get_base(), -1, + 0, scheduler_evt_callback, NULL); + + channels_pending = smartlist_new(); + queue_heuristic = 0; + queue_heuristic_timestamp = approx_time(); +} + +/** Check if there's more scheduling work */ + +static int +scheduler_more_work(void) +{ + tor_assert(channels_pending); + + return ((scheduler_get_queue_heuristic() < sched_q_low_water) && + ((smartlist_len(channels_pending) > 0))) ? 1 : 0; +} + +/** Retrigger the scheduler in a way safe to use from the callback */ + +static void +scheduler_retrigger(void) +{ + tor_assert(run_sched_ev); + event_active(run_sched_ev, EV_TIMEOUT, 1); +} + +/** Notify the scheduler of a channel being closed */ + +MOCK_IMPL(void, +scheduler_release_channel,(channel_t *chan)) +{ + tor_assert(chan); + tor_assert(channels_pending); + + if (chan->scheduler_state == SCHED_CHAN_PENDING) { + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + } + + chan->scheduler_state = SCHED_CHAN_IDLE; +} + +/** Run the scheduling algorithm if necessary */ + +MOCK_IMPL(void, +scheduler_run, (void)) +{ + int n_cells, n_chans_before, n_chans_after; + uint64_t q_len_before, q_heur_before, q_len_after, q_heur_after; + ssize_t flushed, flushed_this_time; + smartlist_t *to_readd = NULL; + channel_t *chan = NULL; + + log_debug(LD_SCHED, "We have a chance to run the scheduler"); + + if (scheduler_get_queue_heuristic() < sched_q_low_water) { + n_chans_before = smartlist_len(channels_pending); + q_len_before = channel_get_global_queue_estimate(); + q_heur_before = scheduler_get_queue_heuristic(); + + while (scheduler_get_queue_heuristic() <= sched_q_high_water && + smartlist_len(channels_pending) > 0) { + /* Pop off a channel */ + chan = smartlist_pqueue_pop(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx)); + tor_assert(chan); + + /* Figure out how many cells we can write */ + n_cells = channel_num_cells_writeable(chan); + if (n_cells > 0) { + log_debug(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "%d cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); + + flushed = 0; + while (flushed < n_cells && + scheduler_get_queue_heuristic() <= sched_q_high_water) { + flushed_this_time = + channel_flush_some_cells(chan, + MIN(sched_max_flush_cells, + (size_t) n_cells - flushed)); + if (flushed_this_time <= 0) break; + flushed += flushed_this_time; + } + + if (flushed < n_cells) { + /* We ran out of cells to flush */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* The channel may still have some cells */ + if (channel_more_to_flush(chan)) { + /* The channel goes to either pending or waiting_to_write */ + if (channel_num_cells_writeable(chan) > 0) { + /* Add it back to pending later */ + if (!to_readd) to_readd = smartlist_new(); + smartlist_add(to_readd, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "is still pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* It's waiting to be able to write more */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_to_write from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } else { + /* No cells left; it can go to idle or waiting_for_cells */ + if (channel_num_cells_writeable(chan) > 0) { + /* + * It can still accept writes, so it goes to + * waiting_for_cells + */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* + * We exactly filled up the output queue with all available + * cells; go to idle. + */ + chan->scheduler_state = SCHED_CHAN_IDLE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "become idle from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } + } + + log_debug(LD_SCHED, + "Scheduler flushed %d cells onto pending channel " + U64_FORMAT " at %p", + (int)flushed, U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + log_info(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "no cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan); + /* Put it back to WAITING_TO_WRITE */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + } + } + + /* Readd any channels we need to */ + if (to_readd) { + SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, chan) { + chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + } SMARTLIST_FOREACH_END(chan); + smartlist_free(to_readd); + } + + n_chans_after = smartlist_len(channels_pending); + q_len_after = channel_get_global_queue_estimate(); + q_heur_after = scheduler_get_queue_heuristic(); + log_debug(LD_SCHED, + "Scheduler handled %d of %d pending channels, queue size from " + U64_FORMAT " to " U64_FORMAT ", queue heuristic from " + U64_FORMAT " to " U64_FORMAT, + n_chans_before - n_chans_after, n_chans_before, + U64_PRINTF_ARG(q_len_before), U64_PRINTF_ARG(q_len_after), + U64_PRINTF_ARG(q_heur_before), U64_PRINTF_ARG(q_heur_after)); + } +} + +/** Trigger the scheduling event so we run the scheduler later */ + +#if 0 +static void +scheduler_trigger(void) +{ + log_debug(LD_SCHED, "Triggering scheduler event"); + + tor_assert(run_sched_ev); + + event_add(run_sched_ev, EV_TIMEOUT, 1); +} +#endif + +/** Mark a channel as ready to accept writes */ + +void +scheduler_channel_wants_writes(channel_t *chan) +{ + int became_pending = 0; + + tor_assert(chan); + tor_assert(channels_pending); + + /* If it's already in waiting_to_write, we can put it in pending */ + if (chan->scheduler_state == SCHED_CHAN_WAITING_TO_WRITE) { + /* + * It can write now, so it goes to channels_pending. + */ + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + chan->scheduler_state = SCHED_CHAN_PENDING; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p went from waiting_to_write " + "to pending", + U64_PRINTF_ARG(chan->global_identifier), chan); + became_pending = 1; + } else { + /* + * It's not in SCHED_CHAN_WAITING_TO_WRITE, so it can't become pending; + * it's either idle and goes to WAITING_FOR_CELLS, or it's a no-op. + */ + if (!(chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS || + chan->scheduler_state == SCHED_CHAN_PENDING)) { + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p entered waiting_for_cells", + U64_PRINTF_ARG(chan->global_identifier), chan); + } + } + + /* + * If we made a channel pending, we potentially have scheduling work + * to do. + */ + if (became_pending) scheduler_retrigger(); +} + +/** + * Notify the scheduler that a channel's position in the pqueue may have + * changed + */ + +void +scheduler_touch_channel(channel_t *chan) +{ + tor_assert(chan); + + if (chan->scheduler_state == SCHED_CHAN_PENDING) { + /* Remove and re-add it */ + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + } + /* else no-op, since it isn't in the queue */ +} + +/** + * Notify the scheduler of a queue size adjustment, to recalculate the + * queue heuristic. + */ + +void +scheduler_adjust_queue_size(channel_t *chan, int dir, uint64_t adj) +{ + time_t now = approx_time(); + + log_debug(LD_SCHED, + "Queue size adjustment by %s" U64_FORMAT " for channel " + U64_FORMAT, + (dir >= 0) ? "+" : "-", + U64_PRINTF_ARG(adj), + U64_PRINTF_ARG(chan->global_identifier)); + + /* Get the queue heuristic up to date */ + scheduler_update_queue_heuristic(now); + + /* Adjust as appropriate */ + if (dir >= 0) { + /* Increasing it */ + queue_heuristic += adj; + } else { + /* Decreasing it */ + if (queue_heuristic > adj) queue_heuristic -= adj; + else queue_heuristic = 0; + } + + log_debug(LD_SCHED, + "Queue heuristic is now " U64_FORMAT, + U64_PRINTF_ARG(queue_heuristic)); +} + +/** + * Query the current value of the queue heuristic + */ + +STATIC uint64_t +scheduler_get_queue_heuristic(void) +{ + time_t now = approx_time(); + + scheduler_update_queue_heuristic(now); + + return queue_heuristic; +} + +/** + * Adjust the queue heuristic value to the present time + */ + +STATIC void +scheduler_update_queue_heuristic(time_t now) +{ + time_t diff; + + if (queue_heuristic_timestamp == 0) { + /* + * Nothing we can sensibly do; must not have been initted properly. + * Oh well. + */ + queue_heuristic_timestamp = now; + } else if (queue_heuristic_timestamp < now) { + diff = now - queue_heuristic_timestamp; + /* + * This is a simple exponential age-out; the other proposed alternative + * was a linear age-out using the bandwidth history in rephist.c; I'm + * going with this out of concern that if an adversary can jam the + * scheduler long enough, it would cause the bandwidth to drop to + * zero and render the aging mechanism ineffective thereafter. + */ + if (0 <= diff && diff < 64) queue_heuristic >>= diff; + else queue_heuristic = 0; + + queue_heuristic_timestamp = now; + + log_debug(LD_SCHED, + "Queue heuristic is now " U64_FORMAT, + U64_PRINTF_ARG(queue_heuristic)); + } + /* else no update needed, or time went backward */ +} + +/** + * Set scheduler watermarks and flush size + */ + +void +scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush) +{ + /* Sanity assertions - caller should ensure these are true */ + tor_assert(lo > 0); + tor_assert(hi > lo); + tor_assert(max_flush > 0); + + sched_q_low_water = lo; + sched_q_high_water = hi; + sched_max_flush_cells = max_flush; +} + diff --git a/src/or/scheduler.h b/src/or/scheduler.h new file mode 100644 index 0000000000..27dd2d8388 --- /dev/null +++ b/src/or/scheduler.h @@ -0,0 +1,50 @@ +/* * Copyright (c) 2013-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file scheduler.h + * \brief Header file for scheduler.c + **/ + +#ifndef TOR_SCHEDULER_H +#define TOR_SCHEDULER_H + +#include "or.h" +#include "channel.h" +#include "testsupport.h" + +/* Global-visibility scheduler functions */ + +/* Set up and shut down the scheduler from main.c */ +void scheduler_free_all(void); +void scheduler_init(void); +MOCK_DECL(void, scheduler_run, (void)); + +/* Mark channels as having cells or wanting/not wanting writes */ +MOCK_DECL(void,scheduler_channel_doesnt_want_writes,(channel_t *chan)); +MOCK_DECL(void,scheduler_channel_has_waiting_cells,(channel_t *chan)); +void scheduler_channel_wants_writes(channel_t *chan); + +/* Notify the scheduler of a channel being closed */ +MOCK_DECL(void,scheduler_release_channel,(channel_t *chan)); + +/* Notify scheduler of queue size adjustments */ +void scheduler_adjust_queue_size(channel_t *chan, int dir, uint64_t adj); + +/* Notify scheduler that a channel's queue position may have changed */ +void scheduler_touch_channel(channel_t *chan); + +/* Adjust the watermarks from config file*/ +void scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush); + +/* Things only scheduler.c and its test suite should see */ + +#ifdef SCHEDULER_PRIVATE_ +MOCK_DECL(STATIC int, scheduler_compare_channels, + (const void *c1_v, const void *c2_v)); +STATIC uint64_t scheduler_get_queue_heuristic(void); +STATIC void scheduler_update_queue_heuristic(time_t now); +#endif + +#endif /* !defined(TOR_SCHEDULER_H) */ + diff --git a/src/or/statefile.c b/src/or/statefile.c index bcb7b07417..dd1894beb7 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -1,9 +1,10 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#define STATEFILE_PRIVATE #include "or.h" #include "circuitstats.h" #include "config.h" @@ -12,6 +13,7 @@ #include "hibernate.h" #include "rephist.h" #include "router.h" +#include "sandbox.h" #include "statefile.h" /** A list of state-file "abbreviations," for compatibility. */ @@ -90,8 +92,11 @@ static config_var_t state_vars_[] = { #undef VAR #undef V -static int or_state_validate(or_state_t *old_options, or_state_t *options, - int from_setconf, char **msg); +static int or_state_validate(or_state_t *state, char **msg); + +static int or_state_validate_cb(void *old_options, void *options, + void *default_options, + int from_setconf, char **msg); /** Magic value for or_state_t. */ #define OR_STATE_MAGIC 0x57A73f57 @@ -109,7 +114,7 @@ static const config_format_t state_format = { STRUCT_OFFSET(or_state_t, magic_), state_abbrevs_, state_vars_, - (validate_fn_t)or_state_validate, + or_state_validate_cb, &state_extra_var, }; @@ -117,8 +122,8 @@ static const config_format_t state_format = { static or_state_t *global_state = NULL; /** Return the persistent state struct for this Tor. */ -or_state_t * -get_or_state(void) +MOCK_IMPL(or_state_t *, +get_or_state, (void)) { tor_assert(global_state); return global_state; @@ -194,21 +199,27 @@ validate_transports_in_state(or_state_t *state) return 0; } -/** Return 0 if every setting in <b>state</b> is reasonable, and a - * permissible transition from <b>old_state</b>. Else warn and return -1. - * Should have no side effects, except for normalizing the contents of - * <b>state</b>. - */ -/* XXX from_setconf is here because of bug 238 */ static int -or_state_validate(or_state_t *old_state, or_state_t *state, - int from_setconf, char **msg) +or_state_validate_cb(void *old_state, void *state, void *default_state, + int from_setconf, char **msg) { /* We don't use these; only options do. Still, we need to match that * signature. */ (void) from_setconf; + (void) default_state; (void) old_state; + return or_state_validate(state, msg); +} + +/** Return 0 if every setting in <b>state</b> is reasonable, and a + * permissible transition from <b>old_state</b>. Else warn and return -1. + * Should have no side effects, except for normalizing the contents of + * <b>state</b>. + */ +static int +or_state_validate(or_state_t *state, char **msg) +{ if (entry_guards_parse_state(state, 0, msg)<0) return -1; @@ -237,7 +248,8 @@ or_state_set(or_state_t *new_state) tor_free(err); ret = -1; } - if (circuit_build_times_parse_state(&circ_times, global_state) < 0) { + if (circuit_build_times_parse_state( + get_circuit_build_times_mutable(),global_state) < 0) { ret = -1; } return ret; @@ -249,7 +261,7 @@ or_state_set(or_state_t *new_state) static void or_state_save_broken(char *fname) { - int i; + int i, res; file_status_t status; char *fname2 = NULL; for (i = 0; i < 100; ++i) { @@ -263,12 +275,18 @@ or_state_save_broken(char *fname) log_warn(LD_BUG, "Unable to parse state in \"%s\"; too many saved bad " "state files to move aside. Discarding the old state file.", fname); - unlink(fname); + res = unlink(fname); + if (res != 0) { + log_warn(LD_FS, + "Also couldn't discard old state file \"%s\" because " + "unlink() failed: %s", + fname, strerror(errno)); + } } else { log_warn(LD_BUG, "Unable to parse state in \"%s\". Moving it aside " "to \"%s\". This could be a bug in Tor; please tell " "the developers.", fname, fname2); - if (rename(fname, fname2) < 0) { + if (tor_rename(fname, fname2) < 0) {//XXXX sandbox prohibits log_warn(LD_BUG, "Weirdly, I couldn't even move the state aside. The " "OS gave an error of %s", strerror(errno)); } @@ -276,6 +294,16 @@ or_state_save_broken(char *fname) tor_free(fname2); } +STATIC or_state_t * +or_state_new(void) +{ + or_state_t *new_state = tor_malloc_zero(sizeof(or_state_t)); + new_state->magic_ = OR_STATE_MAGIC; + config_init(&state_format, new_state); + + return new_state; +} + /** Reload the persistent state from disk, generating a new state as needed. * Return 0 on success, less than 0 on failure. */ @@ -295,7 +323,10 @@ or_state_load(void) goto done; } break; + /* treat empty state files as if the file doesn't exist, and generate + * a new state file, overwriting the empty file in or_state_save() */ case FN_NOENT: + case FN_EMPTY: break; case FN_ERROR: case FN_DIR: @@ -303,9 +334,7 @@ or_state_load(void) log_warn(LD_GENERAL,"State file \"%s\" is not a file? Failing.", fname); goto done; } - new_state = tor_malloc_zero(sizeof(or_state_t)); - new_state->magic_ = OR_STATE_MAGIC; - config_init(&state_format, new_state); + new_state = or_state_new(); if (contents) { config_line_t *lines=NULL; int assign_retval; @@ -322,7 +351,7 @@ or_state_load(void) } } - if (!badstate && or_state_validate(NULL, new_state, 1, &errmsg) < 0) + if (!badstate && or_state_validate(new_state, &errmsg) < 0) badstate = 1; if (errmsg) { @@ -340,9 +369,7 @@ or_state_load(void) tor_free(contents); config_free(&state_format, new_state); - new_state = tor_malloc_zero(sizeof(or_state_t)); - new_state->magic_ = OR_STATE_MAGIC; - config_init(&state_format, new_state); + new_state = or_state_new(); } else if (contents) { log_info(LD_GENERAL, "Loaded state from \"%s\"", fname); } else { @@ -404,7 +431,7 @@ or_state_save(time_t now) * to avoid redundant writes. */ entry_guards_update_state(global_state); rep_hist_update_state(global_state); - circuit_build_times_update_state(&circ_times, global_state); + circuit_build_times_update_state(get_circuit_build_times(), global_state); if (accounting_is_enabled(get_options())) accounting_run_housekeeping(now); @@ -449,7 +476,7 @@ or_state_save(time_t now) /** Return the config line for transport <b>transport</b> in the current state. * Return NULL if there is no config line for <b>transport</b>. */ -static config_line_t * +STATIC config_line_t * get_transport_in_state_by_name(const char *transport) { or_state_t *or_state = get_or_state(); @@ -607,10 +634,19 @@ save_transport_to_state(const char *transport, tor_free(transport_addrport); } +STATIC void +or_state_free(or_state_t *state) +{ + if (!state) + return; + + config_free(&state_format, state); +} + void or_state_free_all(void) { - config_free(&state_format, global_state); + or_state_free(global_state); global_state = NULL; } diff --git a/src/or/statefile.h b/src/or/statefile.h index dcdee6c604..8c790ea206 100644 --- a/src/or/statefile.h +++ b/src/or/statefile.h @@ -1,13 +1,13 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_STATEFILE_H #define TOR_STATEFILE_H -or_state_t *get_or_state(void); +MOCK_DECL(or_state_t *,get_or_state,(void)); int did_last_state_file_write_fail(void); int or_state_save(time_t now); @@ -18,5 +18,11 @@ int or_state_load(void); int or_state_loaded(void); void or_state_free_all(void); +#ifdef STATEFILE_PRIVATE +STATIC config_line_t *get_transport_in_state_by_name(const char *transport); +STATIC void or_state_free(or_state_t *state); +STATIC or_state_t *or_state_new(void); +#endif + #endif diff --git a/src/or/status.c b/src/or/status.c index 69f92ed097..8f7be0aa3c 100644 --- a/src/or/status.c +++ b/src/or/status.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2013, The Tor Project, Inc. */ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -6,7 +6,10 @@ * \brief Keep status information and log the heartbeat messages. **/ +#define STATUS_PRIVATE + #include "or.h" +#include "circuituse.h" #include "config.h" #include "status.h" #include "nodelist.h" @@ -14,25 +17,24 @@ #include "router.h" #include "circuitlist.h" #include "main.h" +#include "rephist.h" #include "hibernate.h" #include "rephist.h" +#include "statefile.h" + +static void log_accounting(const time_t now, const or_options_t *options); +#include "geoip.h" /** Return the total number of circuits. */ -static int +STATIC int count_circuits(void) { - circuit_t *circ; - int nr=0; - - for (circ = circuit_get_global_list_(); circ; circ = circ->next) - nr++; - - return nr; + return smartlist_len(circuit_get_global_list()); } /** Take seconds <b>secs</b> and return a newly allocated human-readable * uptime string */ -static char * +STATIC char * secs_to_uptime(long secs) { long int days = secs / 86400; @@ -59,7 +61,7 @@ secs_to_uptime(long secs) /** Take <b>bytes</b> and returns a newly allocated human-readable usage * string. */ -static char * +STATIC char * bytes_to_usage(uint64_t bytes) { char *bw_string = NULL; @@ -91,7 +93,6 @@ log_heartbeat(time_t now) const int hibernating = we_are_hibernating(); const or_options_t *options = get_options(); - (void)now; if (public_server_mode(options) && !hibernating) { /* Let's check if we are in the current cached consensus. */ @@ -109,21 +110,46 @@ log_heartbeat(time_t now) log_fn(LOG_NOTICE, LD_HEARTBEAT, "Heartbeat: Tor's uptime is %s, with %d " "circuits open. I've sent %s and received %s.%s", - uptime, count_circuits(),bw_sent,bw_rcvd, + uptime, count_circuits(), bw_sent, bw_rcvd, hibernating?" We are currently hibernating.":""); - if (stats_n_data_cells_packaged && !hibernating) - log_notice(LD_HEARTBEAT, "Average packaged cell fullness: %2.3f%%", - 100*(U64_TO_DBL(stats_n_data_bytes_packaged) / - U64_TO_DBL(stats_n_data_cells_packaged*RELAY_PAYLOAD_SIZE)) ); + if (server_mode(options) && accounting_is_enabled(options) && !hibernating) { + log_accounting(now, options); + } - if (r > 1.0) { - double overhead = ( r - 1.0 ) * 100.0; - log_notice(LD_HEARTBEAT, "TLS write overhead: %.f%%", overhead); + double fullness_pct = 100; + if (stats_n_data_cells_packaged && !hibernating) { + fullness_pct = + 100*(U64_TO_DBL(stats_n_data_bytes_packaged) / + U64_TO_DBL(stats_n_data_cells_packaged*RELAY_PAYLOAD_SIZE)); } + const double overhead_pct = ( r - 1.0 ) * 100.0; + +#define FULLNESS_PCT_THRESHOLD 80 +#define TLS_OVERHEAD_THRESHOLD 15 + + const int severity = (fullness_pct < FULLNESS_PCT_THRESHOLD || + overhead_pct > TLS_OVERHEAD_THRESHOLD) + ? LOG_NOTICE : LOG_INFO; - if (public_server_mode(options)) + log_fn(severity, LD_HEARTBEAT, + "Average packaged cell fullness: %2.3f%%. " + "TLS write overhead: %.f%%", fullness_pct, overhead_pct); + + if (public_server_mode(options)) { rep_hist_log_circuit_handshake_stats(now); + rep_hist_log_link_protocol_counts(); + } + + circuit_log_ancient_one_hop_circuits(1800); + + if (options->BridgeRelay) { + char *msg = NULL; + msg = format_client_stats_heartbeat(now); + if (msg) + log_notice(LD_HEARTBEAT, "%s", msg); + tor_free(msg); + } tor_free(uptime); tor_free(bw_sent); @@ -132,3 +158,31 @@ log_heartbeat(time_t now) return 0; } +static void +log_accounting(const time_t now, const or_options_t *options) +{ + or_state_t *state = get_or_state(); + char *acc_rcvd = bytes_to_usage(state->AccountingBytesReadInInterval); + char *acc_sent = bytes_to_usage(state->AccountingBytesWrittenInInterval); + uint64_t acc_bytes = options->AccountingMax; + char *acc_max; + time_t interval_end = accounting_get_end_time(); + char end_buf[ISO_TIME_LEN + 1]; + char *remaining = NULL; + if (options->AccountingRule == ACCT_SUM) + acc_bytes *= 2; + acc_max = bytes_to_usage(acc_bytes); + format_local_iso_time(end_buf, interval_end); + remaining = secs_to_uptime(interval_end - now); + + log_notice(LD_HEARTBEAT, "Heartbeat: Accounting enabled. " + "Sent: %s / %s, Received: %s / %s. The " + "current accounting interval ends on %s, in %s.", + acc_sent, acc_max, acc_rcvd, acc_max, end_buf, remaining); + + tor_free(acc_rcvd); + tor_free(acc_sent); + tor_free(acc_max); + tor_free(remaining); +} + diff --git a/src/or/status.h b/src/or/status.h index 7c3b969c86..3dd8206e0f 100644 --- a/src/or/status.h +++ b/src/or/status.h @@ -1,10 +1,18 @@ -/* Copyright (c) 2010-2013, The Tor Project, Inc. */ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_STATUS_H #define TOR_STATUS_H +#include "testsupport.h" + int log_heartbeat(time_t now); +#ifdef STATUS_PRIVATE +STATIC int count_circuits(void); +STATIC char *secs_to_uptime(long secs); +STATIC char *bytes_to_usage(uint64_t bytes); +#endif + #endif diff --git a/src/or/tor_main.c b/src/or/tor_main.c index 05dc0bf0bf..af03b8c06a 100644 --- a/src/or/tor_main.c +++ b/src/or/tor_main.c @@ -1,6 +1,6 @@ /* Copyright 2001-2004 Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** String describing which Tor Git repository version the source was diff --git a/src/or/transports.c b/src/or/transports.c index 3749d6bb21..6f07054ea8 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2013, The Tor Project, Inc. */ +/* Copyright (c) 2011-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -51,35 +51,37 @@ * logic, because of race conditions that can cause dangling * pointers. ] * - * <b>In even more detail, this is what happens when a SIGHUP - * occurs:</b> + * <b>In even more detail, this is what happens when a config read + * (like a SIGHUP or a SETCONF) occurs:</b> * * We immediately destroy all unconfigured proxies (We shouldn't have - * unconfigured proxies in the first place, except when SIGHUP rings - * immediately after tor is launched.). + * unconfigured proxies in the first place, except when the config + * read happens immediately after tor is launched.). * * We mark all managed proxies and transports to signify that they * must be removed if they don't contribute by the new torrc * (we mark using the <b>marked_for_removal</b> element). * We also mark all managed proxies to signify that they might need to * be restarted so that they end up supporting all the transports the - * new torrc wants them to support (using the <b>got_hup</b> element). + * new torrc wants them to support + * (we mark using the <b>was_around_before_config_read</b> element). * We also clear their <b>transports_to_launch</b> list so that we can * put there the transports we need to launch according to the new * torrc. * * We then start parsing torrc again. * - * Everytime we encounter a transport line using a known pre-SIGHUP - * managed proxy, we cleanse that proxy from the removal mark. - * We also mark it as unconfigured so that on the next scheduled - * events tick, we investigate whether we need to restart the proxy - * so that it also spawns the new transports. - * If the post-SIGHUP <b>transports_to_launch</b> list is identical to - * the pre-SIGHUP one, it means that no changes were introduced to - * this proxy during the SIGHUP and no restart has to take place. + * Everytime we encounter a transport line using a managed proxy that + * was around before the config read, we cleanse that proxy from the + * removal mark. We also toggle the <b>check_if_restarts_needed</b> + * flag, so that on the next <b>pt_configure_remaining_proxies</b> + * tick, we investigate whether we need to restart the proxy so that + * it also spawns the new transports. If the post-config-read + * <b>transports_to_launch</b> list is identical to the pre-config-read + * one, it means that no changes were introduced to this proxy during + * the config read and no restart has to take place. * - * During the post-SIGHUP torrc parsing, we unmark all transports + * During the post-config-read torrc parsing, we unmark all transports * spawned by managed proxies that we find in our torrc. * We do that so that if we don't need to restart a managed proxy, we * can continue using its old transports normally. @@ -95,24 +97,21 @@ #include "util.h" #include "router.h" #include "statefile.h" +#include "entrynodes.h" +#include "connection_or.h" +#include "ext_orport.h" +#include "control.h" static process_environment_t * create_managed_proxy_environment(const managed_proxy_t *mp); static INLINE int proxy_configuration_finished(const managed_proxy_t *mp); -static void managed_proxy_destroy(managed_proxy_t *mp, - int also_terminate_process); - static void handle_finished_proxy(managed_proxy_t *mp); -static int configure_proxy(managed_proxy_t *mp); - static void parse_method_error(const char *line, int is_server_method); #define parse_server_method_error(l) parse_method_error(l, 1) #define parse_client_method_error(l) parse_method_error(l, 0) -static INLINE void free_execve_args(char **arg); - /** Managed proxy protocol strings */ #define PROTO_ENV_ERROR "ENV-ERROR" #define PROTO_NEG_SUCCESS "VERSION" @@ -123,6 +122,8 @@ static INLINE void free_execve_args(char **arg); #define PROTO_SMETHOD_ERROR "SMETHOD-ERROR" #define PROTO_CMETHODS_DONE "CMETHODS DONE" #define PROTO_SMETHODS_DONE "SMETHODS DONE" +#define PROTO_PROXY_DONE "PROXY DONE" +#define PROTO_PROXY_ERROR "PROXY-ERROR" /** The first and only supported - at the moment - configuration protocol version. */ @@ -136,7 +137,8 @@ static smartlist_t *transport_list = NULL; SOCKS version <b>socks_ver</b>. */ static transport_t * transport_new(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver) + const char *name, int socks_ver, + const char *extra_info_args) { transport_t *t = tor_malloc_zero(sizeof(transport_t)); @@ -144,6 +146,8 @@ transport_new(const tor_addr_t *addr, uint16_t port, t->port = port; t->name = tor_strdup(name); t->socks_version = socks_ver; + if (extra_info_args) + t->extra_info_args = tor_strdup(extra_info_args); return t; } @@ -156,6 +160,7 @@ transport_free(transport_t *transport) return; tor_free(transport->name); + tor_free(transport->extra_info_args); tor_free(transport); } @@ -319,11 +324,11 @@ transport_add(transport_t *t) /** Remember a new pluggable transport proxy at <b>addr</b>:<b>port</b>. * <b>name</b> is set to the name of the protocol this proxy uses. * <b>socks_ver</b> is set to the SOCKS version of the proxy. */ -int -transport_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver) +MOCK_IMPL(int, +transport_add_from_config, (const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver)) { - transport_t *t = transport_new(addr, port, name, socks_ver); + transport_t *t = transport_new(addr, port, name, socks_ver, NULL); int r = transport_add(t); @@ -434,6 +439,17 @@ add_transport_to_proxy(const char *transport, managed_proxy_t *mp) static int proxy_needs_restart(const managed_proxy_t *mp) { + int ret = 1; + char* proxy_uri; + + /* If the PT proxy config has changed, then all existing pluggable transports + * should be restarted. + */ + + proxy_uri = get_pt_proxy_uri(); + if (strcmp_opt(proxy_uri, mp->proxy_uri) != 0) + goto needs_restart; + /* mp->transport_to_launch is populated with the names of the transports that must be launched *after* the SIGHUP. mp->transports is populated with the transports that were @@ -454,10 +470,10 @@ proxy_needs_restart(const managed_proxy_t *mp) } SMARTLIST_FOREACH_END(t); - return 0; - + ret = 0; needs_restart: - return 1; + tor_free(proxy_uri); + return ret; } /** Managed proxy <b>mp</b> must be restarted. Do all the necessary @@ -488,6 +504,11 @@ proxy_prepare_for_restart(managed_proxy_t *mp) SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); smartlist_clear(mp->transports); + /* Reset the proxy's HTTPS/SOCKS proxy */ + tor_free(mp->proxy_uri); + mp->proxy_uri = get_pt_proxy_uri(); + mp->proxy_supported = 0; + /* flag it as an infant proxy so that it gets launched on next tick */ mp->conf_state = PT_PROTO_INFANT; unconfigured_proxies_n++; @@ -531,8 +552,7 @@ launch_managed_proxy(managed_proxy_t *mp) } /** Check if any of the managed proxies we are currently trying to - * configure have anything new to say. This is called from - * run_scheduled_events(). */ + * configure has anything new to say. */ void pt_configure_remaining_proxies(void) { @@ -549,14 +569,15 @@ pt_configure_remaining_proxies(void) assert_unconfigured_count_ok(); SMARTLIST_FOREACH_BEGIN(tmp, managed_proxy_t *, mp) { - tor_assert(mp->conf_state != PT_PROTO_BROKEN || + tor_assert(mp->conf_state != PT_PROTO_BROKEN && mp->conf_state != PT_PROTO_FAILED_LAUNCH); - if (mp->got_hup) { - mp->got_hup = 0; + if (mp->was_around_before_config_read) { + /* This proxy is marked by a config read. Check whether we need + to restart it. */ + + mp->was_around_before_config_read = 0; - /* This proxy is marked by a SIGHUP. Check whether we need to - restart it. */ if (proxy_needs_restart(mp)) { log_info(LD_GENERAL, "Preparing managed proxy '%s' for restart.", mp->argv[0]); @@ -589,7 +610,7 @@ pt_configure_remaining_proxies(void) * Return 1 if the transport configuration finished, and return 0 * otherwise (if we still have more configuring to do for this * proxy). */ -static int +STATIC int configure_proxy(managed_proxy_t *mp) { int configuration_finished = 0; @@ -657,6 +678,7 @@ register_server_proxy(const managed_proxy_t *mp) save_transport_to_state(t->name, &t->addr, t->port); log_notice(LD_GENERAL, "Registered server transport '%s' at '%s'", t->name, fmt_addrport(&t->addr, t->port)); + control_event_transport_launched("server", t->name, &t->addr, t->port); } SMARTLIST_FOREACH_END(t); } @@ -679,9 +701,11 @@ register_client_proxy(const managed_proxy_t *mp) break; case 0: log_info(LD_GENERAL, "Successfully registered transport %s", t->name); + control_event_transport_launched("client", t->name, &t->addr, t->port); break; case 1: log_info(LD_GENERAL, "Successfully registered transport %s", t->name); + control_event_transport_launched("client", t->name, &t->addr, t->port); transport_free(transport_tmp); break; } @@ -699,7 +723,7 @@ register_proxy(const managed_proxy_t *mp) } /** Free memory allocated by managed proxy <b>mp</b>. */ -static void +STATIC void managed_proxy_destroy(managed_proxy_t *mp, int also_terminate_process) { @@ -713,17 +737,60 @@ managed_proxy_destroy(managed_proxy_t *mp, smartlist_free(mp->transports_to_launch); /* remove it from the list of managed proxies */ - smartlist_remove(managed_proxy_list, mp); + if (managed_proxy_list) + smartlist_remove(managed_proxy_list, mp); /* free the argv */ free_execve_args(mp->argv); + /* free the outgoing proxy URI */ + tor_free(mp->proxy_uri); + tor_process_handle_destroy(mp->process_handle, also_terminate_process); mp->process_handle = NULL; tor_free(mp); } +/** Convert the tor proxy options to a URI suitable for TOR_PT_PROXY. + * Return a newly allocated string containing the URI, or NULL if no + * proxy is set. */ +STATIC char * +get_pt_proxy_uri(void) +{ + const or_options_t *options = get_options(); + char *uri = NULL; + + if (options->Socks4Proxy || options->Socks5Proxy || options->HTTPSProxy) { + char addr[TOR_ADDR_BUF_LEN+1]; + + if (options->Socks4Proxy) { + tor_addr_to_str(addr, &options->Socks4ProxyAddr, sizeof(addr), 1); + tor_asprintf(&uri, "socks4a://%s:%d", addr, options->Socks4ProxyPort); + } else if (options->Socks5Proxy) { + tor_addr_to_str(addr, &options->Socks5ProxyAddr, sizeof(addr), 1); + if (!options->Socks5ProxyUsername && !options->Socks5ProxyPassword) { + tor_asprintf(&uri, "socks5://%s:%d", addr, options->Socks5ProxyPort); + } else { + tor_asprintf(&uri, "socks5://%s:%s@%s:%d", + options->Socks5ProxyUsername, + options->Socks5ProxyPassword, + addr, options->Socks5ProxyPort); + } + } else if (options->HTTPSProxy) { + tor_addr_to_str(addr, &options->HTTPSProxyAddr, sizeof(addr), 1); + if (!options->HTTPSProxyAuthenticator) { + tor_asprintf(&uri, "http://%s:%d", addr, options->HTTPSProxyPort); + } else { + tor_asprintf(&uri, "http://%s@%s:%d", options->HTTPSProxyAuthenticator, + addr, options->HTTPSProxyPort); + } + } + } + + return uri; +} + /** Handle a configured or broken managed proxy <b>mp</b>. */ static void handle_finished_proxy(managed_proxy_t *mp) @@ -736,6 +803,13 @@ handle_finished_proxy(managed_proxy_t *mp) managed_proxy_destroy(mp, 0); /* destroy it but don't terminate */ break; case PT_PROTO_CONFIGURED: /* if configured correctly: */ + if (mp->proxy_uri && !mp->proxy_supported) { + log_warn(LD_CONFIG, "Managed proxy '%s' did not configure the " + "specified outgoing proxy and will be terminated.", + mp->argv[0]); + managed_proxy_destroy(mp, 1); /* annihilate it. */ + break; + } register_proxy(mp); /* register its transports */ mp->conf_state = PT_PROTO_COMPLETED; /* and mark it as completed. */ break; @@ -750,7 +824,6 @@ handle_finished_proxy(managed_proxy_t *mp) } unconfigured_proxies_n--; - tor_assert(unconfigured_proxies_n >= 0); } /** Return true if the configuration of the managed proxy <b>mp</b> is @@ -781,7 +854,7 @@ handle_methods_done(const managed_proxy_t *mp) /** Handle a configuration protocol <b>line</b> received from a * managed proxy <b>mp</b>. */ -void +STATIC void handle_proxy_line(const char *line, managed_proxy_t *mp) { log_info(LD_GENERAL, "Got a line from managed proxy '%s': (%s)", @@ -854,6 +927,22 @@ handle_proxy_line(const char *line, managed_proxy_t *mp) goto err; return; + } else if (!strcmpstart(line, PROTO_PROXY_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (mp->proxy_uri) { + mp->proxy_supported = 1; + return; + } + + /* No proxy was configured, this should log */ + } else if (!strcmpstart(line, PROTO_PROXY_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_proxy_error(line); + goto err; } else if (!strcmpstart(line, SPAWN_ERROR_MESSAGE)) { /* managed proxy launch failed: parse error message to learn why. */ int retval, child_state, saved_errno; @@ -882,7 +971,7 @@ handle_proxy_line(const char *line, managed_proxy_t *mp) } /** Parses an ENV-ERROR <b>line</b> and warns the user accordingly. */ -void +STATIC void parse_env_error(const char *line) { /* (Length of the protocol string) plus (a space) and (the first char of @@ -898,7 +987,7 @@ parse_env_error(const char *line) /** Handles a VERSION <b>line</b>. Updates the configuration protocol * version in <b>mp</b>. */ -int +STATIC int parse_version(const char *line, managed_proxy_t *mp) { if (strlen(line) < (strlen(PROTO_NEG_SUCCESS) + 2)) { @@ -939,14 +1028,14 @@ parse_method_error(const char *line, int is_server) /** Parses an SMETHOD <b>line</b> and if well-formed it registers the * new transport in <b>mp</b>. */ -int +STATIC int parse_smethod_line(const char *line, managed_proxy_t *mp) { int r; smartlist_t *items = NULL; char *method_name=NULL; - + char *args_string=NULL; char *addrport=NULL; tor_addr_t tor_addr; char *address=NULL; @@ -963,6 +1052,9 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) goto err; } + /* Example of legit SMETHOD line: + SMETHOD obfs2 0.0.0.0:25612 ARGS:secret=supersekrit,key=superkey */ + tor_assert(!strcmp(smartlist_get(items,0),PROTO_SMETHOD)); method_name = smartlist_get(items,1); @@ -990,7 +1082,19 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) goto err; } - transport = transport_new(&tor_addr, port, method_name, PROXY_NONE); + if (smartlist_len(items) > 3) { + /* Seems like there are also some [options] in the SMETHOD line. + Let's see if we can parse them. */ + char *options_string = smartlist_get(items, 3); + log_debug(LD_CONFIG, "Got options_string: %s", options_string); + if (!strcmpstart(options_string, "ARGS:")) { + args_string = options_string+strlen("ARGS:"); + log_debug(LD_CONFIG, "Got ARGS: %s", args_string); + } + } + + transport = transport_new(&tor_addr, port, method_name, + PROXY_NONE, args_string); if (!transport) goto err; @@ -1016,7 +1120,7 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) /** Parses a CMETHOD <b>line</b>, and if well-formed it registers * the new transport in <b>mp</b>. */ -int +STATIC int parse_cmethod_line(const char *line, managed_proxy_t *mp) { int r; @@ -1082,7 +1186,7 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) goto err; } - transport = transport_new(&tor_addr, port, method_name, socks_ver); + transport = transport_new(&tor_addr, port, method_name, socks_ver, NULL); if (!transport) goto err; @@ -1105,6 +1209,65 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) return r; } +/** Parses an PROXY-ERROR <b>line</b> and warns the user accordingly. */ +STATIC void +parse_proxy_error(const char *line) +{ + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(PROTO_PROXY_ERROR) + 2)) + log_notice(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", PROTO_PROXY_ERROR); + + log_warn(LD_CONFIG, "Managed proxy failed to configure the " + "pluggable transport's outgoing proxy. (%s)", + line+strlen(PROTO_PROXY_ERROR)+1); +} + +/** Return a newly allocated string that tor should place in + * TOR_PT_SERVER_TRANSPORT_OPTIONS while configuring the server + * manged proxy in <b>mp</b>. Return NULL if no such options are found. */ +STATIC char * +get_transport_options_for_server_proxy(const managed_proxy_t *mp) +{ + char *options_string = NULL; + smartlist_t *string_sl = smartlist_new(); + + tor_assert(mp->is_server); + + /** Loop over the transports of the proxy. If we have options for + any of them, format them appropriately and place them in our + smartlist. Finally, join our smartlist to get the final + string. */ + SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, const char *, transport) { + smartlist_t *options_tmp_sl = NULL; + options_tmp_sl = get_options_for_server_transport(transport); + if (!options_tmp_sl) + continue; + + /** Loop over the options of this transport, escape them, and + place them in the smartlist. */ + SMARTLIST_FOREACH_BEGIN(options_tmp_sl, const char *, options) { + char *escaped_opts = tor_escape_str_for_pt_args(options, ":;\\"); + smartlist_add_asprintf(string_sl, "%s:%s", + transport, escaped_opts); + tor_free(escaped_opts); + } SMARTLIST_FOREACH_END(options); + + SMARTLIST_FOREACH(options_tmp_sl, char *, c, tor_free(c)); + smartlist_free(options_tmp_sl); + } SMARTLIST_FOREACH_END(transport); + + if (smartlist_len(string_sl)) { + options_string = smartlist_join_strings(string_sl, ";", 0, NULL); + } + + SMARTLIST_FOREACH(string_sl, char *, t, tor_free(t)); + smartlist_free(string_sl); + + return options_string; +} + /** Return the string that tor should place in TOR_PT_SERVER_BINDADDR * while configuring the server managed proxy in <b>mp</b>. The * string is stored in the heap, and it's the the responsibility of @@ -1139,6 +1302,8 @@ get_bindaddr_for_server_proxy(const managed_proxy_t *mp) static process_environment_t * create_managed_proxy_environment(const managed_proxy_t *mp) { + const or_options_t *options = get_options(); + /* Environment variables to be added to or set in mp's environment. */ smartlist_t *envs = smartlist_new(); /* XXXX The next time someone touches this code, shorten the name of @@ -1176,8 +1341,10 @@ create_managed_proxy_environment(const managed_proxy_t *mp) { char *orport_tmp = get_first_listener_addrport_string(CONN_TYPE_OR_LISTENER); - smartlist_add_asprintf(envs, "TOR_PT_ORPORT=%s", orport_tmp); - tor_free(orport_tmp); + if (orport_tmp) { + smartlist_add_asprintf(envs, "TOR_PT_ORPORT=%s", orport_tmp); + tor_free(orport_tmp); + } } { @@ -1186,13 +1353,49 @@ create_managed_proxy_environment(const managed_proxy_t *mp) tor_free(bindaddr_tmp); } + { + char *server_transport_options = + get_transport_options_for_server_proxy(mp); + if (server_transport_options) { + smartlist_add_asprintf(envs, "TOR_PT_SERVER_TRANSPORT_OPTIONS=%s", + server_transport_options); + tor_free(server_transport_options); + } + } + /* XXX024 Remove the '=' here once versions of obfsproxy which * assert that this env var exists are sufficiently dead. * * (If we remove this line entirely, some joker will stick this * variable in Tor's environment and crash PTs that try to parse * it even when not run in server mode.) */ - smartlist_add(envs, tor_strdup("TOR_PT_EXTENDED_SERVER_PORT=")); + + if (options->ExtORPort_lines) { + char *ext_or_addrport_tmp = + get_first_listener_addrport_string(CONN_TYPE_EXT_OR_LISTENER); + char *cookie_file_loc = get_ext_or_auth_cookie_file_name(); + + if (ext_or_addrport_tmp) { + smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT=%s", + ext_or_addrport_tmp); + } + smartlist_add_asprintf(envs, "TOR_PT_AUTH_COOKIE_FILE=%s", + cookie_file_loc); + + tor_free(ext_or_addrport_tmp); + tor_free(cookie_file_loc); + + } else { + smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT="); + } + } else { + /* If ClientTransportPlugin has a HTTPS/SOCKS proxy configured, set the + * TOR_PT_PROXY line. + */ + + if (mp->proxy_uri) { + smartlist_add_asprintf(envs, "TOR_PT_PROXY=%s", mp->proxy_uri); + } } SMARTLIST_FOREACH_BEGIN(envs, const char *, env_var) { @@ -1216,7 +1419,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp) * <b>proxy_argv</b>. * * Requires that proxy_argv have at least one element. */ -static managed_proxy_t * +STATIC managed_proxy_t * managed_proxy_create(const smartlist_t *transport_list, char **proxy_argv, int is_server) { @@ -1225,6 +1428,7 @@ managed_proxy_create(const smartlist_t *transport_list, mp->is_server = is_server; mp->argv = proxy_argv; mp->transports = smartlist_new(); + mp->proxy_uri = get_pt_proxy_uri(); mp->transports_to_launch = smartlist_new(); SMARTLIST_FOREACH(transport_list, const char *, transport, @@ -1250,9 +1454,9 @@ managed_proxy_create(const smartlist_t *transport_list, * Requires that proxy_argv be a NULL-terminated array of command-line * elements, containing at least one element. **/ -void -pt_kickstart_proxy(const smartlist_t *transport_list, - char **proxy_argv, int is_server) +MOCK_IMPL(void, +pt_kickstart_proxy, (const smartlist_t *transport_list, + char **proxy_argv, int is_server)) { managed_proxy_t *mp=NULL; transport_t *old_transport = NULL; @@ -1267,19 +1471,20 @@ pt_kickstart_proxy(const smartlist_t *transport_list, managed_proxy_create(transport_list, proxy_argv, is_server); } else { /* known proxy. add its transport to its transport list */ - if (mp->got_hup) { - /* If the managed proxy we found is marked by a SIGHUP, it means - that it's not useless and should be kept. If it's marked for - removal, unmark it and increase the unconfigured proxies so - that we try to restart it if we need to. Afterwards, check if - a transport_t for 'transport' used to exist before the SIGHUP - and make sure it doesn't get deleted because we might reuse - it. */ + if (mp->was_around_before_config_read) { + /* If this managed proxy was around even before we read the + config this time, it means that it was already enabled before + and is not useless and should be kept. If it's marked for + removal, unmark it and make sure that we check whether it + needs to be restarted. */ if (mp->marked_for_removal) { mp->marked_for_removal = 0; check_if_restarts_needed = 1; } + /* For each new transport, check if the managed proxy used to + support it before the SIGHUP. If that was the case, make sure + it doesn't get removed because we might reuse it. */ SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport) { old_transport = transport_get_by_name(transport); if (old_transport) @@ -1295,7 +1500,7 @@ pt_kickstart_proxy(const smartlist_t *transport_list, /** Frees the array of pointers in <b>arg</b> used as arguments to execve(2). */ -static INLINE void +STATIC void free_execve_args(char **arg) { char **tmp = arg; @@ -1328,8 +1533,10 @@ pt_prepare_proxy_list_for_config_read(void) tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + /* Mark all proxies for removal, and also note that they have been + here before the config read. */ mp->marked_for_removal = 1; - mp->got_hup = 1; + mp->was_around_before_config_read = 1; SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t)); smartlist_clear(mp->transports_to_launch); } SMARTLIST_FOREACH_END(mp); @@ -1390,6 +1597,8 @@ pt_get_extra_info_descriptor_string(void) tor_assert(mp->transports); SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + char *transport_args = NULL; + /* If the transport proxy returned "0.0.0.0" as its address, and * we know our external IP address, use it. Otherwise, use the * returned address. */ @@ -1405,9 +1614,16 @@ pt_get_extra_info_descriptor_string(void) addrport = fmt_addrport(&t->addr, t->port); } + /* If this transport has any arguments with it, prepend a space + to them so that we can add them to the transport line. */ + if (t->extra_info_args) + tor_asprintf(&transport_args, " %s", t->extra_info_args); + smartlist_add_asprintf(string_chunks, - "transport %s %s", - t->name, addrport); + "transport %s %s%s", + t->name, addrport, + transport_args ? transport_args : ""); + tor_free(transport_args); } SMARTLIST_FOREACH_END(t); } SMARTLIST_FOREACH_END(mp); @@ -1426,6 +1642,57 @@ pt_get_extra_info_descriptor_string(void) return the_string; } +/** Stringify the SOCKS arguments in <b>socks_args</b> according to + * 180_pluggable_transport.txt. The string is allocated on the heap + * and it's the responsibility of the caller to free it after use. */ +char * +pt_stringify_socks_args(const smartlist_t *socks_args) +{ + /* tmp place to store escaped socks arguments, so that we can + concatenate them up afterwards */ + smartlist_t *sl_tmp = NULL; + char *escaped_string = NULL; + char *new_string = NULL; + + tor_assert(socks_args); + tor_assert(smartlist_len(socks_args) > 0); + + sl_tmp = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(socks_args, const char *, s) { + /* Escape ';' and '\'. */ + escaped_string = tor_escape_str_for_pt_args(s, ";\\"); + if (!escaped_string) + goto done; + + smartlist_add(sl_tmp, escaped_string); + } SMARTLIST_FOREACH_END(s); + + new_string = smartlist_join_strings(sl_tmp, ";", 0, NULL); + + done: + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + + return new_string; +} + +/** Return a string of the SOCKS arguments that we should pass to the + * pluggable transports proxy in <b>addr</b>:<b>port</b> according to + * 180_pluggable_transport.txt. The string is allocated on the heap + * and it's the responsibility of the caller to free it after use. */ +char * +pt_get_socks_args_for_proxy_addrport(const tor_addr_t *addr, uint16_t port) +{ + const smartlist_t *socks_args = NULL; + + socks_args = get_socks_args_by_bridge_addrport(addr, port); + if (!socks_args) + return NULL; + + return pt_stringify_socks_args(socks_args); +} + /** The tor config was read. * Destroy all managed proxies that were marked by a previous call to * prepare_proxy_list_for_config_read() and are not used by the new diff --git a/src/or/transports.h b/src/or/transports.h index 6ee82f4556..7c69941496 100644 --- a/src/or/transports.h +++ b/src/or/transports.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2013, The Tor Project, Inc. */ + * Copyright (c) 2007-2015, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -25,18 +25,23 @@ typedef struct transport_t { /** Boolean: We are re-parsing our transport list, and we are going to remove * this one if we don't find it in the list of configured transports. */ unsigned marked_for_removal : 1; + /** Arguments for this transport that must be written to the + extra-info descriptor. */ + char *extra_info_args; } transport_t; void mark_transport_list(void); void sweep_transport_list(void); -int transport_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver); +MOCK_DECL(int, transport_add_from_config, + (const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver)); void transport_free(transport_t *transport); transport_t *transport_get_by_name(const char *name); -void pt_kickstart_proxy(const smartlist_t *transport_list, char **proxy_argv, - int is_server); +MOCK_DECL(void, pt_kickstart_proxy, + (const smartlist_t *transport_list, char **proxy_argv, + int is_server)); #define pt_kickstart_client_proxy(tl, pa) \ pt_kickstart_proxy(tl, pa, 0) @@ -55,6 +60,10 @@ void pt_prepare_proxy_list_for_config_read(void); void sweep_proxy_list(void); smartlist_t *get_transport_proxy_ports(void); +char *pt_stringify_socks_args(const smartlist_t *socks_args); + +char *pt_get_socks_args_for_proxy_addrport(const tor_addr_t *addr, + uint16_t port); #ifdef PT_PRIVATE /** State of the managed proxy configuration protocol. */ @@ -74,6 +83,9 @@ typedef struct { char **argv; /* the cli arguments of this proxy */ int conf_protocol; /* the configuration protocol version used */ + char *proxy_uri; /* the outgoing proxy in TOR_PT_PROXY URI format */ + unsigned int proxy_supported : 1; /* the proxy honors TOR_PT_PROXY */ + int is_server; /* is it a server proxy? */ /* A pointer to the process handle of this managed proxy. */ @@ -90,7 +102,7 @@ typedef struct { * this flag to signify that this proxy might need to be restarted * so that it can listen for other transports according to the new * torrc. */ - unsigned int got_hup : 1; + unsigned int was_around_before_config_read : 1; /* transports to-be-launched by this proxy */ smartlist_t *transports_to_launch; @@ -100,12 +112,26 @@ typedef struct { smartlist_t *transports; } managed_proxy_t; -int parse_cmethod_line(const char *line, managed_proxy_t *mp); -int parse_smethod_line(const char *line, managed_proxy_t *mp); +STATIC int parse_cmethod_line(const char *line, managed_proxy_t *mp); +STATIC int parse_smethod_line(const char *line, managed_proxy_t *mp); + +STATIC int parse_version(const char *line, managed_proxy_t *mp); +STATIC void parse_env_error(const char *line); +STATIC void parse_proxy_error(const char *line); +STATIC void handle_proxy_line(const char *line, managed_proxy_t *mp); +STATIC char *get_transport_options_for_server_proxy(const managed_proxy_t *mp); + +STATIC void managed_proxy_destroy(managed_proxy_t *mp, + int also_terminate_process); + +STATIC managed_proxy_t *managed_proxy_create(const smartlist_t *transport_list, + char **proxy_argv, int is_server); + +STATIC int configure_proxy(managed_proxy_t *mp); + +STATIC char* get_pt_proxy_uri(void); -int parse_version(const char *line, managed_proxy_t *mp); -void parse_env_error(const char *line); -void handle_proxy_line(const char *line, managed_proxy_t *mp); +STATIC void free_execve_args(char **arg); #endif |