diff options
Diffstat (limited to 'src/or')
168 files changed, 18602 insertions, 6025 deletions
diff --git a/src/or/addressmap.c b/src/or/addressmap.c index c92af38254..7e92633601 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -213,8 +213,8 @@ addressmap_clear_excluded_trackexithosts(const or_options_t *options) while (dot > target && *dot != '.') dot--; if (*dot == '.') dot++; - nodename = tor_strndup(dot, len-5-(dot-target));; - node = node_get_by_nickname(nodename, 0); + nodename = tor_strndup(dot, len-5-(dot-target)); + node = node_get_by_nickname(nodename, NNF_NO_WARN_UNNAMED); tor_free(nodename); if (!node || (allow_nodes && !routerset_contains_node(allow_nodes, node)) || @@ -814,7 +814,7 @@ parse_virtual_addr_network(const char *val, sa_family_t family, ipv6?"IPv6":""); return -1; } -#endif +#endif /* 0 */ if (bits > max_prefix_bits) { if (msg) @@ -1044,7 +1044,7 @@ addressmap_register_virtual_address(int type, char *new_address) safe_str_client(*addrp), safe_str_client(new_address)); } -#endif +#endif /* 0 */ return *addrp; } diff --git a/src/or/addressmap.h b/src/or/addressmap.h index 80f453b4c1..1544b76e10 100644 --- a/src/or/addressmap.h +++ b/src/or/addressmap.h @@ -59,7 +59,7 @@ typedef struct virtual_addr_conf_t { STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, tor_addr_t *addr_out); -#endif +#endif /* defined(ADDRESSMAP_PRIVATE) */ -#endif +#endif /* !defined(TOR_ADDRESSMAP_H) */ diff --git a/src/or/bridges.c b/src/or/bridges.c index 0818fb0812..0b1bbbd158 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -54,6 +54,8 @@ struct bridge_info_t { }; static void bridge_free(bridge_info_t *bridge); +static void rewrite_node_address_for_bridge(const bridge_info_t *bridge, + node_t *node); /** 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 @@ -310,7 +312,7 @@ learned_router_identity(const tor_addr_t *addr, uint16_t port, memcpy(&bridge->ed25519_identity, ed_id, sizeof(*ed_id)); learned = 1; } -#endif +#endif /* 0 */ if (learned) { char *transport_info = NULL; const char *transport_name = @@ -454,6 +456,9 @@ bridge_add_from_config(bridge_line_t *bridge_line) b->transport_name = bridge_line->transport_name; b->fetch_status.schedule = DL_SCHED_BRIDGE; b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL; + b->fetch_status.increment_on = DL_SCHED_INCREMENT_ATTEMPT; + /* We can't reset the bridge's download status here, because UseBridges + * might be 0 now, and it might be changed to 1 much later. */ b->socks_args = bridge_line->socks_args; if (!bridge_list) bridge_list = smartlist_new(); @@ -571,6 +576,12 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) return; } + /* If we already have a node_t for this bridge, rewrite its address now. */ + node_t *node = node_get_mutable_by_id(bridge->identity); + if (node) { + rewrite_node_address_for_bridge(bridge, node); + } + tor_addr_port_t bridge_addrport; memcpy(&bridge_addrport.addr, &bridge->addr, sizeof(tor_addr_t)); bridge_addrport.port = bridge->port; @@ -622,6 +633,7 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now) SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { + /* This resets the download status on first use */ if (!download_status_is_ready(&bridge->fetch_status, now, IMPOSSIBLE_TO_DOWNLOAD)) continue; /* don't bother, no need to retry yet */ @@ -632,8 +644,13 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now) continue; } - /* schedule another fetch as if this one will fail, in case it does */ - download_status_failed(&bridge->fetch_status, 0); + /* schedule the next attempt + * we can't increment after a failure, because sometimes we use the + * bridge authority, and sometimes we use the bridge direct */ + download_status_increment_attempt( + &bridge->fetch_status, + safe_str_client(fmt_and_decorate_addr(&bridge->addr)), + now); can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && num_bridge_auths; @@ -779,7 +796,11 @@ 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 = num_bridges_usable() <= 1; + /* Retry directory downloads whenever we get a bridge descriptor: + * - when bootstrapping, and + * - when we aren't sure if any of our bridges are reachable. + * Keep on retrying until we have at least one reachable bridge. */ + int first = num_bridges_usable(0) < 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); @@ -787,8 +808,12 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) if (bridge) { /* if we actually want to use this one */ node_t *node; /* it's here; schedule its re-fetch for a long time from now. */ - if (!from_cache) + if (!from_cache) { + /* This schedules the re-fetch at a constant interval, which produces + * a pattern of bridge traffic. But it's better than trying all + * configured briges several times in the first few minutes. */ download_status_reset(&bridge->fetch_status); + } node = node_get_mutable_by_id(ri->cache_info.identity_digest); tor_assert(node); @@ -805,8 +830,8 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); - /* set entry->made_contact so if it goes down we don't drop it from - * our entry node list */ + /* If we didn't have a reachable bridge before this one, try directory + * documents again. */ if (first) { routerlist_retry_directory_downloads(now); } @@ -814,32 +839,6 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) } } -/** 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 - * directory "server/authority" requests finish. */ -int -any_bridge_descriptors_known(void) -{ - tor_assert(get_options()->UseBridges); - - if (!bridge_list) - return 0; - - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { - const node_t *node; - if (!tor_digest_is_zero(bridge->identity) && - (node = node_get_by_id(bridge->identity)) != NULL && - node->ri) { - return 1; - } - } SMARTLIST_FOREACH_END(bridge); - - return 0; -} - /** Return a smartlist containing all bridge identity digests */ MOCK_IMPL(smartlist_t *, list_bridge_identities, (void)) diff --git a/src/or/bridges.h b/src/or/bridges.h index 3bfc782f9a..54a6250259 100644 --- a/src/or/bridges.h +++ b/src/or/bridges.h @@ -45,7 +45,6 @@ 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); const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port); @@ -66,5 +65,5 @@ MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id, void bridges_free_all(void); -#endif +#endif /* !defined(TOR_BRIDGES_H) */ diff --git a/src/or/buffers.c b/src/or/buffers.c deleted file mode 100644 index 12a6c0239b..0000000000 --- a/src/or/buffers.c +++ /dev/null @@ -1,2192 +0,0 @@ -/* Copyright (c) 2001 Matej Pfajfar. - * Copyright (c) 2001-2004, Roger Dingledine. - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file buffers.c - * \brief Implements a generic buffer interface. - * - * A buf_t is a (fairly) opaque byte-oriented FIFO that can read to or flush - * from memory, sockets, file descriptors, TLS connections, or another buf_t. - * Buffers are implemented as linked lists of memory chunks. - * - * All socket-backed and TLS-based connection_t objects have a pair of - * buffers: one for incoming data, and one for outcoming data. These are fed - * and drained from functions in connection.c, trigged by events that are - * monitored in main.c. - * - * This module has basic support for reading and writing on buf_t objects. It - * also contains specialized functions for handling particular protocols - * on a buf_t backend, including SOCKS (used in connection_edge.c), Tor cells - * (used in connection_or.c and channeltls.c), HTTP (used in directory.c), and - * line-oriented communication (used in control.c). - **/ -#define BUFFERS_PRIVATE -#include "or.h" -#include "addressmap.h" -#include "buffers.h" -#include "config.h" -#include "connection_edge.h" -#include "connection_or.h" -#include "control.h" -#include "reasons.h" -#include "ext_orport.h" -#include "util.h" -#include "torlog.h" -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -//#define PARANOIA - -#ifdef PARANOIA -/** Helper: If PARANOIA is defined, assert that the buffer in local variable - * <b>buf</b> is well-formed. */ -#define check() STMT_BEGIN assert_buf_ok(buf); STMT_END -#else -#define check() STMT_NIL -#endif - -/* Implementation notes: - * - * After flirting with memmove, and dallying with ring-buffers, we're finally - * getting up to speed with the 1970s and implementing buffers as a linked - * list of small chunks. Each buffer has such a list; data is removed from - * the head of the list, and added at the tail. The list is singly linked, - * and the buffer keeps a pointer to the head and the tail. - * - * Every chunk, except the tail, contains at least one byte of data. Data in - * each chunk is contiguous. - * - * When you need to treat the first N characters on a buffer as a contiguous - * string, use the buf_pullup function to make them so. Don't do this more - * than necessary. - * - * The major free Unix kernels have handled buffers like this since, like, - * 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); -static int parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out); - -/* Chunk manipulation functions */ - -#define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0]) - -/* We leave this many NUL bytes at the end of the buffer. */ -#ifdef DISABLE_MEMORY_SENTINELS -#define SENTINEL_LEN 0 -#else -#define SENTINEL_LEN 4 -#endif - -/* Header size plus NUL bytes at the end */ -#define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN) - -/** Return the number of bytes needed to allocate a chunk to hold - * <b>memlen</b> bytes. */ -#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_OVERHEAD + (memlen)) -/** Return the number of usable bytes in a chunk allocated with - * malloc(<b>memlen</b>). */ -#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_OVERHEAD) - -#define DEBUG_SENTINEL - -#if defined(DEBUG_SENTINEL) && !defined(DISABLE_MEMORY_SENTINELS) -#define DBG_S(s) s -#else -#define DBG_S(s) (void)0 -#endif - -#ifdef DISABLE_MEMORY_SENTINELS -#define CHUNK_SET_SENTINEL(chunk, alloclen) STMT_NIL -#else -#define CHUNK_SET_SENTINEL(chunk, alloclen) do { \ - uint8_t *a = (uint8_t*) &(chunk)->mem[(chunk)->memlen]; \ - DBG_S(uint8_t *b = &((uint8_t*)(chunk))[(alloclen)-SENTINEL_LEN]); \ - DBG_S(tor_assert(a == b)); \ - memset(a,0,SENTINEL_LEN); \ - } while (0) -#endif - -/** Return the next character in <b>chunk</b> onto which data can be appended. - * If the chunk is full, this might be off the end of chunk->mem. */ -static inline char * -CHUNK_WRITE_PTR(chunk_t *chunk) -{ - return chunk->data + chunk->datalen; -} - -/** Return the number of bytes that can be written onto <b>chunk</b> without - * running out of space. */ -static inline size_t -CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) -{ - return (chunk->mem + chunk->memlen) - (chunk->data + chunk->datalen); -} - -/** Move all bytes stored in <b>chunk</b> to the front of <b>chunk</b>->mem, - * to free up space at the end. */ -static inline void -chunk_repack(chunk_t *chunk) -{ - if (chunk->datalen && chunk->data != &chunk->mem[0]) { - memmove(chunk->mem, chunk->data, chunk->datalen); - } - chunk->data = &chunk->mem[0]; -} - -/** Keep track of total size of allocated chunks for consistency asserts */ -static size_t total_bytes_allocated_in_chunks = 0; -static void -buf_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 * -chunk_new_with_alloc_size(size_t alloc) -{ - chunk_t *ch; - 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]; - CHUNK_SET_SENTINEL(ch, alloc); - return ch; -} - -/** 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. */ -static inline chunk_t * -chunk_grow(chunk_t *chunk, size_t sz) -{ - off_t offset; - const size_t memlen_orig = chunk->memlen; - const size_t orig_alloc = CHUNK_ALLOC_SIZE(memlen_orig); - const size_t new_alloc = CHUNK_ALLOC_SIZE(sz); - tor_assert(sz > chunk->memlen); - offset = chunk->data - chunk->mem; - chunk = tor_realloc(chunk, new_alloc); - chunk->memlen = sz; - chunk->data = chunk->mem + offset; -#ifdef DEBUG_CHUNK_ALLOC - tor_assert(chunk->DBG_alloc == orig_alloc); - chunk->DBG_alloc = new_alloc; -#endif - total_bytes_allocated_in_chunks += new_alloc - orig_alloc; - CHUNK_SET_SENTINEL(chunk, new_alloc); - return chunk; -} - -/** If a read onto the end of a chunk would be smaller than this number, then - * just start a new chunk. */ -#define MIN_READ_LEN 8 -/** Every chunk should take up at least this many bytes. */ -#define MIN_CHUNK_ALLOC 256 -/** No chunk should take up more than this many bytes. */ -#define MAX_CHUNK_ALLOC 65536 - -/** Return the allocation size we'd like to use to hold <b>target</b> - * bytes. */ -STATIC size_t -preferred_chunk_size(size_t target) -{ - tor_assert(target <= SIZE_T_CEILING - CHUNK_OVERHEAD); - if (CHUNK_ALLOC_SIZE(target) >= MAX_CHUNK_ALLOC) - return CHUNK_ALLOC_SIZE(target); - size_t sz = MIN_CHUNK_ALLOC; - while (CHUNK_SIZE_WITH_ALLOC(sz) < target) { - sz <<= 1; - } - return sz; -} - -/** 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>. - */ -STATIC void -buf_pullup(buf_t *buf, size_t bytes) -{ - chunk_t *dest, *src; - size_t capacity; - if (!buf->head) - return; - - check(); - if (buf->datalen < bytes) - bytes = buf->datalen; - - capacity = bytes; - if (buf->head->datalen >= bytes) - return; - - if (buf->head->memlen >= capacity) { - /* We don't need to grow the first chunk, but we might need to repack it.*/ - size_t needed = capacity - buf->head->datalen; - if (CHUNK_REMAINING_CAPACITY(buf->head) < needed) - chunk_repack(buf->head); - tor_assert(CHUNK_REMAINING_CAPACITY(buf->head) >= needed); - } else { - chunk_t *newhead; - size_t newsize; - /* We need to grow the chunk. */ - chunk_repack(buf->head); - newsize = CHUNK_SIZE_WITH_ALLOC(preferred_chunk_size(capacity)); - newhead = chunk_grow(buf->head, newsize); - tor_assert(newhead->memlen >= capacity); - if (newhead != buf->head) { - if (buf->tail == buf->head) - buf->tail = newhead; - buf->head = newhead; - } - } - - dest = buf->head; - while (dest->datalen < bytes) { - size_t n = bytes - dest->datalen; - src = dest->next; - tor_assert(src); - if (n >= src->datalen) { - memcpy(CHUNK_WRITE_PTR(dest), src->data, src->datalen); - dest->datalen += src->datalen; - dest->next = src->next; - if (buf->tail == src) - buf->tail = dest; - buf_chunk_free_unchecked(src); - } else { - memcpy(CHUNK_WRITE_PTR(dest), src->data, n); - dest->datalen += n; - src->data += n; - src->datalen -= n; - tor_assert(dest->datalen == bytes); - } - } - - check(); -} - -#ifdef TOR_UNIT_TESTS -/* Return the data from the first chunk of buf in cp, and its length in sz. */ -void -buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz) -{ - if (!buf || !buf->head) { - *cp = NULL; - *sz = 0; - } else { - *cp = buf->head->data; - *sz = buf->head->datalen; - } -} - -/* Write sz bytes from cp into a newly allocated buffer buf. - * Returns NULL when passed a NULL cp or zero sz. - * Asserts on failure: only for use in unit tests. - * buf must be freed using buf_free(). */ -buf_t * -buf_new_with_data(const char *cp, size_t sz) -{ - /* Validate arguments */ - if (!cp || sz <= 0) { - return NULL; - } - - tor_assert(sz < SSIZE_T_CEILING); - - /* Allocate a buffer */ - buf_t *buf = buf_new_with_capacity(sz); - tor_assert(buf); - assert_buf_ok(buf); - tor_assert(!buf->head); - - /* Allocate a chunk that is sz bytes long */ - buf->head = chunk_new_with_alloc_size(CHUNK_ALLOC_SIZE(sz)); - buf->tail = buf->head; - tor_assert(buf->head); - assert_buf_ok(buf); - tor_assert(buf_allocation(buf) >= sz); - - /* Copy the data and size the buffers */ - tor_assert(sz <= buf_slack(buf)); - tor_assert(sz <= CHUNK_REMAINING_CAPACITY(buf->head)); - memcpy(&buf->head->mem[0], cp, sz); - buf->datalen = sz; - buf->head->datalen = sz; - buf->head->data = &buf->head->mem[0]; - assert_buf_ok(buf); - - /* Make sure everything is large enough */ - tor_assert(buf_allocation(buf) >= sz); - tor_assert(buf_allocation(buf) >= buf_datalen(buf) + buf_slack(buf)); - /* Does the buffer implementation allocate more than the requested size? - * (for example, by rounding up). If so, these checks will fail. */ - tor_assert(buf_datalen(buf) == sz); - tor_assert(buf_slack(buf) == 0); - - return buf; -} -#endif - -/** Remove the first <b>n</b> bytes from buf. */ -static inline void -buf_remove_from_front(buf_t *buf, size_t n) -{ - tor_assert(buf->datalen >= n); - while (n) { - tor_assert(buf->head); - if (buf->head->datalen > n) { - buf->head->datalen -= n; - buf->head->data += n; - buf->datalen -= n; - return; - } else { - chunk_t *victim = buf->head; - n -= victim->datalen; - buf->datalen -= victim->datalen; - buf->head = victim->next; - if (buf->tail == victim) - buf->tail = NULL; - buf_chunk_free_unchecked(victim); - } - } - check(); -} - -/** Create and return a new buf with default chunk capacity <b>size</b>. - */ -buf_t * -buf_new_with_capacity(size_t size) -{ - buf_t *b = buf_new(); - b->default_chunk_size = preferred_chunk_size(size); - return b; -} - -/** Allocate and return a new buffer with default capacity. */ -buf_t * -buf_new(void) -{ - buf_t *buf = tor_malloc_zero(sizeof(buf_t)); - buf->magic = BUFFER_MAGIC; - buf->default_chunk_size = 4096; - 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) -{ - chunk_t *chunk, *next; - buf->datalen = 0; - for (chunk = buf->head; chunk; chunk = next) { - next = chunk->next; - buf_chunk_free_unchecked(chunk); - } - buf->head = buf->tail = NULL; -} - -/** Return the number of bytes stored in <b>buf</b> */ -MOCK_IMPL(size_t, -buf_datalen, (const buf_t *buf)) -{ - return buf->datalen; -} - -/** Return the total length of all chunks used in <b>buf</b>. */ -size_t -buf_allocation(const buf_t *buf) -{ - size_t total = 0; - const chunk_t *chunk; - for (chunk = buf->head; chunk; chunk = chunk->next) { - total += CHUNK_ALLOC_SIZE(chunk->memlen); - } - return total; -} - -/** Return the number of bytes that can be added to <b>buf</b> without - * performing any additional allocation. */ -size_t -buf_slack(const buf_t *buf) -{ - if (!buf->tail) - return 0; - else - return CHUNK_REMAINING_CAPACITY(buf->tail); -} - -/** Release storage held by <b>buf</b>. */ -void -buf_free(buf_t *buf) -{ - if (!buf) - return; - - buf_clear(buf); - buf->magic = 0xdeadbeef; - tor_free(buf); -} - -/** Return a new copy of <b>in_chunk</b> */ -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; - newch->data = newch->mem + offset; - } - return newch; -} - -/** Return a new copy of <b>buf</b> */ -buf_t * -buf_copy(const buf_t *buf) -{ - chunk_t *ch; - buf_t *out = buf_new(); - out->default_chunk_size = buf->default_chunk_size; - for (ch = buf->head; ch; ch = ch->next) { - chunk_t *newch = chunk_copy(ch); - if (out->tail) { - out->tail->next = newch; - out->tail = newch; - } else { - out->head = out->tail = newch; - } - } - out->datalen = buf->datalen; - return out; -} - -/** Append a new chunk with enough capacity to hold <b>capacity</b> bytes to - * the tail of <b>buf</b>. If <b>capped</b>, don't allocate a chunk bigger - * than MAX_CHUNK_ALLOC. */ -static chunk_t * -buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) -{ - chunk_t *chunk; - - 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) { - chunk = chunk_new_with_alloc_size(MAX_CHUNK_ALLOC); - } else { - chunk = chunk_new_with_alloc_size(preferred_chunk_size(capacity)); - } - - chunk->inserted_time = (uint32_t)monotime_coarse_absolute_msec(); - - if (buf->tail) { - tor_assert(buf->head); - buf->tail->next = chunk; - buf->tail = chunk; - } else { - tor_assert(!buf->head); - buf->head = buf->tail = chunk; - } - check(); - return chunk; -} - -/** Return the age of the oldest chunk in the buffer <b>buf</b>, in - * milliseconds. Requires the current monotonic time, in truncated msec, - * 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, - * and the number of bytes read otherwise. */ -static inline int -read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, - int *reached_eof, int *socket_error) -{ - ssize_t read_result; - if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) - at_most = CHUNK_REMAINING_CAPACITY(chunk); - read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); - - if (read_result < 0) { - int e = tor_socket_errno(fd); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?"); -#endif - *socket_error = e; - return -1; - } - return 0; /* would block. */ - } else if (read_result == 0) { - log_debug(LD_NET,"Encountered eof on fd %d", (int)fd); - *reached_eof = 1; - return 0; - } else { /* actually got bytes. */ - buf->datalen += read_result; - chunk->datalen += read_result; - log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, - (int)buf->datalen); - tor_assert(read_result < INT_MAX); - return (int)read_result; - } -} - -/** As read_to_chunk(), but return (negative) error code on error, blocking, - * or TLS, and the number of bytes read otherwise. */ -static inline int -read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, - size_t at_most) -{ - int read_result; - - tor_assert(CHUNK_REMAINING_CAPACITY(chunk) >= at_most); - read_result = tor_tls_read(tls, CHUNK_WRITE_PTR(chunk), at_most); - if (read_result < 0) - return read_result; - buf->datalen += read_result; - chunk->datalen += read_result; - return read_result; -} - -/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most - * <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0 - * (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on - * error; else return the number of bytes read. - */ -/* XXXX indicate "read blocked" somehow? */ -int -read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, - int *socket_error) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes read" are not mutually exclusive. - */ - int r = 0; - size_t total_read = 0; - - check(); - tor_assert(reached_eof); - tor_assert(SOCKET_OK(s)); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - at_most)) - return -1; - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk(buf, chunk, s, readlen, reached_eof, socket_error); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) { /* eof, block, or no more to read. */ - break; - } - } - return (int)total_read; -} - -/** As read_to_buf, but reads from a TLS connection, and returns a TLS - * status value rather than the number of bytes read. - * - * Using TLS on OR connections complicates matters in two ways. - * - * First, a TLS stream has its own read buffer independent of the - * connection's read buffer. (TLS needs to read an entire frame from - * the network before it can decrypt any data. Thus, trying to read 1 - * byte from TLS can require that several KB be read from the network - * and decrypted. The extra data is stored in TLS's decrypt buffer.) - * Because the data hasn't been read by Tor (it's still inside the TLS), - * this means that sometimes a connection "has stuff to read" even when - * poll() didn't return POLLIN. The tor_tls_get_pending_bytes function is - * used in connection.c to detect TLS objects with non-empty internal - * buffers and read from them again. - * - * Second, the TLS stream's events do not correspond directly to network - * events: sometimes, before a TLS stream can read, the network must be - * ready to write -- or vice versa. - */ -int -read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf) -{ - int r = 0; - size_t total_read = 0; - - check_no_tls_errors(); - - check(); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - at_most)) - return -1; - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk_tls(buf, chunk, tls, readlen); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) /* eof, block, or no more to read. */ - break; - } - return (int)total_read; -} - -/** Helper for flush_buf(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct - * the bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, 0 on blocking, -1 on failure. - */ -static inline int -flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, - size_t *buf_flushlen) -{ - ssize_t write_result; - - if (sz > chunk->datalen) - sz = chunk->datalen; - write_result = tor_socket_send(s, chunk->data, sz, 0); - - if (write_result < 0) { - int e = tor_socket_errno(s); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"write() failed: WSAENOBUFS. Not enough ram?"); -#endif - return -1; - } - log_debug(LD_NET,"write() would block, returning."); - return 0; - } else { - *buf_flushlen -= write_result; - buf_remove_from_front(buf, write_result); - tor_assert(write_result < INT_MAX); - return (int)write_result; - } -} - -/** Helper for flush_buf_tls(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. (Tries to write - * more if there is a forced pending write size.) On success, deduct the - * bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, and a TOR_TLS error code on failure or blocking. - */ -static inline int -flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, - size_t sz, size_t *buf_flushlen) -{ - int r; - size_t forced; - char *data; - - forced = tor_tls_get_forced_write_size(tls); - if (forced > sz) - sz = forced; - if (chunk) { - data = chunk->data; - tor_assert(sz <= chunk->datalen); - } else { - data = NULL; - tor_assert(sz == 0); - } - r = tor_tls_write(tls, data, sz); - if (r < 0) - return r; - if (*buf_flushlen > (size_t)r) - *buf_flushlen -= r; - else - *buf_flushlen = 0; - buf_remove_from_front(buf, r); - log_debug(LD_NET,"flushed %d bytes, %d ready to flush, %d remain.", - r,(int)*buf_flushlen,(int)buf->datalen); - return r; -} - -/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most - * <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by - * the number of bytes actually written, and remove the written bytes - * from the buffer. Return the number of bytes written on success, - * -1 on failure. Return 0 if write() would block. - */ -int -flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes flushed" are not mutually exclusive. - */ - int r; - size_t flushed = 0; - tor_assert(buf_flushlen); - tor_assert(SOCKET_OK(s)); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(sz <= *buf_flushlen); - - check(); - while (sz) { - size_t flushlen0; - tor_assert(buf->head); - if (buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - - r = flush_chunk(s, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0 || (size_t)r < flushlen0) /* can't flush any more now. */ - break; - } - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** As flush_buf(), but writes data to a TLS connection. Can write more than - * <b>flushlen</b> bytes. - */ -int -flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t flushlen, - size_t *buf_flushlen) -{ - int r; - size_t flushed = 0; - ssize_t sz; - tor_assert(buf_flushlen); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(flushlen <= *buf_flushlen); - sz = (ssize_t) flushlen; - - /* we want to let tls write even if flushlen is zero, because it might - * have a partial record pending */ - check_no_tls_errors(); - - check(); - do { - size_t flushlen0; - if (buf->head) { - if ((ssize_t)buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - } else { - flushlen0 = 0; - } - - r = flush_chunk_tls(tls, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0) /* Can't flush any more now. */ - break; - } while (sz > 0); - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** Append <b>string_len</b> bytes from <b>string</b> to the end of - * <b>buf</b>. - * - * Return the new length of the buffer on success, -1 on failure. - */ -int -write_to_buf(const char *string, size_t string_len, buf_t *buf) -{ - if (!string_len) - return (int)buf->datalen; - check(); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - string_len)) - return -1; - - while (string_len) { - size_t copy; - if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail)) - buf_add_chunk_with_capacity(buf, string_len, 1); - - copy = CHUNK_REMAINING_CAPACITY(buf->tail); - if (copy > string_len) - copy = string_len; - memcpy(CHUNK_WRITE_PTR(buf->tail), string, copy); - string_len -= copy; - string += copy; - buf->datalen += copy; - buf->tail->datalen += copy; - } - - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> - * onto <b>string</b>. - */ -static inline void -peek_from_buf(char *string, size_t string_len, const buf_t *buf) -{ - chunk_t *chunk; - - tor_assert(string); - /* make sure we don't ask for too much */ - tor_assert(string_len <= buf->datalen); - /* assert_buf_ok(buf); */ - - chunk = buf->head; - while (string_len) { - size_t copy = string_len; - tor_assert(chunk); - if (chunk->datalen < copy) - copy = chunk->datalen; - memcpy(string, chunk->data, copy); - string_len -= copy; - string += copy; - chunk = chunk->next; - } -} - -/** Remove <b>string_len</b> bytes from the front of <b>buf</b>, and store - * them into <b>string</b>. Return the new buffer size. <b>string_len</b> - * must be \<= the number of bytes on the buffer. - */ -int -fetch_from_buf(char *string, size_t string_len, buf_t *buf) -{ - /* There must be string_len bytes in buf; write them onto string, - * then memmove buf back (that is, remove them from buf). - * - * Return the number of bytes still on the buffer. */ - - check(); - peek_from_buf(string, string_len, buf); - buf_remove_from_front(buf, string_len); - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** True iff the cell command <b>command</b> is one that implies a - * variable-length cell in Tor link protocol <b>linkproto</b>. */ -static inline int -cell_command_is_var_length(uint8_t command, int linkproto) -{ - /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells - * work as implemented here. If it's 1, there are no variable-length cells. - * Tor does not support other versions right now, and so can't negotiate - * them. - */ - switch (linkproto) { - case 1: - /* Link protocol version 1 has no variable-length cells. */ - return 0; - case 2: - /* In link protocol version 2, VERSIONS is the only variable-length cell */ - return command == CELL_VERSIONS; - case 0: - case 3: - default: - /* In link protocol version 3 and later, and in version "unknown", - * commands 128 and higher indicate variable-length. VERSIONS is - * grandfathered in. */ - return command == CELL_VERSIONS || command >= 128; - } -} - -/** Check <b>buf</b> for a variable-length cell according to the rules of link - * protocol version <b>linkproto</b>. If one is found, pull it off the buffer - * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. - * Return 0 if whatever is on the start of buf_t is not a variable-length - * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start - * of a variable-length cell on <b>buf</b>, but the whole thing isn't there - * yet. */ -int -fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) -{ - char hdr[VAR_CELL_MAX_HEADER_SIZE]; - var_cell_t *result; - uint8_t command; - uint16_t length; - const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; - const int circ_id_len = get_circ_id_size(wide_circ_ids); - const unsigned header_len = get_var_cell_header_size(wide_circ_ids); - check(); - *out = NULL; - if (buf->datalen < header_len) - return 0; - peek_from_buf(hdr, header_len, buf); - - command = get_uint8(hdr + circ_id_len); - if (!(cell_command_is_var_length(command, linkproto))) - return 0; - - length = ntohs(get_uint16(hdr + circ_id_len + 1)); - if (buf->datalen < (size_t)(header_len+length)) - return 1; - result = var_cell_new(length); - result->command = command; - if (wide_circ_ids) - result->circ_id = ntohl(get_uint32(hdr)); - else - result->circ_id = ntohs(get_uint16(hdr)); - - buf_remove_from_front(buf, header_len); - peek_from_buf((char*) result->payload, length, buf); - buf_remove_from_front(buf, length); - check(); - - *out = result; - return 1; -} - -/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to - * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. - * Return the number of bytes actually copied. - */ -int -move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) -{ - /* We can do way better here, but this doesn't turn up in any profiles. */ - char b[4096]; - size_t cp, len; - - if (BUG(buf_out->datalen >= INT_MAX)) - return -1; - if (BUG(buf_out->datalen >= INT_MAX - *buf_flushlen)) - return -1; - - len = *buf_flushlen; - if (len > buf_in->datalen) - len = buf_in->datalen; - - cp = len; /* Remember the number of bytes we intend to copy. */ - tor_assert(cp < INT_MAX); - while (len) { - /* This isn't the most efficient implementation one could imagine, since - * it does two copies instead of 1, but I kinda doubt that this will be - * critical path. */ - size_t n = len > sizeof(b) ? sizeof(b) : len; - fetch_from_buf(b, n, buf_in); - write_to_buf(b, n, buf_out); - len -= n; - } - *buf_flushlen -= cp; - return (int)cp; -} - -/** Internal structure: represents a position in a buffer. */ -typedef struct buf_pos_t { - const chunk_t *chunk; /**< Which chunk are we pointing to? */ - int pos;/**< Which character inside the chunk's data are we pointing to? */ - size_t chunk_pos; /**< Total length of all previous chunks. */ -} buf_pos_t; - -/** Initialize <b>out</b> to point to the first character of <b>buf</b>.*/ -static void -buf_pos_init(const buf_t *buf, buf_pos_t *out) -{ - out->chunk = buf->head; - out->pos = 0; - out->chunk_pos = 0; -} - -/** Advance <b>out</b> to the first appearance of <b>ch</b> at the current - * position of <b>out</b>, or later. Return -1 if no instances are found; - * otherwise returns the absolute position of the character. */ -static off_t -buf_find_pos_of_char(char ch, buf_pos_t *out) -{ - const chunk_t *chunk; - int pos; - tor_assert(out); - if (out->chunk) { - if (out->chunk->datalen) { - tor_assert(out->pos < (off_t)out->chunk->datalen); - } else { - tor_assert(out->pos == 0); - } - } - pos = out->pos; - for (chunk = out->chunk; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data+pos, ch, chunk->datalen - pos); - if (cp) { - out->chunk = chunk; - tor_assert(cp - chunk->data < INT_MAX); - out->pos = (int)(cp - chunk->data); - return out->chunk_pos + out->pos; - } else { - out->chunk_pos += chunk->datalen; - pos = 0; - } - } - return -1; -} - -/** Advance <b>pos</b> by a single character, if there are any more characters - * in the buffer. Returns 0 on success, -1 on failure. */ -static inline int -buf_pos_inc(buf_pos_t *pos) -{ - ++pos->pos; - if (pos->pos == (off_t)pos->chunk->datalen) { - if (!pos->chunk->next) - return -1; - pos->chunk_pos += pos->chunk->datalen; - pos->chunk = pos->chunk->next; - pos->pos = 0; - } - return 0; -} - -/** Return true iff the <b>n</b>-character string in <b>s</b> appears - * (verbatim) at <b>pos</b>. */ -static int -buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) -{ - buf_pos_t p; - if (!n) - return 1; - - memcpy(&p, pos, sizeof(p)); - - while (1) { - char ch = p.chunk->data[p.pos]; - if (ch != *s) - return 0; - ++s; - /* If we're out of characters that don't match, we match. Check this - * _before_ we test incrementing pos, in case we're at the end of the - * string. */ - if (--n == 0) - return 1; - if (buf_pos_inc(&p)<0) - return 0; - } -} - -/** 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. */ -STATIC int -buf_find_string_offset(const buf_t *buf, const char *s, size_t n) -{ - buf_pos_t pos; - buf_pos_init(buf, &pos); - while (buf_find_pos_of_char(*s, &pos) >= 0) { - if (buf_matches_at_pos(&pos, s, n)) { - tor_assert(pos.chunk_pos + pos.pos < INT_MAX); - return (int)(pos.chunk_pos + pos.pos); - } else { - if (buf_pos_inc(&pos)<0) - return -1; - } - } - return -1; -} - -/** - * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at - * <b>headers</b>, looking for a "Content-Length" header. Try to set - * *<b>result_out</b> to the numeric value of that header if possible. - * Return -1 if the header was malformed, 0 if it was missing, and 1 if - * it was present and well-formed. - */ -STATIC int -buf_http_find_content_length(const char *headers, size_t headerlen, - size_t *result_out) -{ - const char *p, *newline; - char *len_str, *eos=NULL; - size_t remaining, result; - int ok; - *result_out = 0; /* The caller shouldn't look at this unless the - * return value is 1, but let's prevent confusion */ - -#define CONTENT_LENGTH "\r\nContent-Length: " - p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); - if (p == NULL) - return 0; - - tor_assert(p >= headers && p < headers+headerlen); - remaining = (headers+headerlen)-p; - p += strlen(CONTENT_LENGTH); - remaining -= strlen(CONTENT_LENGTH); - - newline = memchr(p, '\n', remaining); - if (newline == NULL) - return -1; - - len_str = tor_memdup_nulterm(p, newline-p); - /* We limit the size to INT_MAX because other parts of the buffer.c - * code don't like buffers to be any bigger than that. */ - result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); - if (eos && !tor_strisspace(eos)) { - ok = 0; - } else { - *result_out = result; - } - tor_free(len_str); - - return ok ? 1 : -1; -} - -/** There is a (possibly incomplete) http statement on <b>buf</b>, of the - * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) - * If a) the headers include a Content-Length field and all bytes in - * the body are present, or b) there's no Content-Length field and - * all headers are present, then: - * - * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. - * - memdup body into <b>*body_out</b>, and NUL-terminate it. - * - Then remove them from <b>buf</b>, and return 1. - * - * - If headers or body is NULL, discard that part of the buf. - * - If a headers or body doesn't fit in the arg, return -1. - * (We ensure that the headers or body don't exceed max len, - * _even if_ we're planning to discard them.) - * - If force_complete is true, then succeed even if not all of the - * content has arrived. - * - * Else, change nothing and return 0. - */ -int -fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete) -{ - char *headers; - size_t headerlen, bodylen, contentlen=0; - int crlf_offset; - int r; - - check(); - if (!buf->head) - return 0; - - crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); - if (crlf_offset > (int)max_headerlen || - (crlf_offset < 0 && buf->datalen > max_headerlen)) { - log_debug(LD_HTTP,"headers too long."); - return -1; - } else if (crlf_offset < 0) { - log_debug(LD_HTTP,"headers not all here yet."); - return 0; - } - /* Okay, we have a full header. Make sure it all appears in the first - * chunk. */ - if ((int)buf->head->datalen < crlf_offset + 4) - buf_pullup(buf, crlf_offset+4); - headerlen = crlf_offset + 4; - - headers = buf->head->data; - bodylen = buf->datalen - headerlen; - log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); - - if (max_headerlen <= headerlen) { - log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", - (int)headerlen, (int)max_headerlen-1); - return -1; - } - if (max_bodylen <= bodylen) { - log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", - (int)bodylen, (int)max_bodylen-1); - return -1; - } - - r = buf_http_find_content_length(headers, headerlen, &contentlen); - if (r == -1) { - log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " - "someone is trying to crash us."); - return -1; - } else if (r == 1) { - /* if content-length is malformed, then our body length is 0. fine. */ - log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); - if (bodylen < contentlen) { - if (!force_complete) { - log_debug(LD_HTTP,"body not all here yet."); - return 0; /* not all there yet */ - } - } - if (bodylen > contentlen) { - bodylen = contentlen; - log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); - } - } else { - tor_assert(r == 0); - /* Leave bodylen alone */ - } - - /* all happy. copy into the appropriate places, and return 1 */ - if (headers_out) { - *headers_out = tor_malloc(headerlen+1); - fetch_from_buf(*headers_out, headerlen, buf); - (*headers_out)[headerlen] = 0; /* NUL terminate it */ - } - if (body_out) { - tor_assert(body_used); - *body_used = bodylen; - *body_out = tor_malloc(bodylen+1); - fetch_from_buf(*body_out, bodylen, buf); - (*body_out)[bodylen] = 0; /* NUL terminate it */ - } - check(); - return 1; -} - -/** - * Wait this many seconds before warning the user about using SOCKS unsafely - * again. */ -#define SOCKS_WARN_INTERVAL 5 - -/** Warn that the user application has made an unsafe socks request using - * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than - * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ -static void -log_unsafe_socks_warning(int socks_protocol, const char *address, - uint16_t port, int safe_socks) -{ - static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); - - if (safe_socks) { - log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, - "Your application (using socks%d to port %d) is giving " - "Tor only an IP address. Applications that do DNS resolves " - "themselves may leak information. Consider using Socks4A " - "(e.g. via privoxy or socat) instead. For more information, " - "please see https://wiki.torproject.org/TheOnionRouter/" - "TorFAQ#SOCKSAndDNS.%s", - socks_protocol, - (int)port, - safe_socks ? " Rejecting." : ""); - } - control_event_client_status(LOG_WARN, - "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", - socks_protocol, address, (int)port); -} - -/** Do not attempt to parse socks messages longer than this. This value is - * actually significantly higher than the longest possible socks message. */ -#define MAX_SOCKS_MESSAGE_LEN 512 - -/** Return a new socks_request_t. */ -socks_request_t * -socks_request_new(void) -{ - return tor_malloc_zero(sizeof(socks_request_t)); -} - -/** Free all storage held in the socks_request_t <b>req</b>. */ -void -socks_request_free(socks_request_t *req) -{ - if (!req) - return; - if (req->username) { - memwipe(req->username, 0x10, req->usernamelen); - tor_free(req->username); - } - if (req->password) { - memwipe(req->password, 0x04, req->passwordlen); - tor_free(req->password); - } - memwipe(req, 0xCC, sizeof(socks_request_t)); - tor_free(req); -} - -/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one - * of the forms - * - socks4: "socksheader username\\0" - * - socks4a: "socksheader username\\0 destaddr\\0" - * - socks5 phase one: "version #methods methods" - * - socks5 phase two: "version command 0 addresstype..." - * If it's a complete and valid handshake, and destaddr fits in - * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, - * assign to <b>req</b>, and return 1. - * - * If it's invalid or too big, return -1. - * - * Else it's not all there yet, leave buf alone and return 0. - * - * If you want to specify the socks reply, write it into <b>req->reply</b> - * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. - * - * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether - * the connection is possibly leaking DNS requests locally or not. - * - * If <b>safe_socks</b> is true, then reject unsafe socks protocols. - * - * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are - * undefined. - */ -int -fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks) -{ - int res; - ssize_t n_drain; - size_t want_length = 128; - - if (buf->datalen < 2) /* version and another byte */ - return 0; - - do { - n_drain = 0; - buf_pullup(buf, want_length); - tor_assert(buf->head && buf->head->datalen >= 2); - want_length = 0; - - res = parse_socks(buf->head->data, buf->head->datalen, req, log_sockstype, - safe_socks, &n_drain, &want_length); - - if (n_drain < 0) - buf_clear(buf); - else if (n_drain > 0) - buf_remove_from_front(buf, n_drain); - - } while (res == 0 && buf->head && want_length < buf->datalen && - buf->datalen >= 2); - - return res; -} - -/** 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; -} - -/** 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 - * <b>drain_out</b> to the amount of data that should be removed (or -1 if the - * buffer should be cleared). Instead of pulling more data into the first - * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes - * we'd like to see in the input buffer, if they're available. */ -static int -parse_socks(const char *data, size_t datalen, socks_request_t *req, - int log_sockstype, int safe_socks, ssize_t *drain_out, - size_t *want_length_out) -{ - unsigned int len; - char tmpbuf[TOR_ADDR_BUF_LEN+1]; - tor_addr_t destaddr; - uint32_t destip; - uint8_t socksver; - char *next, *startaddr; - unsigned char usernamelen, passlen; - struct in_addr in; - - if (datalen < 2) { - /* We always need at least 2 bytes. */ - *want_length_out = 2; - return 0; - } - - if (req->socks_version == 5 && !req->got_auth) { - /* See if we have received authentication. Strictly speaking, we should - also check whether we actually negotiated username/password - authentication. But some broken clients will send us authentication - even if we negotiated SOCKS_NO_AUTH. */ - if (*data == 1) { /* username/pass version 1 */ - /* Format is: authversion [1 byte] == 1 - usernamelen [1 byte] - username [usernamelen bytes] - passlen [1 byte] - password [passlen bytes] */ - usernamelen = (unsigned char)*(data + 1); - if (datalen < 2u + usernamelen + 1u) { - *want_length_out = 2u + usernamelen + 1u; - return 0; - } - passlen = (unsigned char)*(data + 2u + usernamelen); - if (datalen < 2u + usernamelen + 1u + passlen) { - *want_length_out = 2u + usernamelen + 1u + passlen; - return 0; - } - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 1; /* authversion == 1 */ - req->reply[1] = 0; /* authentication successful */ - log_debug(LD_APP, - "socks5: Accepted username/password without checking."); - if (usernamelen) { - req->username = tor_memdup(data+2u, usernamelen); - req->usernamelen = usernamelen; - } - if (passlen) { - req->password = tor_memdup(data+3u+usernamelen, passlen); - req->passwordlen = passlen; - } - *drain_out = 2u + usernamelen + 1u + passlen; - req->got_auth = 1; - *want_length_out = 7; /* Minimal socks5 command. */ - return 0; - } else if (req->auth_type == SOCKS_USER_PASS) { - /* unknown version byte */ - log_warn(LD_APP, "Socks5 username/password version %d not recognized; " - "rejecting.", (int)*data); - return -1; - } - } - - socksver = *data; - - switch (socksver) { /* which version of socks? */ - case 5: /* socks5 */ - - if (req->socks_version != 5) { /* we need to negotiate a method */ - unsigned char nummethods = (unsigned char)*(data+1); - int have_user_pass, have_no_auth; - int r=0; - tor_assert(!req->socks_version); - if (datalen < 2u+nummethods) { - *want_length_out = 2u+nummethods; - return 0; - } - if (!nummethods) - return -1; - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; /* socks5 reply */ - have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); - have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); - if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { - req->auth_type = SOCKS_USER_PASS; - req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" - auth method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); - r=0; - } else if (have_no_auth) { - req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth - method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); - r=0; - } else { - log_warn(LD_APP, - "socks5: offered methods don't include 'no auth' or " - "username/password. Rejecting."); - req->reply[1] = '\xFF'; /* reject all methods */ - r=-1; - } - /* Remove packet from buf. Some SOCKS clients will have sent extra - * junk at this point; let's hope it's an authentication message. */ - *drain_out = 2u + nummethods; - - return r; - } - if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { - log_warn(LD_APP, - "socks5: negotiated authentication, but none provided"); - return -1; - } - /* we know the method; read in the request */ - log_debug(LD_APP,"socks5: checking request"); - if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ - *want_length_out = 7; - return 0; /* not yet */ - } - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - 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; - } - switch (*(data+3)) { /* address type */ - case 1: /* IPv4 address */ - case 4: /* IPv6 address */ { - const int is_v6 = *(data+3) == 4; - const unsigned addrlen = is_v6 ? 16 : 4; - log_debug(LD_APP,"socks5: ipv4 address type"); - if (datalen < 6+addrlen) {/* ip/port there? */ - *want_length_out = 6+addrlen; - return 0; /* not yet */ - } - - if (is_v6) - tor_addr_from_ipv6_bytes(&destaddr, data+4); - else - tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); - - 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.", - (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); - return -1; - } - strlcpy(req->address,tmpbuf,sizeof(req->address)); - req->port = ntohs(get_uint16(data+4+addrlen)); - *drain_out = 6+addrlen; - if (req->command != SOCKS_COMMAND_RESOLVE_PTR && - !addressmap_have_mapping(req->address,0)) { - log_unsafe_socks_warning(5, req->address, req->port, safe_socks); - 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; - } - len = (unsigned char)*(data+4); - if (datalen < 7+len) { /* addr/port there? */ - *want_length_out = 7+len; - 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); - return -1; - } - memcpy(req->address,data+5,len); - req->address[len] = 0; - req->port = ntohs(get_uint16(data+5+len)); - *drain_out = 5+len+2; - - 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.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks5 to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "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; - } - tor_assert(0); - break; - case 4: { /* socks4 */ - enum {socks4, socks4a} socks4_prot = socks4a; - const char *authstart, *authend; - /* http://ss5.sourceforge.net/socks4.protocol.txt */ - /* http://ss5.sourceforge.net/socks4A.protocol.txt */ - - req->socks_version = 4; - if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ - *want_length_out = SOCKS4_NETWORK_LEN; - return 0; /* not yet */ - } - // buf_pullup(buf, 1280); - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE) { - /* not a connect or resolve? we don't support it. (No resolve_ptr with - * socks4.) */ - log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", - req->command); - return -1; - } - - req->port = ntohs(get_uint16(data+2)); - destip = ntohl(get_uint32(data+4)); - if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { - log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); - return -1; - } - if (destip >> 8) { - log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); - in.s_addr = htonl(destip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); - if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { - log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", - (int)strlen(tmpbuf)); - return -1; - } - log_debug(LD_APP, - "socks4: successfully read destip (%s)", - safe_str_client(tmpbuf)); - socks4_prot = socks4; - } - - authstart = data + SOCKS4_NETWORK_LEN; - next = memchr(authstart, 0, - datalen-SOCKS4_NETWORK_LEN); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP, "Socks4 user name too long; rejecting."); - return -1; - } - log_debug(LD_APP,"socks4: Username not here yet."); - *want_length_out = datalen+1024; /* More than we need, but safe */ - return 0; - } - authend = next; - tor_assert(next < data+datalen); - - startaddr = NULL; - if (socks4_prot != socks4a && - !addressmap_have_mapping(tmpbuf,0)) { - log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); - - if (safe_socks) - return -1; - } - if (socks4_prot == socks4a) { - if (next+1 == data+datalen) { - log_debug(LD_APP,"socks4: No part of destaddr here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - startaddr = next+1; - next = memchr(startaddr, 0, data + datalen - startaddr); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP,"socks4: Destaddr too long."); - return -1; - } - log_debug(LD_APP,"socks4: Destaddr not all here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { - log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); - return -1; - } - // tor_assert(next < buf->cur+buf->datalen); - - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks4a to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "necessary. This is good.", req->port); - } - log_debug(LD_APP,"socks4: Everything is here. Success."); - strlcpy(req->address, startaddr ? startaddr : tmpbuf, - sizeof(req->address)); - if (!tor_strisprint(req->address) || strchr(req->address,'\"')) { - log_warn(LD_PROTOCOL, - "Your application (using socks4 to port %d) gave Tor " - "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (authend != authstart) { - req->got_auth = 1; - req->usernamelen = authend - authstart; - req->username = tor_memdup(authstart, authend - authstart); - } - /* next points to the final \0 on inbuf */ - *drain_out = next - data + 1; - return 1; - } - case 'G': /* get */ - case 'H': /* head */ - case 'P': /* put/post */ - case 'C': /* connect */ - strlcpy((char*)req->reply, -"HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" -"Content-Type: text/html; charset=iso-8859-1\r\n\r\n" -"<html>\n" -"<head>\n" -"<title>Tor is not an HTTP Proxy</title>\n" -"</head>\n" -"<body>\n" -"<h1>Tor is not an HTTP Proxy</h1>\n" -"<p>\n" -"It appears you have configured your web browser to use Tor as an HTTP proxy." -"\n" -"This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n" -"Please configure your client accordingly.\n" -"</p>\n" -"<p>\n" -"See <a href=\"https://www.torproject.org/documentation.html\">" - "https://www.torproject.org/documentation.html</a> for more " - "information.\n" -"<!-- Plus this comment, to make the body response more than 512 bytes, so " -" IE will be willing to display it. Comment comment comment comment " -" comment comment comment comment comment comment comment comment.-->\n" -"</p>\n" -"</body>\n" -"</html>\n" - , MAX_SOCKS_REPLY_LEN); - req->replylen = strlen((char*)req->reply)+1; - /* fall through */ - default: /* version is not socks4 or socks5 */ - log_warn(LD_APP, - "Socks version %d not recognized. (Tor is not an http proxy.)", - *(data)); - { - /* Tell the controller the first 8 bytes. */ - char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); - control_event_client_status(LOG_WARN, - "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", - escaped(tmp)); - tor_free(tmp); - } - return -1; - } -} - -/** Inspect a reply from SOCKS server stored in <b>buf</b> according - * to <b>state</b>, removing the protocol data upon success. Return 0 on - * incomplete response, 1 on success and -1 on error, in which case - * <b>reason</b> is set to a descriptive message (free() when finished - * with it). - * - * As a special case, 2 is returned when user/pass is required - * during SOCKS5 handshake and user/pass is configured. - */ -int -fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) -{ - ssize_t drain = 0; - int r; - if (buf->datalen < 2) - return 0; - - buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN); - tor_assert(buf->head && buf->head->datalen >= 2); - - r = parse_socks_client((uint8_t*)buf->head->data, buf->head->datalen, - state, reason, &drain); - if (drain > 0) - buf_remove_from_front(buf, drain); - else if (drain < 0) - buf_clear(buf); - - return r; -} - -/** Implementation logic for fetch_from_*_socks_client. */ -static int -parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out) -{ - unsigned int addrlen; - *drain_out = 0; - if (datalen < 2) - return 0; - - switch (state) { - case PROXY_SOCKS4_WANT_CONNECT_OK: - /* Wait for the complete response */ - if (datalen < 8) - return 0; - - if (data[1] != 0x5a) { - *reason = tor_strdup(socks4_response_code_to_string(data[1])); - return -1; - } - - /* Success */ - *drain_out = 8; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: - /* we don't have any credentials */ - if (data[1] != 0x00) { - *reason = tor_strdup("server doesn't support any of our " - "available authentication methods"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: - /* we have a username and password. return 1 if we can proceed without - * providing authentication, or 2 otherwise. */ - switch (data[1]) { - case 0x00: - log_info(LD_NET, "SOCKS 5 client: we have auth details but server " - "doesn't require authentication."); - *drain_out = -1; - return 1; - case 0x02: - log_info(LD_NET, "SOCKS 5 client: need authentication."); - *drain_out = -1; - return 2; - /* fall through */ - } - - *reason = tor_strdup("server doesn't support any of our available " - "authentication methods"); - return -1; - - case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: - /* handle server reply to rfc1929 authentication */ - if (data[1] != 0x00) { - *reason = tor_strdup("authentication failed"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: authentication successful."); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_CONNECT_OK: - /* response is variable length. BND.ADDR, etc, isn't needed - * (don't bother with buf_pullup()), but make sure to eat all - * the data used */ - - /* wait for address type field to arrive */ - if (datalen < 4) - return 0; - - switch (data[3]) { - case 0x01: /* ip4 */ - addrlen = 4; - break; - case 0x04: /* ip6 */ - addrlen = 16; - break; - case 0x03: /* fqdn (can this happen here?) */ - if (datalen < 5) - return 0; - addrlen = 1 + data[4]; - break; - default: - *reason = tor_strdup("invalid response to connect request"); - return -1; - } - - /* wait for address and port */ - if (datalen < 6 + addrlen) - return 0; - - if (data[1] != 0x00) { - *reason = tor_strdup(socks5_response_code_to_string(data[1])); - return -1; - } - - *drain_out = 6 + addrlen; - return 1; - } - - /* shouldn't get here... */ - tor_assert(0); - - return -1; -} - -/** Return 1 iff buf looks more like it has an (obsolete) v0 controller - * command on it than any valid v1 controller command. */ -int -peek_buf_has_control0_command(buf_t *buf) -{ - if (buf->datalen >= 4) { - char header[4]; - uint16_t cmd; - peek_from_buf(header, sizeof(header), buf); - cmd = ntohs(get_uint16(header+2)); - if (cmd <= 0x14) - return 1; /* This is definitely not a v1 control command. */ - } - return 0; -} - -/** Return the index within <b>buf</b> at which <b>ch</b> first appears, - * or -1 if <b>ch</b> does not appear on buf. */ -static off_t -buf_find_offset_of_char(buf_t *buf, char ch) -{ - chunk_t *chunk; - off_t offset = 0; - for (chunk = buf->head; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data, ch, chunk->datalen); - if (cp) - return offset + (cp - chunk->data); - else - offset += chunk->datalen; - } - return -1; -} - -/** Try to read a single LF-terminated line from <b>buf</b>, and write it - * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer - * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the - * line, not counting the terminating NUL. Return 1 if we read a whole line, - * return 0 if we don't have a whole line yet, and return -1 if the line - * length exceeds *<b>data_len</b>. - */ -int -fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) -{ - size_t sz; - off_t offset; - - if (!buf->head) - return 0; - - offset = buf_find_offset_of_char(buf, '\n'); - if (offset < 0) - return 0; - sz = (size_t) offset; - if (sz+2 > *data_len) { - *data_len = sz + 2; - return -1; - } - fetch_from_buf(data_out, sz+1, buf); - data_out[sz+1] = '\0'; - *data_len = sz+1; - return 1; -} - -/** Compress on uncompress the <b>data_len</b> bytes in <b>data</b> using the - * compression state <b>state</b>, appending the result to <b>buf</b>. If - * <b>done</b> is true, flush the data in the state and finish the - * compression/uncompression. Return -1 on failure, 0 on success. */ -int -write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, - const char *data, size_t data_len, - const int done) -{ - char *next; - size_t old_avail, avail; - int over = 0; - - do { - int need_new_chunk = 0; - if (!buf->tail || ! CHUNK_REMAINING_CAPACITY(buf->tail)) { - size_t cap = data_len / 4; - buf_add_chunk_with_capacity(buf, cap, 1); - } - next = CHUNK_WRITE_PTR(buf->tail); - avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail); - switch (tor_compress_process(state, &next, &avail, - &data, &data_len, done)) { - case TOR_COMPRESS_DONE: - over = 1; - break; - case TOR_COMPRESS_ERROR: - return -1; - case TOR_COMPRESS_OK: - if (data_len == 0) { - tor_assert_nonfatal(!done); - over = 1; - } - break; - case TOR_COMPRESS_BUFFER_FULL: - if (avail) { - /* The compression module says we need more room - * (TOR_COMPRESS_BUFFER_FULL). Start a new chunk automatically, - * whether were going to or not. */ - need_new_chunk = 1; - } - if (data_len == 0 && !done) { - /* We've consumed all the input data, though, so there's no - * point in forging ahead right now. */ - over = 1; - } - break; - } - buf->datalen += old_avail - avail; - buf->tail->datalen += old_avail - avail; - if (need_new_chunk) { - buf_add_chunk_with_capacity(buf, data_len/4, 1); - } - - } while (!over); - check(); - return 0; -} - -/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */ -int -buf_set_to_copy(buf_t **output, - const buf_t *input) -{ - if (*output) - buf_free(*output); - *output = buf_copy(input); - return 0; -} - -/** Log an error and exit if <b>buf</b> is corrupted. - */ -void -assert_buf_ok(buf_t *buf) -{ - tor_assert(buf); - tor_assert(buf->magic == BUFFER_MAGIC); - - if (! buf->head) { - tor_assert(!buf->tail); - tor_assert(buf->datalen == 0); - } else { - chunk_t *ch; - size_t total = 0; - tor_assert(buf->tail); - for (ch = buf->head; ch; ch = ch->next) { - total += ch->datalen; - tor_assert(ch->datalen <= ch->memlen); - tor_assert(ch->data >= &ch->mem[0]); - tor_assert(ch->data <= &ch->mem[0]+ch->memlen); - if (ch->data == &ch->mem[0]+ch->memlen) { - static int warned = 0; - if (! warned) { - log_warn(LD_BUG, "Invariant violation in buf.c related to #15083"); - warned = 1; - } - } - tor_assert(ch->data+ch->datalen <= &ch->mem[0] + ch->memlen); - if (!ch->next) - tor_assert(ch == buf->tail); - } - tor_assert(buf->datalen == total); - } -} - diff --git a/src/or/buffers.h b/src/or/buffers.h deleted file mode 100644 index 23b58a571a..0000000000 --- a/src/or/buffers.h +++ /dev/null @@ -1,109 +0,0 @@ -/* Copyright (c) 2001 Matej Pfajfar. - * Copyright (c) 2001-2004, Roger Dingledine. - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file buffers.h - * \brief Header file for buffers.c. - **/ - -#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); - -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); - -int flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen); -int flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t sz, size_t *buf_flushlen); - -int write_to_buf(const char *string, size_t string_len, buf_t *buf); -int write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, - const char *data, size_t data_len, int done); -int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); -int fetch_from_buf(char *string, size_t string_len, buf_t *buf); -int fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto); -int fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete); -socks_request_t *socks_request_new(void); -void socks_request_free(socks_request_t *req); -int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks); -int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); -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); - -int buf_set_to_copy(buf_t **output, - const buf_t *input); - -void assert_buf_ok(buf_t *buf); - -#ifdef BUFFERS_PRIVATE -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); -#ifdef TOR_UNIT_TESTS -void buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz); -buf_t *buf_new_with_data(const char *cp, size_t sz); -#endif -STATIC size_t preferred_chunk_size(size_t target); - -#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 - -#ifdef BUFFERS_PRIVATE -STATIC int buf_http_find_content_length(const char *headers, size_t headerlen, - size_t *result_out); -#endif - -#endif - diff --git a/src/or/channel.c b/src/or/channel.c index 2970b96791..0b5a7fde90 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -701,7 +701,7 @@ channel_remove_from_digest_map(channel_t *chan) return; } -#endif +#endif /* 0 */ /* Pull it out of its list, wherever that list is */ TOR_LIST_REMOVE(chan, next_with_same_id); @@ -1832,7 +1832,7 @@ cell_queue_entry_is_padding(cell_queue_entry_t *q) return 0; } -#endif +#endif /* 0 */ /** * Allocate a new cell queue entry for a fixed-size cell @@ -2088,8 +2088,8 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) * are appropriate to the state transition in question. */ -void -channel_change_state(channel_t *chan, channel_state_t to_state) +static void +channel_change_state_(channel_t *chan, channel_state_t to_state) { channel_state_t from_state; unsigned char was_active, is_active; @@ -2208,18 +2208,8 @@ channel_change_state(channel_t *chan, channel_state_t to_state) 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); - chan->has_been_open = 1; - - /* Check for queued cells to process */ - if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)) - channel_process_cells(chan); - if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)) - channel_flush_cells(chan); - } else if (to_state == CHANNEL_STATE_CLOSED || - to_state == CHANNEL_STATE_ERROR) { + if (to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) { /* Assert that all queues are empty */ tor_assert(TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)); tor_assert(TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)); @@ -2227,6 +2217,35 @@ channel_change_state(channel_t *chan, channel_state_t to_state) } /** + * As channel_change_state_, but change the state to any state but open. + */ +void +channel_change_state(channel_t *chan, channel_state_t to_state) +{ + tor_assert(to_state != CHANNEL_STATE_OPEN); + channel_change_state_(chan, to_state); +} + +/** + * As channel_change_state, but change the state to open. + */ +void +channel_change_state_open(channel_t *chan) +{ + channel_change_state_(chan, CHANNEL_STATE_OPEN); + + /* Tell circuits if we opened and stuff */ + channel_do_open_actions(chan); + chan->has_been_open = 1; + + /* Check for queued cells to process */ + if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)) + channel_process_cells(chan); + if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)) + channel_flush_cells(chan); +} + +/** * Change channel listener state * * This internal and subclass use only function is used to change channel @@ -2584,8 +2603,8 @@ channel_flush_cells(channel_t *chan) * available. */ -int -channel_more_to_flush(channel_t *chan) +MOCK_IMPL(int, +channel_more_to_flush, (channel_t *chan)) { tor_assert(chan); @@ -4071,7 +4090,7 @@ channel_mark_bad_for_new_circs(channel_t *chan) */ int -channel_is_client(channel_t *chan) +channel_is_client(const channel_t *chan) { tor_assert(chan); @@ -4093,6 +4112,20 @@ channel_mark_client(channel_t *chan) } /** + * Clear the client flag + * + * Mark a channel as being _not_ from a client + */ + +void +channel_clear_client(channel_t *chan) +{ + tor_assert(chan); + + chan->is_client = 0; +} + +/** * Get the canonical flag for a channel * * This returns the is_canonical for a channel; this flag is determined by @@ -4822,8 +4855,6 @@ channel_update_xmit_queue_size(channel_t *chan) 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; @@ -4846,8 +4877,6 @@ channel_update_xmit_queue_size(channel_t *chan) 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 ea280f2fd2..32336fe1d2 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -487,7 +487,7 @@ STATIC void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off); void channel_write_cell_generic_(channel_t *chan, const char *cell_type, void *cell, cell_queue_entry_t *q); -#endif +#endif /* defined(CHANNEL_PRIVATE_) */ /* Channel operations for subclasses and internal use only */ @@ -522,6 +522,7 @@ void channel_listener_free(channel_listener_t *chan_l); /* State/metadata setters */ void channel_change_state(channel_t *chan, channel_state_t to_state); +void channel_change_state_open(channel_t *chan); void channel_clear_identity_digest(channel_t *chan); void channel_clear_remote_end(channel_t *chan); void channel_mark_local(channel_t *chan); @@ -567,7 +568,7 @@ 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); +MOCK_DECL(int, channel_more_to_flush, (channel_t *chan)); /* Notify flushed outgoing for dirreq handling */ void channel_notify_flushed(channel_t *chan); @@ -579,7 +580,7 @@ void channel_do_open_actions(channel_t *chan); extern uint64_t estimated_total_queue_size; #endif -#endif +#endif /* defined(TOR_CHANNEL_INTERNAL_) */ /* Helper functions to perform operations on channels */ @@ -665,11 +666,12 @@ int channel_is_bad_for_new_circs(channel_t *chan); void channel_mark_bad_for_new_circs(channel_t *chan); int channel_is_canonical(channel_t *chan); int channel_is_canonical_is_reliable(channel_t *chan); -int channel_is_client(channel_t *chan); +int channel_is_client(const channel_t *chan); int channel_is_local(channel_t *chan); int channel_is_incoming(channel_t *chan); int channel_is_outgoing(channel_t *chan); void channel_mark_client(channel_t *chan); +void channel_clear_client(channel_t *chan); int channel_matches_extend_info(channel_t *chan, extend_info_t *extend_info); int channel_matches_target_addr_for_extend(channel_t *chan, const tor_addr_t *target); @@ -718,5 +720,5 @@ int packed_cell_is_destroy(channel_t *chan, /* Declare the handle helpers */ HANDLE_DECL(channel, channel_s,) -#endif +#endif /* !defined(TOR_CHANNEL_H) */ diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c index ccaf5b4ec8..435436c45c 100644 --- a/src/or/channelpadding.c +++ b/src/or/channelpadding.c @@ -71,7 +71,7 @@ static int consensus_nf_pad_single_onion; * its a client, use that. Then finally verify in the consensus). */ #define CHANNEL_IS_CLIENT(chan, options) \ - (!public_server_mode((options)) || (chan)->is_client || \ + (!public_server_mode((options)) || channel_is_client(chan) || \ !connection_or_digest_is_known_relay((chan)->identity_digest)) /** diff --git a/src/or/channelpadding.h b/src/or/channelpadding.h index a227e27d5b..58bf741d5c 100644 --- a/src/or/channelpadding.h +++ b/src/or/channelpadding.h @@ -41,5 +41,5 @@ int channelpadding_get_circuits_available_timeout(void); unsigned int channelpadding_get_channel_idle_timeout(const channel_t *, int); void channelpadding_new_consensus_params(networkstatus_t *ns); -#endif +#endif /* !defined(TOR_CHANNELPADDING_H) */ diff --git a/src/or/channeltls.c b/src/or/channeltls.c index c6a0bb21e9..8277813186 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -847,7 +847,7 @@ channel_tls_write_packed_cell_method(channel_t *chan, tor_assert(packed_cell); if (tlschan->conn) { - connection_write_to_buf(packed_cell->body, cell_network_size, + connection_buf_add(packed_cell->body, cell_network_size, TO_CONN(tlschan->conn)); /* This is where the cell is finished; used to be done from relay.c */ @@ -993,7 +993,7 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, * We can go to CHANNEL_STATE_OPEN from CHANNEL_STATE_OPENING or * CHANNEL_STATE_MAINT on this. */ - channel_change_state(base_chan, CHANNEL_STATE_OPEN); + channel_change_state_open(base_chan); /* 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); @@ -1044,7 +1044,7 @@ channel_tls_time_process_cell(cell_t *cell, channel_tls_t *chan, int *time, *time += time_passed; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ /** * Handle an incoming cell on a channel_tls_t @@ -1072,9 +1072,9 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn) channel_tls_time_process_cell(cl, cn, & tp ## time , \ channel_tls_process_ ## tp ## _cell); \ } STMT_END -#else +#else /* !(defined(KEEP_TIMING_STATS)) */ #define PROCESS_CELL(tp, cl, cn) channel_tls_process_ ## tp ## _cell(cl, cn) -#endif +#endif /* defined(KEEP_TIMING_STATS) */ tor_assert(cell); tor_assert(conn); @@ -1204,7 +1204,7 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn) /* remember which second it is, for next time */ current_second = now; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ tor_assert(var_cell); tor_assert(conn); @@ -1579,7 +1579,7 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) connection_or_close_normally(chan->conn, 1); return; } -#endif +#endif /* defined(DISABLE_V3_LINKPROTO_SERVERSIDE) */ if (send_versions) { if (connection_or_send_versions(chan->conn, 1) < 0) { @@ -1680,6 +1680,8 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) long apparent_skew = 0; tor_addr_t my_apparent_addr = TOR_ADDR_NULL; + int started_here = 0; + const char *identity_digest = NULL; tor_assert(cell); tor_assert(chan); @@ -1699,10 +1701,12 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) } tor_assert(chan->conn->handshake_state && chan->conn->handshake_state->received_versions); + started_here = connection_or_nonopen_was_started_here(chan->conn); + identity_digest = chan->conn->identity_digest; if (chan->conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { tor_assert(chan->conn->link_proto >= 3); - if (chan->conn->handshake_state->started_here) { + if (started_here) { if (!(chan->conn->handshake_state->authenticated)) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Got a NETINFO cell from server, " @@ -1789,12 +1793,11 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) return; } /* A relay can connect from anywhere and be canonical, so - * long as it tells you from where it came. This may be a bit - * concerning.. Luckily we have another check in - * channel_tls_matches_target_method() to ensure that extends - * only go to the IP they ask for. - * - * XXX: Bleh. That check is not used if the connection is canonical. + * long as it tells you from where it came. This may sound a bit + * concerning... but that's what "canonical" means: that the + * address is one that the relay itself has claimed. The relay + * might be doing something funny, but nobody else is doing a MITM + * on the relay's TCP. */ if (tor_addr_eq(&addr, &(chan->conn->real_addr))) { connection_or_set_canonical(chan->conn, 1); @@ -1813,7 +1816,7 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) "they will not consider this connection canonical. They " "think we are at %s, but we think its %s.", safe_str(descr), - safe_str(hex_str(chan->conn->identity_digest, DIGEST_LEN)), + safe_str(hex_str(identity_digest, DIGEST_LEN)), safe_str(tor_addr_is_null(&my_apparent_addr) ? "<none>" : fmt_and_decorate_addr(&my_apparent_addr)), safe_str(fmt_addr32(me->addr))); @@ -1823,7 +1826,8 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) /** Warn when we get a netinfo skew with at least this value. */ #define NETINFO_NOTICE_SKEW 3600 if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && - connection_or_digest_is_known_relay(chan->conn->identity_digest)) { + (started_here || + connection_or_digest_is_known_relay(chan->conn->identity_digest))) { int trusted = router_digest_is_trusted_dir(chan->conn->identity_digest); clock_skew_warning(TO_CONN(chan->conn), apparent_skew, trusted, LD_GENERAL, "NETINFO cell", "OR"); @@ -1857,8 +1861,7 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) safe_str_client(chan->conn->base_.address), chan->conn->base_.port, (int)(chan->conn->link_proto), - hex_str(TLS_CHAN_TO_BASE(chan)->identity_digest, - DIGEST_LEN), + hex_str(identity_digest, DIGEST_LEN), tor_addr_is_null(&my_apparent_addr) ? "<none>" : fmt_and_decorate_addr(&my_apparent_addr)); } @@ -1915,7 +1918,6 @@ certs_cell_typenum_to_cert_type(int typenum) * of the connection, we then authenticate the server or mark the connection. * If it's the server side, wait for an AUTHENTICATE cell. */ - STATIC void channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) { @@ -1930,7 +1932,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) int n_certs, i; certs_cell_t *cc = NULL; - int send_netinfo = 0; + int send_netinfo = 0, started_here = 0; memset(x509_certs, 0, sizeof(x509_certs)); memset(ed_certs, 0, sizeof(ed_certs)); @@ -1948,6 +1950,11 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) goto err; \ } while (0) + /* Can't use connection_or_nonopen_was_started_here(); its conn->tls + * check looks like it breaks + * test_link_handshake_recv_certs_ok_server(). */ + started_here = chan->conn->handshake_state->started_here; + if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3) ERR("We're not doing a v3 handshake!"); if (chan->conn->link_proto < 3) @@ -2061,7 +2068,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) /* Note that this warns more loudly about time and validity if we were * _trying_ to connect to an authority, not necessarily if we _did_ connect * to one. */ - if (chan->conn->handshake_state->started_here && + if (started_here && router_digest_is_trusted_dir(TLS_CHAN_TO_BASE(chan)->identity_digest)) severity = LOG_WARN; else @@ -2079,7 +2086,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) if (!checked_rsa_id) ERR("Invalid certificate chain!"); - if (chan->conn->handshake_state->started_here) { + if (started_here) { /* No more information is needed. */ chan->conn->handshake_state->authenticated = 1; diff --git a/src/or/channeltls.h b/src/or/channeltls.h index 1f9a39d8a4..d9c4239c3a 100644 --- a/src/or/channeltls.h +++ b/src/or/channeltls.h @@ -26,7 +26,7 @@ struct channel_tls_s { or_connection_t *conn; }; -#endif /* TOR_CHANNEL_INTERNAL_ */ +#endif /* defined(TOR_CHANNEL_INTERNAL_) */ channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port, const char *id_digest, @@ -69,7 +69,7 @@ STATIC void channel_tls_process_auth_challenge_cell(var_cell_t *cell, STATIC void channel_tls_common_init(channel_tls_t *tlschan); STATIC void channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *tlschan); -#endif +#endif /* defined(CHANNELTLS_PRIVATE) */ -#endif +#endif /* !defined(TOR_CHANNELTLS_H) */ diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 4c0bd9e455..f4bd5ea2fa 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -292,7 +292,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) return circ->cpath && circ->cpath->next != circ->cpath && circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; -#else +#else /* !(defined(N2N_TAGGING_IS_POSSIBLE)) */ /* 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. @@ -300,7 +300,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) */ return circ->cpath && circ->cpath->state == CPATH_STATE_AWAITING_KEYS; -#endif +#endif /* defined(N2N_TAGGING_IS_POSSIBLE) */ } /** diff --git a/src/or/circpathbias.h b/src/or/circpathbias.h index 2a4c316807..c9e572d2ae 100644 --- a/src/or/circpathbias.h +++ b/src/or/circpathbias.h @@ -25,5 +25,5 @@ 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 +#endif /* !defined(TOR_CIRCPATHBIAS_H) */ diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 16cef0e56b..b36fed63b3 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -46,6 +46,7 @@ #include "crypto.h" #include "directory.h" #include "entrynodes.h" +#include "hs_ntor.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -70,10 +71,15 @@ static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, static int circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, int relayed); -static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit); +static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit, + int is_hs_v3_rp_circuit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); +static int circuit_send_first_onion_skin(origin_circuit_t *circ); +static int circuit_build_no_more_hops(origin_circuit_t *circ); +static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ, + crypt_path_t *hop); /** This function tries to get a channel to the specified endpoint, * and then calls command_setup_channel() to give it the right @@ -284,14 +290,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } } else { /* ! verbose_names */ - node = node_get_by_id(id); - if (node && node_is_named(node)) { - elt = tor_strdup(node_get_nickname(node)); - } else { - elt = tor_malloc(HEX_DIGEST_LEN+2); - elt[0] = '$'; - base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); - } + elt = tor_malloc(HEX_DIGEST_LEN+2); + elt[0] = '$'; + base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } tor_assert(elt); if (verbose) { @@ -500,10 +501,15 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags) { origin_circuit_t *circ; int err_reason = 0; + int is_hs_v3_rp_circuit = 0; + + if (flags & CIRCLAUNCH_IS_V3_RP) { + is_hs_v3_rp_circuit = 1; + } circ = origin_circuit_init(purpose, flags); - if (onion_pick_cpath_exit(circ, exit_ei) < 0 || + if (onion_pick_cpath_exit(circ, exit_ei, is_hs_v3_rp_circuit) < 0 || onion_populate_cpath(circ) < 0) { circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOPATH); return NULL; @@ -912,234 +918,275 @@ circuit_purpose_may_omit_guard(int purpose) * If circ's first hop is closed, then we need to build a create * cell and send it forward. * - * Otherwise, we need to build a relay extend cell and send it - * forward. + * Otherwise, if circ's cpath still has any non-open hops, we need to + * build a relay extend cell and send it forward to the next non-open hop. + * + * If all hops on the cpath are open, we're done building the circuit + * and we should do housekeeping for the newly opened circuit. * * Return -reason if we want to tear down circ, else return 0. */ int circuit_send_next_onion_skin(origin_circuit_t *circ) { - crypt_path_t *hop; - const node_t *node; - tor_assert(circ); if (circ->cpath->state == CPATH_STATE_CLOSED) { - /* This is the first hop. */ - create_cell_t cc; - int fast; - int len; - log_debug(LD_CIRC,"First skin; sending create cell."); - memset(&cc, 0, sizeof(cc)); - if (circ->build_state->onehop_tunnel) - control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); - else { - control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - - /* If this is not a one-hop tunnel, the channel is being used - * for traffic that wants anonymity and protection from traffic - * analysis (such as netflow record retention). That means we want - * to pad it. - */ - if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) - circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; - } + /* Case one: we're on the first hop. */ + return circuit_send_first_onion_skin(circ); + } - node = node_get_by_id(circ->base_.n_chan->identity_digest); - fast = should_use_create_fast_for_circuit(circ); - if (!fast) { - /* We are an OR and we know the right onion key: we should - * send a create cell. - */ - circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type, - circ->cpath->extend_info); - } else { - /* We are not an OR, and we're building the first hop of a circuit to a - * new OR: we can be speedy and use CREATE_FAST to save an RSA operation - * and a DH operation. */ - cc.cell_type = CELL_CREATE_FAST; - cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; - } + tor_assert(circ->cpath->state == CPATH_STATE_OPEN); + tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); + crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath); - len = onion_skin_create(cc.handshake_type, - circ->cpath->extend_info, - &circ->cpath->handshake_state, - cc.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); - return - END_CIRC_REASON_INTERNAL; - } - cc.handshake_len = len; + if (hop) { + /* Case two: we're on a hop after the first. */ + return circuit_send_intermediate_onion_skin(circ, hop); + } - if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) - return - END_CIRC_REASON_RESOURCELIMIT; + /* Case three: the circuit is finished. Do housekeeping tasks on it. */ + return circuit_build_no_more_hops(circ); +} - circ->cpath->state = CPATH_STATE_AWAITING_KEYS; - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); - log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", - fast ? "CREATE_FAST" : "CREATE", - node ? node_describe(node) : "<unnamed>"); +/** + * Called from circuit_send_next_onion_skin() when we find ourselves connected + * to the first hop in <b>circ</b>: Send a CREATE or CREATE2 or CREATE_FAST + * cell to that hop. Return 0 on success; -reason on failure (if the circuit + * should be torn down). + */ +static int +circuit_send_first_onion_skin(origin_circuit_t *circ) +{ + int fast; + int len; + const node_t *node; + create_cell_t cc; + memset(&cc, 0, sizeof(cc)); + + log_debug(LD_CIRC,"First skin; sending create cell."); + + if (circ->build_state->onehop_tunnel) { + control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); } else { - extend_cell_t ec; - int len; - tor_assert(circ->cpath->state == CPATH_STATE_OPEN); - tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); - log_debug(LD_CIRC,"starting to send subsequent skin."); - hop = onion_next_hop_in_cpath(circ->cpath); - memset(&ec, 0, sizeof(ec)); - if (!hop) { - /* done building the circuit. whew. */ - guard_usable_t r; - if (! circ->guard_state) { - if (circuit_get_cpath_len(circ) != 1 && - ! circuit_purpose_may_omit_guard(circ->base_.purpose) && - get_options()->UseEntryGuards) { - log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " - "guard state", - circuit_get_cpath_len(circ), circ, circ->base_.purpose); - } - r = GUARD_USABLE_NOW; - } else { - r = entry_guard_succeeded(&circ->guard_state); - } - const int is_usable_for_streams = (r == GUARD_USABLE_NOW); - if (r == GUARD_USABLE_NOW) { - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); - } else if (r == GUARD_MAYBE_USABLE_LATER) { - // Wait till either a better guard succeeds, or till - // all better guards fail. - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); - } else { - tor_assert_nonfatal(r == GUARD_USABLE_NEVER); - return - END_CIRC_REASON_INTERNAL; - } + control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - /* XXXX #21422 -- the rest of this branch needs careful thought! - * Some of the things here need to happen when a circuit becomes - * mechanically open; some need to happen when it is actually usable. - * I think I got them right, but more checking would be wise. -NM - */ + /* If this is not a one-hop tunnel, the channel is being used + * for traffic that wants anonymity and protection from traffic + * analysis (such as netflow record retention). That means we want + * to pad it. + */ + if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) + circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; + } - if (circuit_timeout_want_to_count_circ(circ)) { - struct timeval end; - long timediff; - tor_gettimeofday(&end); - timediff = tv_mdiff(&circ->base_.timestamp_began, &end); + node = node_get_by_id(circ->base_.n_chan->identity_digest); + fast = should_use_create_fast_for_circuit(circ); + if (!fast) { + /* We know the right onion key: we should send a create cell. */ + circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type, + circ->cpath->extend_info); + } else { + /* We don't know an onion key, so we need to fall back to CREATE_FAST. */ + cc.cell_type = CELL_CREATE_FAST; + cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; + } - /* - * If the circuit build time is much greater than we would have cut - * it off at, we probably had a suspend event along this codepath, - * and we should discard the value. - */ - 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(get_options())) { - /* Only count circuit times if the network is live */ - 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( - get_circuit_build_times_mutable()); - } - } - } - log_info(LD_CIRC,"circuit built!"); - circuit_reset_failure_count(0); + len = onion_skin_create(cc.handshake_type, + circ->cpath->extend_info, + &circ->cpath->handshake_state, + cc.onionskin); + if (len < 0) { + log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); + return - END_CIRC_REASON_INTERNAL; + } + cc.handshake_len = len; - if (circ->build_state->onehop_tunnel || circ->has_opened) { - control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); - } + if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) + return - END_CIRC_REASON_RESOURCELIMIT; - pathbias_count_build_success(circ); - circuit_rep_hist_note_result(circ); - if (is_usable_for_streams) - circuit_has_opened(circ); /* do other actions as necessary */ - - if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { - const or_options_t *options = get_options(); - 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."); - 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(options)) { - inform_testing_reachability(); - consider_testing_reachability(1, 1); - } + circ->cpath->state = CPATH_STATE_AWAITING_KEYS; + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); + log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", + fast ? "CREATE_FAST" : "CREATE", + node ? node_describe(node) : "<unnamed>"); + return 0; +} + +/** + * Called from circuit_send_next_onion_skin() when we find that we have no + * more hops: mark the circuit as finished, and perform the necessary + * bookkeeping. Return 0 on success; -reason on failure (if the circuit + * should be torn down). + */ +static int +circuit_build_no_more_hops(origin_circuit_t *circ) +{ + guard_usable_t r; + if (! circ->guard_state) { + if (circuit_get_cpath_len(circ) != 1 && + ! circuit_purpose_may_omit_guard(circ->base_.purpose) && + get_options()->UseEntryGuards) { + log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " + "guard state", + circuit_get_cpath_len(circ), circ, circ->base_.purpose); + } + r = GUARD_USABLE_NOW; + } else { + r = entry_guard_succeeded(&circ->guard_state); + } + const int is_usable_for_streams = (r == GUARD_USABLE_NOW); + if (r == GUARD_USABLE_NOW) { + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); + } else if (r == GUARD_MAYBE_USABLE_LATER) { + // Wait till either a better guard succeeds, or till + // all better guards fail. + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); + } else { + tor_assert_nonfatal(r == GUARD_USABLE_NEVER); + return - END_CIRC_REASON_INTERNAL; + } + + /* XXXX #21422 -- the rest of this branch needs careful thought! + * Some of the things here need to happen when a circuit becomes + * mechanically open; some need to happen when it is actually usable. + * I think I got them right, but more checking would be wise. -NM + */ + + if (circuit_timeout_want_to_count_circ(circ)) { + struct timeval end; + long timediff; + tor_gettimeofday(&end); + timediff = tv_mdiff(&circ->base_.timestamp_began, &end); + + /* + * If the circuit build time is much greater than we would have cut + * it off at, we probably had a suspend event along this codepath, + * and we should discard the value. + */ + 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(get_options())) { + /* Only count circuit times if the network is live */ + 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()); } - /* We're done with measurement circuits here. Just close them */ - if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + circuit_build_times_network_circ_success( + get_circuit_build_times_mutable()); } - return 0; } - - if (tor_addr_family(&hop->extend_info->addr) != AF_INET) { - log_warn(LD_BUG, "Trying to extend to a non-IPv4 address."); - return - END_CIRC_REASON_INTERNAL; + } + log_info(LD_CIRC,"circuit built!"); + circuit_reset_failure_count(0); + + if (circ->build_state->onehop_tunnel || circ->has_opened) { + control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); + } + + pathbias_count_build_success(circ); + circuit_rep_hist_note_result(circ); + if (is_usable_for_streams) + circuit_has_opened(circ); /* do other actions as necessary */ + + if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { + const or_options_t *options = get_options(); + 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."); + 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."); } - - circuit_pick_extend_handshake(&ec.cell_type, - &ec.create_cell.cell_type, - &ec.create_cell.handshake_type, - hop->extend_info); - - tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); - ec.orport_ipv4.port = hop->extend_info->port; - tor_addr_make_unspec(&ec.orport_ipv6.addr); - memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); - /* Set the ED25519 identity too -- it will only get included - * in the extend2 cell if we're configured to use it, though. */ - ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity); - - len = onion_skin_create(ec.create_cell.handshake_type, - hop->extend_info, - &hop->handshake_state, - ec.create_cell.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create failed."); - return - END_CIRC_REASON_INTERNAL; + control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); + clear_broken_connection_map(1); + if (server_mode(options) && !check_whether_orport_reachable(options)) { + inform_testing_reachability(); + consider_testing_reachability(1, 1); } - ec.create_cell.handshake_len = len; + } - log_info(LD_CIRC,"Sending extend relay cell."); - { - uint8_t command = 0; - uint16_t payload_len=0; - uint8_t payload[RELAY_PAYLOAD_SIZE]; - if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { - log_warn(LD_CIRC,"Couldn't format extend cell"); - return -END_CIRC_REASON_INTERNAL; - } + /* We're done with measurement circuits here. Just close them */ + if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + } + return 0; +} - /* send it to hop->prev, because it will transfer - * it to a create cell and then send to hop */ - if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), - command, - (char*)payload, payload_len, - hop->prev) < 0) - return 0; /* circuit is closed */ +/** + * Called from circuit_send_next_onion_skin() when we find that we have a hop + * other than the first that we need to extend to: use <b>hop</b>'s + * information to extend the circuit another step. Return 0 on success; + * -reason on failure (if the circuit should be torn down). + */ +static int +circuit_send_intermediate_onion_skin(origin_circuit_t *circ, + crypt_path_t *hop) +{ + int len; + extend_cell_t ec; + memset(&ec, 0, sizeof(ec)); + + log_debug(LD_CIRC,"starting to send subsequent skin."); + + if (tor_addr_family(&hop->extend_info->addr) != AF_INET) { + log_warn(LD_BUG, "Trying to extend to a non-IPv4 address."); + return - END_CIRC_REASON_INTERNAL; + } + + circuit_pick_extend_handshake(&ec.cell_type, + &ec.create_cell.cell_type, + &ec.create_cell.handshake_type, + hop->extend_info); + + tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); + ec.orport_ipv4.port = hop->extend_info->port; + tor_addr_make_unspec(&ec.orport_ipv6.addr); + memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); + /* Set the ED25519 identity too -- it will only get included + * in the extend2 cell if we're configured to use it, though. */ + ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity); + + len = onion_skin_create(ec.create_cell.handshake_type, + hop->extend_info, + &hop->handshake_state, + ec.create_cell.onionskin); + if (len < 0) { + log_warn(LD_CIRC,"onion_skin_create failed."); + return - END_CIRC_REASON_INTERNAL; + } + ec.create_cell.handshake_len = len; + + log_info(LD_CIRC,"Sending extend relay cell."); + { + uint8_t command = 0; + uint16_t payload_len=0; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { + log_warn(LD_CIRC,"Couldn't format extend cell"); + return -END_CIRC_REASON_INTERNAL; } - hop->state = CPATH_STATE_AWAITING_KEYS; + + /* send it to hop->prev, because that relay will transfer + * it to a create cell and then send to hop */ + if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), + command, + (char*)payload, payload_len, + hop->prev) < 0) + return 0; /* circuit is closed */ } + hop->state = CPATH_STATE_AWAITING_KEYS; return 0; } @@ -1325,40 +1372,77 @@ circuit_extend(cell_t *cell, circuit_t *circ) return 0; } -/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in - * key_data. key_data must contain CPATH_KEY_MATERIAL bytes, which are - * used as follows: +/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data. + * + * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden + * service circuits and <b>key_data</b> must be at least + * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length. + * + * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN + * bytes, which are used as follows: * - 20 to initialize f_digest * - 20 to initialize b_digest * - 16 to key f_crypto * - 16 to key b_crypto * * (If 'reverse' is true, then f_XX and b_XX are swapped.) + * + * Return 0 if init was successful, else -1 if it failed. */ int -circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, - int reverse) +circuit_init_cpath_crypto(crypt_path_t *cpath, + const char *key_data, size_t key_data_len, + int reverse, int is_hs_v3) { crypto_digest_t *tmp_digest; crypto_cipher_t *tmp_crypto; + size_t digest_len = 0; + size_t cipher_key_len = 0; tor_assert(cpath); tor_assert(key_data); tor_assert(!(cpath->f_crypto || cpath->b_crypto || cpath->f_digest || cpath->b_digest)); - cpath->f_digest = crypto_digest_new(); - crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN); - cpath->b_digest = crypto_digest_new(); - crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN); + /* Basic key size validation */ + if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) { + return -1; + } else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) { + return -1; + } - if (!(cpath->f_crypto = - crypto_cipher_new(key_data+(2*DIGEST_LEN)))) { + /* If we are using this cpath for next gen onion services use SHA3-256, + otherwise use good ol' SHA1 */ + if (is_hs_v3) { + digest_len = DIGEST256_LEN; + cipher_key_len = CIPHER256_KEY_LEN; + cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256); + cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256); + } else { + digest_len = DIGEST_LEN; + cipher_key_len = CIPHER_KEY_LEN; + cpath->f_digest = crypto_digest_new(); + cpath->b_digest = crypto_digest_new(); + } + + tor_assert(digest_len != 0); + tor_assert(cipher_key_len != 0); + const int cipher_key_bits = (int) cipher_key_len * 8; + + crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len); + crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len); + + cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len), + cipher_key_bits); + if (!cpath->f_crypto) { log_warn(LD_BUG,"Forward cipher initialization failed."); return -1; } - if (!(cpath->b_crypto = - crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) { + + cpath->b_crypto = crypto_cipher_new_with_bits( + key_data+(2*digest_len)+cipher_key_len, + cipher_key_bits); + if (!cpath->b_crypto) { log_warn(LD_BUG,"Backward cipher initialization failed."); return -1; } @@ -1424,7 +1508,7 @@ circuit_finish_handshake(origin_circuit_t *circ, onion_handshake_state_release(&hop->handshake_state); - if (circuit_init_cpath_crypto(hop, keys, 0)<0) { + if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) { return -END_CIRC_REASON_TORPROTOCOL; } @@ -1482,7 +1566,7 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason) log_info(LD_CIRC, "finished"); return 0; -#endif +#endif /* 0 */ } /** Given a response payload and keys, initialize, then send a created @@ -1491,12 +1575,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason) int onionskin_answer(or_circuit_t *circ, const created_cell_t *created_cell, - const char *keys, + const char *keys, size_t keys_len, const uint8_t *rend_circ_nonce) { cell_t cell; crypt_path_t *tmp_cpath; + tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN); + if (created_cell_format(&cell, created_cell) < 0) { log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)", (int)created_cell->cell_type, (int)created_cell->handshake_len); @@ -1512,7 +1598,7 @@ onionskin_answer(or_circuit_t *circ, log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.", (unsigned int)get_uint32(keys), (unsigned int)get_uint32(keys+20)); - if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) { + if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) { log_warn(LD_BUG,"Circuit initialization failed"); tor_free(tmp_cpath); return -1; @@ -1526,12 +1612,12 @@ onionskin_answer(or_circuit_t *circ, memcpy(circ->rend_circ_nonce, rend_circ_nonce, DIGEST_LEN); - circ->is_first_hop = (created_cell->cell_type == CELL_CREATED_FAST); + int used_create_fast = (created_cell->cell_type == CELL_CREATED_FAST); append_cell_to_circuit_queue(TO_CIRCUIT(circ), circ->p_chan, &cell, CELL_DIRECTION_IN, 0); log_debug(LD_CIRC,"Finished sending '%s' cell.", - circ->is_first_hop ? "created_fast" : "created"); + used_create_fast ? "created_fast" : "created"); /* Ignore the local bit when ExtendAllowPrivateAddresses is set: * it violates the assumption that private addresses are local. @@ -1956,9 +2042,10 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) } if (options->ExitNodes) { log_warn(LD_CIRC, - "No specified %sexit routers seem to be running: " + "No exits in ExitNodes%s seem to be running: " "can't choose an exit.", - options->ExcludeExitNodesUnion_ ? "non-excluded " : ""); + options->ExcludeExitNodesUnion_ ? + ", except possibly those excluded by your configuration, " : ""); } return NULL; } @@ -2017,7 +2104,7 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags, return rp_node; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) */ /* Pick a Rendezvous Point for our HS circuits according to <b>flags</b>. */ static const node_t * @@ -2053,7 +2140,7 @@ pick_rendezvous_node(router_crn_flags_t flags) "Unable to find a random rendezvous point that is reachable via " "a direct connection, falling back to a 3-hop path."); } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ return router_choose_random_node(NULL, options->ExcludeNodes, flags); } @@ -2070,7 +2157,8 @@ pick_rendezvous_node(router_crn_flags_t flags) */ static const node_t * choose_good_exit_server(uint8_t purpose, - int need_uptime, int need_capacity, int is_internal) + int need_uptime, int need_capacity, int is_internal, + int need_hs_v3) { const or_options_t *options = get_options(); router_crn_flags_t flags = CRN_NEED_DESC; @@ -2078,6 +2166,8 @@ choose_good_exit_server(uint8_t purpose, flags |= CRN_NEED_UPTIME; if (need_capacity) flags |= CRN_NEED_CAPACITY; + if (need_hs_v3) + flags |= CRN_RENDEZVOUS_V3; switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: @@ -2177,9 +2267,15 @@ warn_if_last_router_excluded(origin_circuit_t *circ, /** Decide a suitable length for circ's cpath, and pick an exit * router (or use <b>exit</b> if provided). Store these in the - * cpath. Return 0 if ok, -1 if circuit should be closed. */ + * cpath. + * + * If <b>is_hs_v3_rp_circuit</b> is set, then this exit should be suitable to + * be used as an HS v3 rendezvous point. + * + * Return 0 if ok, -1 if circuit should be closed. */ static int -onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) +onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei, + int is_hs_v3_rp_circuit) { cpath_build_state_t *state = circ->build_state; @@ -2203,7 +2299,8 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) } else { /* we have to decide one */ const node_t *node = choose_good_exit_server(circ->base_.purpose, state->need_uptime, - state->need_capacity, state->is_internal); + state->need_capacity, state->is_internal, + is_hs_v3_rp_circuit); if (!node) { log_warn(LD_CIRC,"Failed to choose an exit server"); return -1; @@ -2311,6 +2408,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop) } } +#ifdef TOR_UNIT_TESTS + +/** Unittest helper function: Count number of hops in cpath linked list. */ +unsigned int +cpath_get_n_hops(crypt_path_t **head_ptr) +{ + unsigned int n_hops = 0; + crypt_path_t *tmp; + + if (!*head_ptr) { + return 0; + } + + tmp = *head_ptr; + do { + n_hops++; + tmp = tmp->next; + } while (tmp != *head_ptr); + + return n_hops; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + /** A helper function used by onion_extend_cpath(). Use <b>purpose</b> * and <b>state</b> and the cpath <b>head</b> (currently populated only * to length <b>cur_len</b> to decide a suitable middle hop for a @@ -2332,8 +2453,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 %d: random choice.", - cur_len); + log_debug(LD_CIRC, "Contemplating intermediate hop #%d: random choice.", + cur_len+1); excluded = smartlist_new(); if ((r = build_state_get_exit_node(state))) { nodelist_add_node_and_family(excluded, r); @@ -2358,9 +2479,6 @@ choose_good_middle_server(uint8_t purpose, * router (if we're an OR), and respect firewall settings; if we're * configured to use entry guards, return one. * - * If <b>state</b> is NULL, we're choosing a router to serve as an entry - * guard, not for any particular circuit. - * * Set *<b>guard_state_out</b> to information about the guard that * we're selecting, which we'll use later to remember whether the * guard worked or not. @@ -2378,6 +2496,11 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, CRN_DIRECT_CONN); const node_t *node; + /* Once we used this function to select a node to be a guard. We had + * 'state == NULL' be the signal for that. But we don't do that any more. + */ + tor_assert_nonfatal(state); + if (state && options->UseEntryGuards && (purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) { /* This request is for an entry server to use for a regular circuit, @@ -2467,12 +2590,12 @@ onion_extend_cpath(origin_circuit_t *circ) } if (!info) { - log_warn(LD_CIRC,"Failed to find node for hop %d of our path. Discarding " - "this circuit.", cur_len); + log_warn(LD_CIRC,"Failed to find node for hop #%d of our path. Discarding " + "this circuit.", cur_len+1); return -1; } - log_debug(LD_CIRC,"Chose router %s for hop %d (exit is %s)", + log_debug(LD_CIRC,"Chose router %s for hop #%d (exit is %s)", extend_info_describe(info), cur_len+1, build_state_get_exit_nickname(state)); @@ -2580,7 +2703,7 @@ extend_info_from_node(const node_t *node, int for_direct_connect) ed_pubkey = node_get_ed25519_id(node); } else if (node_get_ed25519_id(node)) { log_info(LD_CIRC, "Not including the ed25519 ID for %s, since it won't " - " be able to authenticate it.", + "be able to authenticate it.", node_describe(node)); } diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 45d9b2fb75..b8a651e055 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -31,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ); int circuit_send_next_onion_skin(origin_circuit_t *circ); void circuit_note_clock_jumped(int seconds_elapsed); int circuit_extend(cell_t *cell, circuit_t *circ); -int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, - int reverse); +int circuit_init_cpath_crypto(crypt_path_t *cpath, + const char *key_data, size_t key_data_len, + int reverse, int is_hs_v3); struct created_cell_t; int circuit_finish_handshake(origin_circuit_t *circ, const struct created_cell_t *created_cell); @@ -40,7 +41,7 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason); int onionskin_answer(or_circuit_t *circ, const struct created_cell_t *created_cell, - const char *keys, + const char *keys, size_t keys_len, const uint8_t *rend_circ_nonce); MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now, int *need_uptime, @@ -83,9 +84,11 @@ MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes)); #if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags, const or_options_t *options); -#endif +unsigned int cpath_get_n_hops(crypt_path_t **head_ptr); -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) */ -#endif +#endif /* defined(CIRCUITBUILD_PRIVATE) */ + +#endif /* !defined(TOR_CIRCUITBUILD_H) */ diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 6ffaabc16f..d442887c9e 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -65,8 +65,9 @@ #include "control.h" #include "entrynodes.h" #include "main.h" +#include "hs_circuit.h" #include "hs_circuitmap.h" -#include "hs_common.h" +#include "hs_ident.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -924,14 +925,24 @@ circuit_clear_testing_cell_stats(circuit_t *circ) STATIC void circuit_free(circuit_t *circ) { + circid_t n_circ_id = 0; void *mem; size_t memlen; int should_free = 1; if (!circ) return; + /* We keep a copy of this so we can log its value before it gets unset. */ + n_circ_id = circ->n_circ_id; + circuit_clear_testing_cell_stats(circ); + /* Cleanup circuit from anything HS v3 related. We also do this when the + * circuit is closed. This is to avoid any code path that free registered + * circuits without closing them before. This needs to be done before the + * hs identifier is freed. */ + hs_circ_cleanup(circ); + if (CIRCUIT_IS_ORIGIN(circ)) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); mem = ocirc; @@ -958,6 +969,11 @@ circuit_free(circuit_t *circ) crypto_pk_free(ocirc->intro_key); rend_data_free(ocirc->rend_data); + /* Finally, free the identifier of the circuit and nullify it so multiple + * cleanup will work. */ + hs_ident_circuit_free(ocirc->hs_ident); + ocirc->hs_ident = NULL; + tor_free(ocirc->dest_address); if (ocirc->socks_username) { memwipe(ocirc->socks_username, 0x12, ocirc->socks_username_len); @@ -1015,15 +1031,15 @@ circuit_free(circuit_t *circ) /* Remove from map. */ circuit_set_n_circid_chan(circ, 0, NULL); - /* Clear HS circuitmap token from this circ (if any) */ - if (circ->hs_token) { - hs_circuitmap_remove_circuit(circ); - } - /* Clear cell queue _after_ removing it from the map. Otherwise our * "active" checks will be violated. */ cell_queue_clear(&circ->n_chan_cells); + log_info(LD_CIRC, "Circuit %u (id: %" PRIu32 ") has been freed.", + n_circ_id, + CIRCUIT_IS_ORIGIN(circ) ? + TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0); + if (should_free) { memwipe(mem, 0xAA, memlen); /* poison memory */ tor_free(mem); @@ -1431,7 +1447,7 @@ circuit_unlink_all_from_channel(channel_t *chan, int reason) smartlist_free(detached_2); } } -#endif +#endif /* defined(DEBUG_CIRCUIT_UNLINK_ALL) */ SMARTLIST_FOREACH_BEGIN(detached, circuit_t *, circ) { int mark = 0; @@ -1530,6 +1546,41 @@ circuit_get_next_service_intro_circ(origin_circuit_t *start) return NULL; } +/** Return the first service rendezvous circuit originating from the global + * circuit list after <b>start</b> or at the start of the list if <b>start</b> + * is NULL. Return NULL if no circuit is found. + * + * A service rendezvous point circuit has a purpose of either + * CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does + * not return a circuit marked for close and its state must be open. */ +origin_circuit_t * +circuit_get_next_service_rp_circ(origin_circuit_t *start) +{ + int idx = 0; + smartlist_t *lst = circuit_get_global_list(); + + if (start) { + idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1; + } + + for ( ; idx < smartlist_len(lst); ++idx) { + circuit_t *circ = smartlist_get(lst, idx); + + /* Ignore a marked for close circuit or purpose not matching a service + * intro point or if the state is not open. */ + if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN || + (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND && + circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) { + continue; + } + /* The purposes we are looking for are only for origin circuits so the + * following is valid. */ + return TO_ORIGIN_CIRCUIT(circ); + } + /* Not found. */ + return NULL; +} + /** Return the first circuit originating here in global_circuitlist after * <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if * set) matches the private key digest of the rend data associated with the @@ -1571,6 +1622,30 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, return NULL; } +/** We might cannibalize this circuit: Return true if its last hop can be used + * as a v3 rendezvous point. */ +static int +circuit_can_be_cannibalized_for_v3_rp(const origin_circuit_t *circ) +{ + if (!circ->build_state) { + return 0; + } + + extend_info_t *chosen_exit = circ->build_state->chosen_exit; + if (BUG(!chosen_exit)) { + return 0; + } + + const node_t *rp_node = node_get_by_id(chosen_exit->identity_digest); + if (rp_node) { + if (node_supports_v3_rendezvous_point(rp_node)) { + return 1; + } + } + + return 0; +} + /** Return a circuit that is open, is CIRCUIT_PURPOSE_C_GENERAL, * has a timestamp_dirty value of 0, has flags matching the CIRCLAUNCH_* * flags in <b>flags</b>, and if info is defined, does not already use info @@ -1653,6 +1728,14 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, hop = hop->next; } while (hop != circ->cpath); } + + if ((flags & CIRCLAUNCH_IS_V3_RP) && + !circuit_can_be_cannibalized_for_v3_rp(circ)) { + log_debug(LD_GENERAL, "Skipping uncannibalizable circuit for v3 " + "rendezvous point."); + goto next; + } + if (!best || (best->build_state->need_uptime && !need_uptime)) best = circ; next: ; @@ -1838,10 +1921,20 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, } } + /* Notify the HS subsystem that this circuit is closing. */ + hs_circ_cleanup(circ); + if (circuits_pending_close == NULL) circuits_pending_close = smartlist_new(); smartlist_add(circuits_pending_close, circ); + + log_info(LD_GENERAL, "Circuit %u (id: %" PRIu32 ") marked for close at " + "%s:%d (orig reason: %d, new reason: %d)", + circ->n_circ_id, + CIRCUIT_IS_ORIGIN(circ) ? + TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0, + file, line, orig_reason, reason); } /** Called immediately before freeing a marked circuit <b>circ</b> from @@ -1916,8 +2009,8 @@ circuit_about_to_free(circuit_t *circ) int timed_out = (reason == END_CIRC_REASON_TIMEOUT); tor_assert(circ->state == CIRCUIT_STATE_OPEN); tor_assert(ocirc->build_state->chosen_exit); - tor_assert(ocirc->rend_data); - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + 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(rend_data_get_address(ocirc->rend_data)), @@ -1933,7 +2026,8 @@ circuit_about_to_free(circuit_t *circ) reason != END_CIRC_REASON_TIMEOUT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); if (ocirc->build_state->chosen_exit && ocirc->rend_data) { - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + ocirc->rend_data) { log_info(LD_REND, "Failed intro circ %s to %s " "(building circuit to intro point). " "Marking intro point as possibly unreachable.", @@ -2387,8 +2481,8 @@ assert_cpath_ok(const crypt_path_t *cp) /** Verify that circuit <b>c</b> has all of its invariants * correct. Trigger an assert if anything is invalid. */ -void -assert_circuit_ok(const circuit_t *c) +MOCK_IMPL(void, +assert_circuit_ok,(const circuit_t *c)) { edge_connection_t *conn; const or_circuit_t *or_circ = NULL; diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index d647062f46..4190c1f82e 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -13,6 +13,7 @@ #define TOR_CIRCUITLIST_H #include "testsupport.h" +#include "hs_ident.h" MOCK_DECL(smartlist_t *, circuit_get_global_list, (void)); smartlist_t *circuit_get_global_origin_circuit_list(void); @@ -48,6 +49,8 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, const uint8_t *digest, uint8_t purpose); origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start); +origin_circuit_t *circuit_get_next_service_rp_circ(origin_circuit_t *start); +origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start); origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags); void circuit_mark_all_unused_circs(void); @@ -65,7 +68,7 @@ int circuit_count_pending_on_channel(channel_t *chan); circuit_mark_for_close_((c), (reason), __LINE__, SHORT_FILE__) void assert_cpath_layer_ok(const crypt_path_t *cp); -void assert_circuit_ok(const circuit_t *c); +MOCK_DECL(void, assert_circuit_ok,(const circuit_t *c)); void circuit_free_all(void); void circuits_handle_oom(size_t current_allocation); @@ -83,7 +86,7 @@ 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 /* defined(CIRCUITLIST_PRIVATE) */ -#endif +#endif /* !defined(TOR_CIRCUITLIST_H) */ diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 6c825011b6..2cc0e8fc44 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -185,7 +185,7 @@ struct chanid_circid_muxinfo_t { circuitmux_assert_okay(cmux) #else #define circuitmux_assert_okay_paranoid(cmux) -#endif +#endif /* defined(CMUX_PARANOIA) */ /* * Static function declarations @@ -261,13 +261,11 @@ circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, if (circ->n_mux == cmux) { next_p = &(circ->next_active_on_n_chan); prev_p = &(circ->prev_active_on_n_chan); - direction = CELL_DIRECTION_OUT; } else { or_circ = TO_OR_CIRCUIT(circ); tor_assert(or_circ->p_mux == cmux); next_p = &(or_circ->next_active_on_p_chan); prev_p = &(or_circ->prev_active_on_p_chan); - direction = CELL_DIRECTION_IN; } } tor_assert(next_p); @@ -1646,7 +1644,6 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) circid_t circ_id; circuit_t *circ; or_circuit_t *or_circ; - unsigned int circ_is_active; circuit_t **next_p, **prev_p; unsigned int n_circuits, n_active_circuits, n_cells; @@ -1670,8 +1667,6 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) tor_assert(chan); circ = circuit_get_by_circid_channel_even_if_marked(circ_id, chan); tor_assert(circ); - /* Clear the circ_is_active bit to start */ - circ_is_active = 0; /* Assert that we know which direction this is going */ tor_assert((*i)->muxinfo.direction == CELL_DIRECTION_OUT || @@ -1698,7 +1693,7 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) * Should this circuit be active? I.e., does the mux know about > 0 * cells on it? */ - circ_is_active = ((*i)->muxinfo.cell_count > 0); + const int circ_is_active = ((*i)->muxinfo.cell_count > 0); /* It should be in the linked list iff it's active */ if (circ_is_active) { @@ -1750,7 +1745,6 @@ circuitmux_assert_okay_pass_two(circuitmux_t *cmux) circuit_t **next_p, **prev_p; channel_t *chan; unsigned int n_active_circuits = 0; - cell_direction_t direction; chanid_circid_muxinfo_t search, *hashent = NULL; tor_assert(cmux); @@ -1769,7 +1763,7 @@ circuitmux_assert_okay_pass_two(circuitmux_t *cmux) curr_or_circ = NULL; next_circ = NULL; next_p = prev_p = NULL; - direction = 0; + cell_direction_t direction; /* Figure out if this is n_mux or p_mux */ if (cmux == curr_circ->n_mux) { diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index bf93bb8cbf..a95edb99d8 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -156,5 +156,5 @@ void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, MOCK_DECL(int, circuitmux_compare_muxes, (circuitmux_t *cmux_1, circuitmux_t *cmux_2)); -#endif /* TOR_CIRCUITMUX_H */ +#endif /* !defined(TOR_CIRCUITMUX_H) */ diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index c2440b13f0..fde2d22a89 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.c @@ -731,7 +731,7 @@ add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) smartlist_pqueue_add(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), + offsetof(cell_ewma_t, heap_index), ewma); } @@ -746,7 +746,7 @@ remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) smartlist_pqueue_remove(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), + offsetof(cell_ewma_t, heap_index), ewma); } @@ -760,6 +760,6 @@ pop_first_cell_ewma(ewma_policy_data_t *pol) return smartlist_pqueue_pop(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index)); + offsetof(cell_ewma_t, heap_index)); } diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h index 1f04408789..8f4e57865e 100644 --- a/src/or/circuitmux_ewma.h +++ b/src/or/circuitmux_ewma.h @@ -20,5 +20,5 @@ unsigned int cell_ewma_get_tick(void); void cell_ewma_set_scale_factor(const or_options_t *options, const networkstatus_t *consensus); -#endif /* TOR_CIRCUITMUX_EWMA_H */ +#endif /* !defined(TOR_CIRCUITMUX_EWMA_H) */ diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 51d580a1a4..b8421a3c7e 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -60,7 +60,7 @@ static circuit_build_times_t circ_times; static int unit_tests = 0; #else #define unit_tests 0 -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Return a pointer to the data structure describing our current circuit * build time history and computations. */ @@ -148,7 +148,7 @@ circuit_build_times_disabled_(const or_options_t *options, "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); -#endif +#endif /* 0 */ return 1; } else { #if 0 @@ -157,7 +157,7 @@ circuit_build_times_disabled_(const or_options_t *options, "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); -#endif +#endif /* 0 */ return 0; } } @@ -431,9 +431,14 @@ circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, if (num > 0) { if (num != cbt->liveness.num_recent_circs) { int8_t *recent_circs; - log_notice(LD_CIRC, "The Tor Directory Consensus has changed how many " - "circuits we must track to detect network failures from %d " - "to %d.", cbt->liveness.num_recent_circs, num); + if (cbt->liveness.num_recent_circs > 0) { + log_notice(LD_CIRC, "The Tor Directory Consensus has changed how " + "many circuits we must track to detect network failures " + "from %d to %d.", cbt->liveness.num_recent_circs, num); + } else { + log_notice(LD_CIRC, "Upon receiving a consensus directory, " + "re-enabling circuit-based network failure detection."); + } tor_assert(cbt->liveness.timeouts_after_firsthop || cbt->liveness.num_recent_circs == 0); @@ -608,7 +613,7 @@ circuit_build_times_rewind_history(circuit_build_times_t *cbt, int n) "Rewound history by %d places. Current index: %d. " "Total: %d", n, cbt->build_times_idx, cbt->total_build_times); } -#endif +#endif /* 0 */ /** * Add a new build time value <b>time</b> to the set of build times. Time @@ -676,7 +681,7 @@ circuit_build_times_min(circuit_build_times_t *cbt) } return min_build_time; } -#endif +#endif /* 0 */ /** * Calculate and return a histogram for the set of build times. @@ -910,7 +915,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, int tot_values = 0; uint32_t loaded_cnt = 0, N = 0; config_line_t *line; - unsigned int i; + int i; build_time_t *loaded_times; int err = 0; circuit_build_times_init(cbt); @@ -939,7 +944,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, uint32_t count, k; build_time_t ms; int ok; - ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0, + ms = (build_time_t)tor_parse_ulong(ms_str, 10, 0, CBT_BUILD_TIME_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " @@ -949,7 +954,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, smartlist_free(args); break; } - count = (uint32_t)tor_parse_ulong(count_str, 0, 0, + count = (uint32_t)tor_parse_ulong(count_str, 10, 0, UINT32_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " @@ -960,8 +965,8 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, break; } - if (loaded_cnt+count+state->CircuitBuildAbandonedCount - > state->TotalBuildTimes) { + if (loaded_cnt+count+ (unsigned)state->CircuitBuildAbandonedCount + > (unsigned) state->TotalBuildTimes) { log_warn(LD_CIRC, "Too many build times in state file. " "Stopping short before %d", @@ -986,7 +991,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, loaded_times[loaded_cnt++] = CBT_BUILD_ABANDONED; } - if (loaded_cnt != state->TotalBuildTimes) { + if (loaded_cnt != (unsigned)state->TotalBuildTimes) { log_warn(LD_CIRC, "Corrupt state file? Build times count mismatch. " "Read %d times, but file says %d", loaded_cnt, @@ -1165,7 +1170,7 @@ circuit_build_times_cdf(circuit_build_times_t *cbt, double x) tor_assert(0 <= ret && ret <= 1.0); return ret; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef TOR_UNIT_TESTS /** @@ -1200,7 +1205,7 @@ circuit_build_times_generate_sample(circuit_build_times_t *cbt, tor_assert(ret > 0); return ret; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef TOR_UNIT_TESTS /** @@ -1223,7 +1228,7 @@ circuit_build_times_initial_alpha(circuit_build_times_t *cbt, (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms)); tor_assert(cbt->alpha > 0); } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** * Returns true if we need circuits to be built @@ -1682,7 +1687,7 @@ circuitbuild_running_unit_tests(void) { unit_tests = 1; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ void circuit_build_times_update_last_circ(circuit_build_times_t *cbt) diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h index 8a1dec4bfd..92dc6405ba 100644 --- a/src/or/circuitstats.h +++ b/src/or/circuitstats.h @@ -54,7 +54,7 @@ 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 +#endif /* defined(CIRCUITSTATS_PRIVATE) */ #ifdef TOR_UNIT_TESTS build_time_t circuit_build_times_generate_sample(circuit_build_times_t *cbt, @@ -63,7 +63,7 @@ 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); void circuitbuild_running_unit_tests(void); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /* Network liveness functions */ void circuit_build_times_network_is_live(circuit_build_times_t *cbt); @@ -95,7 +95,7 @@ struct circuit_build_times_s { /** How long we wait before actually closing the circuit. */ double close_ms; }; -#endif +#endif /* defined(CIRCUITSTATS_PRIVATE) */ -#endif +#endif /* !defined(TOR_CIRCUITSTATS_H) */ diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 9f9d3abf7c..7baa035696 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -42,6 +42,9 @@ #include "control.h" #include "entrynodes.h" #include "hs_common.h" +#include "hs_client.h" +#include "hs_circuit.h" +#include "hs_ident.h" #include "nodelist.h" #include "networkstatus.h" #include "policies.h" @@ -55,6 +58,36 @@ static void circuit_expire_old_circuits_clientside(void); static void circuit_increment_failure_count(void); +/** Check whether the hidden service destination of the stream at + * <b>edge_conn</b> is the same as the destination of the circuit at + * <b>origin_circ</b>. */ +static int +circuit_matches_with_rend_stream(const edge_connection_t *edge_conn, + const origin_circuit_t *origin_circ) +{ + /* Check if this is a v2 rendezvous circ/stream */ + if ((edge_conn->rend_data && !origin_circ->rend_data) || + (!edge_conn->rend_data && origin_circ->rend_data) || + (edge_conn->rend_data && origin_circ->rend_data && + rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data), + rend_data_get_address(origin_circ->rend_data)))) { + /* this circ is not for this conn */ + return 0; + } + + /* Check if this is a v3 rendezvous circ/stream */ + if ((edge_conn->hs_ident && !origin_circ->hs_ident) || + (!edge_conn->hs_ident && origin_circ->hs_ident) || + (edge_conn->hs_ident && origin_circ->hs_ident && + !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk, + &origin_circ->hs_ident->identity_pk))) { + /* this circ is not for this conn */ + return 0; + } + + return 1; +} + /** Return 1 if <b>circ</b> could be returned by circuit_get_best(). * Else return 0. */ @@ -169,14 +202,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, /* can't exit from this router */ return 0; } - } else { /* not general */ + } else { /* not general: this might be a rend circuit */ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); - if ((edge_conn->rend_data && !origin_circ->rend_data) || - (!edge_conn->rend_data && origin_circ->rend_data) || - (edge_conn->rend_data && origin_circ->rend_data && - rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data), - rend_data_get_address(origin_circ->rend_data)))) { - /* this circ is not for this conn */ + if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) { return 0; } } @@ -309,7 +337,8 @@ circuit_get_best(const entry_connection_t *conn, /* Log an info message if we're going to launch a new intro circ in * parallel */ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && - !must_be_open && origin_circ->hs_circ_has_timed_out) { + !must_be_open && origin_circ->hs_circ_has_timed_out && + !circ->marked_for_close) { intro_going_on_but_too_old = 1; continue; } @@ -381,7 +410,7 @@ circuit_conforms_to_options(const origin_circuit_t *circ, return 1; } -#endif +#endif /* 0 */ /** Close all circuits that start at us, aren't open, and were born * at least CircuitBuildTimeout seconds ago. @@ -514,8 +543,7 @@ circuit_expire_building(void) cutoff = begindir_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) cutoff = close_cutoff; - else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || - victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) + else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) cutoff = c_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) cutoff = s_intro_cutoff; @@ -605,7 +633,7 @@ circuit_expire_building(void) victim->n_circ_id, (int)(now - victim->timestamp_dirty)); } -#endif +#endif /* 0 */ /* if circ is !open, or if it's open but purpose is a non-finished * intro or rend, then mark it for close */ @@ -622,6 +650,7 @@ circuit_expire_building(void) * because that's set when they switch purposes */ if (TO_ORIGIN_CIRCUIT(victim)->rend_data || + TO_ORIGIN_CIRCUIT(victim)->hs_ident || victim->timestamp_dirty > cutoff.tv_sec) continue; break; @@ -631,12 +660,13 @@ circuit_expire_building(void) TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED; break; case CIRCUIT_PURPOSE_C_INTRODUCING: - /* We keep old introducing circuits around for - * a while in parallel, and they can end up "opened". - * We decide below if we're going to mark them timed - * out and eventually close them. - */ - break; + /* That purpose means that the intro point circuit has been opened + * succesfully but the INTRODUCE1 cell hasn't been sent yet because + * the client is waiting for the rendezvous point circuit to open. + * Keep this circuit open while waiting for the rendezvous circuit. + * We let the circuit idle timeout take care of cleaning this + * circuit if it never used. */ + continue; case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: @@ -727,8 +757,6 @@ circuit_expire_building(void) NULL) break; /* fallthrough! */ - case CIRCUIT_PURPOSE_C_INTRODUCING: - /* connection_ap_handshake_attach_circuit() will relaunch for us */ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: /* If we have reached this line, we want to spare the circ for now. */ @@ -755,7 +783,7 @@ circuit_expire_building(void) victim->state, circuit_state_to_string(victim->state), victim->purpose); TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1; - rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim)); + hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim)); continue; } @@ -973,7 +1001,7 @@ circuit_remove_handled_ports(smartlist_t *needed_ports) tor_assert(*port); if (circuit_stream_is_being_handled(NULL, *port, MIN_CIRCUITS_HANDLING_STREAM)) { -// log_debug(LD_CIRC,"Port %d is already being handled; removing.", port); + log_debug(LD_CIRC,"Port %d is already being handled; removing.", *port); smartlist_del(needed_ports, i--); tor_free(port); } else { @@ -1010,6 +1038,10 @@ circuit_stream_is_being_handled(entry_connection_t *conn, continue; if (origin_circ->unusable_for_new_conns) continue; + if (origin_circ->isolation_values_set && + (conn == NULL || + !connection_edge_compatible_with_circuit(conn, origin_circ))) + continue; exitnode = build_state_get_exit_node(build_state); if (exitnode && (!need_uptime || build_state->need_uptime)) { @@ -1086,11 +1118,32 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity) /* Return true if we need any more hidden service server circuits. * HS servers only need an internal circuit. */ STATIC int -needs_hs_server_circuits(int num_uptime_internal) +needs_hs_server_circuits(time_t now, int num_uptime_internal) { - return (num_rend_services() && - num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS && - router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN); + if (!rend_num_services() && !hs_service_get_num_services()) { + /* No services, we don't need anything. */ + goto no_need; + } + + if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) { + /* We have sufficient amount of internal circuit. */ + goto no_need; + } + + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) { + /* Consensus hasn't been checked or might be invalid so requesting + * internal circuits is not wise. */ + goto no_need; + } + + /* At this point, we need a certain amount of circuits and we will most + * likely use them for rendezvous so we note down the use of internal + * circuit for our prediction for circuit needing uptime and capacity. */ + rep_hist_note_used_internal(now, 1, 1); + + return 1; + no_need: + return 0; } /* We need at least this many internal circuits for hidden service clients */ @@ -1189,7 +1242,7 @@ circuit_predict_and_launch_new(void) return; } - if (needs_hs_server_circuits(num_uptime_internal)) { + if (needs_hs_server_circuits(now, num_uptime_internal)) { flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL); @@ -1253,11 +1306,6 @@ circuit_build_needed_circs(time_t now) if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) connection_ap_rescan_and_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_consider_services_intro_points(); - circuit_expire_old_circs_as_needed(now); if (!options->DisablePredictedCircuits) @@ -1293,7 +1341,7 @@ circuit_expire_old_circs_as_needed(time_t now) log_fn(LOG_INFO,"Creating a new testing circuit."); circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, 0); } -#endif +#endif /* 0 */ } } @@ -1339,8 +1387,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) * number of streams on the circuit associated with the rend service. */ if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ->rend_data); - origin_circ->rend_data->nr_streams--; + hs_dec_rdv_stream_counter(origin_circ); } return; } @@ -1469,7 +1516,7 @@ circuit_expire_old_circuits_clientside(void) #define IDLE_ONE_HOP_CIRC_TIMEOUT 60 /** Find each non-origin circuit that has been unused for too long, - * has no streams on it, used a create_fast, and ends here: mark it + * has no streams on it, came from a client, and ends here: mark it * for close. */ void @@ -1485,9 +1532,9 @@ circuit_expire_old_circuits_serverside(time_t now) /* If the circuit has been idle for too long, and there are no streams * on it, and it ends here, and it used a create_fast, mark it for close. */ - if (or_circ->is_first_hop && !circ->n_chan && + if (or_circ->p_chan && channel_is_client(or_circ->p_chan) && + !circ->n_chan && !or_circ->n_streams && !or_circ->resolving_streams && - or_circ->p_chan && channel_when_last_xmit(or_circ->p_chan) <= cutoff) { log_info(LD_CIRC, "Closing circ_id %u (empty %d secs ago)", (unsigned)or_circ->p_circ_id, @@ -1593,7 +1640,7 @@ circuit_has_opened(origin_circuit_t *circ) switch (TO_CIRCUIT(circ)->purpose) { case CIRCUIT_PURPOSE_C_ESTABLISH_REND: - rend_client_rendcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); /* Start building an intro circ if we don't have one yet. */ connection_ap_attach_pending(1); /* This isn't a call to circuit_try_attaching_streams because a @@ -1605,7 +1652,7 @@ circuit_has_opened(origin_circuit_t *circ) * state. */ break; case CIRCUIT_PURPOSE_C_INTRODUCING: - rend_client_introcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_C_GENERAL: /* Tell any AP connections that have been waiting for a new @@ -1614,11 +1661,11 @@ circuit_has_opened(origin_circuit_t *circ) break; case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: /* at the service, waiting for introductions */ - rend_service_intro_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_S_CONNECT_REND: /* at the service, connecting to rend point */ - rend_service_rendezvous_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_TESTING: circuit_testing_opened(circ); @@ -1707,13 +1754,17 @@ circuit_build_failed(origin_circuit_t *circ) already_marked = 1; } log_info(LD_OR, - "Our circuit failed to get a response from the first hop " - "(%s). I'm going to try to rotate to a better connection.", + "Our circuit %u (id: %" PRIu32 ") failed to get a response " + "from the first hop (%s). I'm going to try to rotate to a " + "better connection.", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier, channel_get_canonical_remote_descr(n_chan)); n_chan->is_bad_for_new_circs = 1; } else { log_info(LD_OR, - "Our circuit died before the first hop with no connection"); + "Our circuit %u (id: %" PRIu32 ") died before the first hop " + "with no connection", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier); } if (n_chan_id && !already_marked) { /* New guard API: we failed. */ @@ -1768,7 +1819,7 @@ circuit_build_failed(origin_circuit_t *circ) "(%s hop failed).", escaped(build_state_get_exit_nickname(circ->build_state)), failed_at_last_hop?"last":"non-last"); - rend_service_relaunch_rendezvous(circ); + hs_circ_retry_service_rendezvous_point(circ); break; /* default: * This won't happen in normal operation, but might happen if the @@ -1855,8 +1906,9 @@ circuit_launch_by_extend_info(uint8_t purpose, uint8_t old_purpose = circ->base_.purpose; struct timeval old_timestamp_began = circ->base_.timestamp_began; - log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d (%s)", - build_state_get_exit_nickname(circ->build_state), purpose, + log_info(LD_CIRC, "Cannibalizing circ %u (id: %" PRIu32 ") for " + "purpose %d (%s)", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier, purpose, circuit_purpose_to_string(purpose)); if ((purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || @@ -2027,8 +2079,12 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (!connection_get_by_type(CONN_TYPE_DIR)) { int severity = LOG_NOTICE; /* Retry some stuff that might help the connection work. */ - if (entry_list_is_constrained(options) && - guards_retry_optimistic(options)) { + /* If we are configured with EntryNodes or UseBridges */ + if (entry_list_is_constrained(options)) { + /* Retry all our guards / bridges. + * guards_retry_optimistic() always returns true here. */ + int rv = guards_retry_optimistic(options); + tor_assert_nonfatal_once(rv); log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " "Optimistically trying known %s again.", @@ -2036,7 +2092,12 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, "used client functionality lately" : "received a consensus with exits", options->UseBridges ? "bridges" : "entrynodes"); - } else if (!options->UseBridges || any_bridge_descriptors_known()) { + } else { + /* Getting directory documents doesn't help much if we have a limited + * number of guards */ + tor_assert_nonfatal(!options->UseBridges); + tor_assert_nonfatal(!options->EntryNodes); + /* Retry our directory fetches, so we have a fresh set of guard info */ log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " "Optimistically trying directory fetches again.", @@ -2076,7 +2137,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } else { /* XXXX Duplicates checks in connection_ap_handshake_attach_circuit: * refactor into a single function. */ - const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (node && !connection_ap_can_use_exit(conn, node)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, @@ -2131,22 +2192,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, /* If this is a hidden service trying to start an introduction point, * handle that case. */ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); /* need to pick an intro point */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; - tor_assert(rend_data); - extend_info = rend_client_get_random_intro(rend_data); + extend_info = hs_client_get_random_intro_from_edge(edge_conn); if (!extend_info) { - log_info(LD_REND, - "No intro points for '%s': re-fetching service descriptor.", - safe_str_client(rend_data_get_address(rend_data))); - rend_client_refetch_v2_renddesc(rend_data); + log_info(LD_REND, "No intro points: re-fetching service descriptor."); + if (edge_conn->rend_data) { + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + } else { + hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + } connection_ap_mark_as_non_pending_circuit(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT; return 0; } log_info(LD_REND,"Chose %s as intro point for '%s'.", extend_info_describe(extend_info), - safe_str_client(rend_data_get_address(rend_data))); + (edge_conn->rend_data) ? + safe_str_client(rend_data_get_address(edge_conn->rend_data)) : + "service"); } /* If we have specified a particular exit node for our @@ -2156,7 +2220,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (conn->chosen_exit_name) { const node_t *r; int opt = conn->chosen_exit_optional; - r = node_get_by_nickname(conn->chosen_exit_name, 1); + r = node_get_by_nickname(conn->chosen_exit_name, 0); if (r && node_has_descriptor(r)) { /* We might want to connect to an IPv6 bridge for loading descriptors so we use the preferred address rather than @@ -2234,7 +2298,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, new_circ_purpose == CIRCUIT_PURPOSE_C_INTRODUCING)) { want_onehop = 1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* Determine what kind of a circuit to launch, and actually launch it. */ { @@ -2242,6 +2306,16 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL; if (need_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; if (need_internal) flags |= CIRCLAUNCH_IS_INTERNAL; + + /* If we are about to pick a v3 RP right now, make sure we pick a + * rendezvous point that supports the v3 protocol! */ + if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED && + new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && + ENTRY_TO_EDGE_CONN(conn)->hs_ident) { + flags |= CIRCLAUNCH_IS_V3_RP; + log_info(LD_GENERAL, "Getting rendezvous circuit to v3 service!"); + } + circ = circuit_launch_by_extend_info(new_circ_purpose, extend_info, flags); } @@ -2265,8 +2339,15 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, /* help predict this next time */ rep_hist_note_used_internal(time(NULL), need_uptime, 1); if (circ) { - /* write the service_id into circ */ - circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + if (edge_conn->rend_data) { + /* write the service_id into circ */ + circ->rend_data = rend_data_dup(edge_conn->rend_data); + } else if (edge_conn->hs_ident) { + circ->hs_ident = + hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk, + HS_IDENT_CIRCUIT_INTRO); + } if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && circ->base_.state == CIRCUIT_STATE_OPEN) circuit_has_opened(circ); @@ -2348,8 +2429,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, /* We are attaching a stream to a rendezvous circuit. That means * that an attempt to connect to a hidden service just * succeeded. Tell rendclient.c. */ - rend_client_note_connection_attempt_ended( - ENTRY_TO_EDGE_CONN(apconn)->rend_data); + hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn)); } if (cpath) { /* we were given one; use it */ @@ -2556,7 +2636,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) * open to that exit. See what exit we meant, and whether we can use it. */ if (conn->chosen_exit_name) { - const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (!node && !want_onehop) { /* We ran into this warning when trying to extend a circuit to a @@ -2626,9 +2706,10 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) tor_assert(rendcirc); /* one is already established, attach */ log_info(LD_REND, - "rend joined circ %u already here. attaching. " - "(stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, conn_age); + "rend joined circ %u (id: %" PRIu32 ") already here. " + "Attaching. (stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); /* Mark rendezvous circuits as 'newly dirty' every time you use * them, since the process of rebuilding a rendezvous circ is so * expensive. There is a tradeoff between linkability and @@ -2661,9 +2742,10 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (rendcirc && (rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)) { log_info(LD_REND, - "pending-join circ %u already here, with intro ack. " - "Stalling. (stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, conn_age); + "pending-join circ %u (id: %" PRIu32 ") already here, with " + "intro ack. Stalling. (stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); return 0; } @@ -2675,10 +2757,13 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (retval > 0) { /* one has already sent the intro. keep waiting. */ tor_assert(introcirc); - log_info(LD_REND, "Intro circ %u present and awaiting ack (rend %u). " - "Stalling. (stream %d sec old)", - (unsigned)introcirc->base_.n_circ_id, - rendcirc ? (unsigned)rendcirc->base_.n_circ_id : 0, + log_info(LD_REND, "Intro circ %u (id: %" PRIu32 ") present and " + "awaiting ACK. Rend circuit %u (id: %" PRIu32 "). " + "Stalling. (stream %d sec old)", + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, + rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0, + rendcirc ? rendcirc->global_identifier : 0, conn_age); return 0; } @@ -2688,19 +2773,26 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (rendcirc && introcirc && rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY) { log_info(LD_REND, - "ready rend circ %u already here (no intro-ack yet on " - "intro %u). (stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, - (unsigned)introcirc->base_.n_circ_id, conn_age); + "ready rend circ %u (id: %" PRIu32 ") already here. No" + "intro-ack yet on intro %u (id: %" PRIu32 "). " + "(stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, conn_age); tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); if (introcirc->base_.state == CIRCUIT_STATE_OPEN) { - log_info(LD_REND,"found open intro circ %u (rend %u); sending " - "introduction. (stream %d sec old)", - (unsigned)introcirc->base_.n_circ_id, - (unsigned)rendcirc->base_.n_circ_id, - conn_age); - switch (rend_client_send_introduction(introcirc, rendcirc)) { + int ret; + log_info(LD_REND, "Found open intro circ %u (id: %" PRIu32 "). " + "Rend circuit %u (id: %" PRIu32 "); Sending " + "introduction. (stream %d sec old)", + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); + ret = hs_client_send_introduce1(introcirc, rendcirc); + switch (ret) { case 0: /* success */ rendcirc->base_.timestamp_dirty = time(NULL); introcirc->base_.timestamp_dirty = time(NULL); @@ -2722,10 +2814,13 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) } } - log_info(LD_REND, "Intro (%u) and rend (%u) circs are not both ready. " - "Stalling conn. (%d sec old)", - introcirc ? (unsigned)introcirc->base_.n_circ_id : 0, - rendcirc ? (unsigned)rendcirc->base_.n_circ_id : 0, conn_age); + log_info(LD_REND, "Intro %u (id: %" PRIu32 ") and rend circuit %u " + "(id: %" PRIu32 ") circuits are not both ready. " + "Stalling conn. (%d sec old)", + introcirc ? (unsigned) TO_CIRCUIT(introcirc)->n_circ_id : 0, + introcirc ? introcirc->global_identifier : 0, + rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0, + rendcirc ? rendcirc->global_identifier : 0, conn_age); return 0; } } diff --git a/src/or/circuituse.h b/src/or/circuituse.h index ad4c214a3b..2b0f983f1a 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -44,6 +44,9 @@ void circuit_build_failed(origin_circuit_t *circ); /** Flag to set when the last hop of a circuit doesn't need to be an * exit node. */ #define CIRCLAUNCH_IS_INTERNAL (1<<3) +/** Flag to set when we are trying to launch a v3 rendezvous circuit. We need + * to apply some additional filters on the node picked. */ +#define CIRCLAUNCH_IS_V3_RP (1<<4) origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose, extend_info_t *info, int flags); @@ -68,7 +71,8 @@ STATIC int circuit_is_available_for_use(const circuit_t *circ); STATIC int needs_exit_circuits(time_t now, int *port_needs_uptime, int *port_needs_capacity); -STATIC int needs_hs_server_circuits(int num_uptime_internal); +STATIC int needs_hs_server_circuits(time_t now, + int num_uptime_internal); STATIC int needs_hs_client_circuits(time_t now, int *needs_uptime, @@ -78,7 +82,7 @@ STATIC int needs_hs_client_circuits(time_t now, STATIC int needs_circuits_for_build(int num); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ -#endif +#endif /* !defined(TOR_CIRCUITUSE_H) */ diff --git a/src/or/command.c b/src/or/command.c index c667cbbe52..ae419ad068 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -128,7 +128,7 @@ command_time_process_cell(cell_t *cell, channel_t *chan, int *time, } *time += time_passed; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ /** Process a <b>cell</b> that was just received on <b>chan</b>. Keep internal * statistics about how many of each cell we've processed so far @@ -165,7 +165,7 @@ command_process_cell(channel_t *chan, cell_t *cell) /* remember which second it is, for next time */ current_second = now; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ #ifdef KEEP_TIMING_STATS #define PROCESS_CELL(tp, cl, cn) STMT_BEGIN { \ @@ -173,9 +173,9 @@ command_process_cell(channel_t *chan, cell_t *cell) command_time_process_cell(cl, cn, & tp ## time , \ command_process_ ## tp ## _cell); \ } STMT_END -#else +#else /* !(defined(KEEP_TIMING_STATS)) */ #define PROCESS_CELL(tp, cl, cn) command_process_ ## tp ## _cell(cl, cn) -#endif +#endif /* defined(KEEP_TIMING_STATS) */ switch (cell->command) { case CELL_CREATE: @@ -331,7 +331,7 @@ command_process_create_cell(cell_t *cell, channel_t *chan) // Needed for chutney: Sometimes relays aren't in the consensus yet, and // get marked as clients. This resets their channels once they appear. // Probably useful for normal operation wrt relay flapping, too. - chan->is_client = 0; + channel_clear_client(chan); } else { channel_mark_client(chan); } @@ -353,16 +353,6 @@ command_process_create_cell(cell_t *cell, channel_t *chan) int len; created_cell_t created_cell; - /* If the client used CREATE_FAST, it's probably a tor client or bridge - * relay, and we must not use it for EXTEND requests (in most cases, we - * won't have an authenticated peer ID for the extend). - * Public relays on 0.2.9 and later will use CREATE_FAST if they have no - * ntor onion key for this relay, but that should be a rare occurrence. - * Clients on 0.3.1 and later avoid using CREATE_FAST as much as they can, - * even during bootstrap, so the CREATE_FAST check is most accurate for - * earlier tor client versions. */ - channel_mark_client(chan); - memset(&created_cell, 0, sizeof(created_cell)); len = onion_skin_server_handshake(ONION_HANDSHAKE_TYPE_FAST, create_cell->onionskin, @@ -381,7 +371,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan) created_cell.handshake_len = len; if (onionskin_answer(circ, &created_cell, - (const char *)keys, rend_circ_nonce)<0) { + (const char *)keys, sizeof(keys), + rend_circ_nonce)<0) { log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return; diff --git a/src/or/command.h b/src/or/command.h index 5079d42e75..c0d1996cbb 100644 --- a/src/or/command.h +++ b/src/or/command.h @@ -27,5 +27,5 @@ extern uint64_t stats_n_created_cells_processed; extern uint64_t stats_n_relay_cells_processed; extern uint64_t stats_n_destroy_cells_processed; -#endif +#endif /* !defined(TOR_COMMAND_H) */ diff --git a/src/or/config.c b/src/or/config.c index 2f92591b19..d8f71a0193 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -91,6 +91,7 @@ #include "relay.h" #include "rendclient.h" #include "rendservice.h" +#include "hs_config.h" #include "rephist.h" #include "router.h" #include "sandbox.h" @@ -113,9 +114,9 @@ * Coverity. Here's a kludge to unconfuse it. */ # define __INCLUDE_LEVEL__ 2 -# endif +#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ #include <systemd/sd-daemon.h> -#endif +#endif /* defined(HAVE_SYSTEMD) */ /* Prefix used to indicate a Unix socket in a FooPort configuration. */ static const char unix_socket_prefix[] = "unix:"; @@ -171,18 +172,26 @@ static config_abbrev_t option_abbrevs_[] = { { NULL, NULL, 0, 0}, }; +/** dummy instance of or_options_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(or_options_t); + /** An entry for config_vars: "The option <b>name</b> has type * CONFIG_TYPE_<b>conftype</b>, and corresponds to * or_options_t.<b>member</b>" */ #define VAR(name,conftype,member,initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_options_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(or_options_t, member), \ + initvalue CONF_TEST_MEMBERS(or_options_t, conftype, member) } /** As VAR, but the option name and member name are the same. */ #define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) /** An entry for config_vars: "The option <b>name</b> is obsolete." */ +#ifdef TOR_UNIT_TESTS +#define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL, {.INT=NULL} } +#else #define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL } +#endif /** * Macro to declare *Port options. Each one comes in three entries. @@ -205,7 +214,7 @@ static config_var_t option_vars_[] = { VAR("AccountingRule", STRING, AccountingRule_option, "max"), V(AccountingStart, STRING, NULL), V(Address, STRING, NULL), - V(AllowDotExit, BOOL, "0"), + OBSOLETE("AllowDotExit"), OBSOLETE("AllowInvalidNodes"), V(AllowNonRFC953Hostnames, BOOL, "0"), OBSOLETE("AllowSingleHopCircuits"), @@ -242,6 +251,7 @@ static config_var_t option_vars_[] = { V(BridgePassword, STRING, NULL), V(BridgeRecordUsageByCountry, BOOL, "1"), V(BridgeRelay, BOOL, "0"), + V(BridgeDistribution, STRING, NULL), V(CellStatistics, BOOL, "0"), V(PaddingStatistics, BOOL, "1"), V(LearnCircuitBuildTimeout, BOOL, "1"), @@ -342,7 +352,7 @@ static config_var_t option_vars_[] = { SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip"), V(GeoIPv6File, FILENAME, SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip6"), -#endif +#endif /* defined(_WIN32) */ OBSOLETE("Group"), V(GuardLifetime, INTERVAL, "0 minutes"), V(HardwareAccel, BOOL, "0"), @@ -371,6 +381,7 @@ static config_var_t option_vars_[] = { V(HTTPProxyAuthenticator, STRING, NULL), V(HTTPSProxy, STRING, NULL), V(HTTPSProxyAuthenticator, STRING, NULL), + VPORT(HTTPTunnelPort), V(IPv6Exit, BOOL, "0"), VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), V(ServerTransportListenAddr, LINELIST, NULL), @@ -408,6 +419,7 @@ static config_var_t option_vars_[] = { OBSOLETE("PredictedPortsRelevanceTime"), OBSOLETE("WarnUnsafeSocks"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), + V(NoExec, BOOL, "0"), V(NumCPUs, UINT, "0"), V(NumDirectoryGuards, UINT, "0"), V(NumEntryGuards, UINT, "0"), @@ -483,9 +495,12 @@ 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"), + OBSOLETE("SchedulerLowWaterMark__"), + OBSOLETE("SchedulerHighWaterMark__"), + OBSOLETE("SchedulerMaxFlushCells__"), + V(KISTSchedRunInterval, MSEC_INTERVAL, "0 msec"), + V(KISTSockBufSizeFactor, DOUBLE, "1.0"), + V(Schedulers, CSV, "KIST,KISTLite,Vanilla"), V(ShutdownWaitLength, INTERVAL, "30 seconds"), OBSOLETE("SocksListenAddress"), V(SocksPolicy, LINELIST, NULL), @@ -584,7 +599,16 @@ static config_var_t option_vars_[] = { * blackholed. Clients will try 3 directories simultaneously. * (Relays never use simultaneous connections.) */ V(ClientBootstrapConsensusMaxInProgressTries, UINT, "3"), - V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "1200, 900, 900, 3600"), + /* When a client has any running bridges, check each bridge occasionally, + * whether or not that bridge is actually up. */ + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, + "10800, 25200, 54000, 111600, 262800"), + /* When a client is just starting, or has no running bridges, check each + * bridge a few times quickly, and then try again later. These schedules + * are much longer than the other schedules, because we try each and every + * configured bridge with this schedule. */ + V(TestingBridgeBootstrapDownloadSchedule, CSV_INTERVAL, + "0, 30, 90, 600, 3600, 10800, 25200, 54000, 111600, 262800"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"), V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"), V(TestingConsensusMaxDownloadTries, UINT, "8"), @@ -604,13 +628,12 @@ static config_var_t option_vars_[] = { V(TestingDirAuthVoteHSDirIsStrict, BOOL, "0"), VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "0"), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; /** Override default values with these if the user sets the TestingTorNetwork * option. */ static const config_var_t testing_tor_network_defaults[] = { - V(ServerDNSAllowBrokenConfig, BOOL, "1"), V(DirAllowPrivateAddresses, BOOL, "1"), V(EnforceDistinctSubnets, BOOL, "0"), V(AssumeReachable, BOOL, "1"), @@ -623,7 +646,7 @@ static const config_var_t testing_tor_network_defaults[] = { "0, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), V(ClientBootstrapConsensusMaxDownloadTries, UINT, "80"), V(ClientBootstrapConsensusAuthorityOnlyMaxDownloadTries, UINT, "80"), - V(ClientDNSRejectInternalAddresses, BOOL,"0"), // deprecated in 0.2.9.2-alpha + V(ClientDNSRejectInternalAddresses, BOOL,"0"), V(ClientRejectInternalAddresses, BOOL, "0"), V(CountPrivateBandwidth, BOOL, "1"), V(ExitPolicyRejectPrivate, BOOL, "0"), @@ -634,7 +657,6 @@ static const config_var_t testing_tor_network_defaults[] = { 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"), @@ -646,7 +668,9 @@ static const config_var_t testing_tor_network_defaults[] = { "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(TestingBridgeDownloadSchedule, CSV_INTERVAL, "10, 30, 60"), + V(TestingBridgeBootstrapDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " + "15, 20, 30, 60"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"), V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"), V(TestingConsensusMaxDownloadTries, UINT, "80"), @@ -659,7 +683,7 @@ static const config_var_t testing_tor_network_defaults[] = { VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "1"), V(RendPostPeriod, INTERVAL, "2 minutes"), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; #undef VAR @@ -667,12 +691,19 @@ static const config_var_t testing_tor_network_defaults[] = { #undef OBSOLETE static const config_deprecation_t option_deprecation_notes_[] = { - /* Deprecated since 0.2.9.2-alpha... */ - { "AllowDotExit", "Unrestricted use of the .exit notation can be used for " - "a wide variety of application-level attacks." }, - { "ClientDNSRejectInternalAddresses", "Turning this on makes your client " - "easier to fingerprint, and may open you to esoteric attacks." }, - /* End of options deprecated since 0.2.9.2-alpha. */ + /* Deprecated since 0.3.2.0-alpha. */ + { "HTTPProxy", "It only applies to direct unencrypted HTTP connections " + "to your directory server, which your Tor probably wasn't using." }, + { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy " + "which should be used with HTTPSProxyAuthenticator." }, + /* End of options deprecated since 0.3.2.1-alpha */ + + /* Options deprecated since 0.3.2.2-alpha */ + { "ReachableDirAddresses", "It has no effect on relays, and has had no " + "effect on clients since 0.2.8." }, + { "ClientPreferIPv6DirPort", "It has no effect on relays, and has had no " + "effect on clients since 0.2.8." }, + /* End of options deprecated since 0.3.2.2-alpha. */ { NULL, NULL } }; @@ -699,7 +730,6 @@ static int parse_ports(or_options_t *options, int validate_only, static int check_server_ports(const smartlist_t *ports, const or_options_t *options, int *num_low_ports_out); - static int validate_data_directory(or_options_t *options); static int write_configuration_file(const char *fname, const or_options_t *options); @@ -725,7 +755,7 @@ static uint64_t compute_real_max_mem_in_queues(const uint64_t val, STATIC config_format_t options_format = { sizeof(or_options_t), OR_OPTIONS_MAGIC, - STRUCT_OFFSET(or_options_t, magic_), + offsetof(or_options_t, magic_), option_abbrevs_, option_deprecation_notes_, option_vars_, @@ -756,6 +786,9 @@ static int have_parsed_cmdline = 0; static char *global_dirfrontpagecontents = NULL; /** List of port_cfg_t for all configured ports. */ static smartlist_t *configured_ports = NULL; +/** True iff we're currently validating options, and any calls to + * get_options() are likely to be bugs. */ +static int in_option_validation = 0; /** Return the contents of our frontpage string, or NULL if not configured. */ MOCK_IMPL(const char*, @@ -769,6 +802,7 @@ MOCK_IMPL(or_options_t *, get_options_mutable, (void)) { tor_assert(global_options); + tor_assert_nonfatal(! in_option_validation); return global_options; } @@ -895,6 +929,10 @@ or_options_free(or_options_t *options) rs, routerset_free(rs)); smartlist_free(options->NodeFamilySets); } + if (options->SchedulerTypes_) { + SMARTLIST_FOREACH(options->SchedulerTypes_, int *, i, tor_free(i)); + smartlist_free(options->SchedulerTypes_); + } tor_free(options->BridgePassword_AuthDigest_); tor_free(options->command_arg); tor_free(options->master_key_fname); @@ -990,6 +1028,23 @@ escaped_safe_str(const char *address) return escaped(address); } +/** + * The severity level that should be used for warnings of severity + * LOG_PROTOCOL_WARN. + * + * We keep this outside the options, in case somebody needs to use + * LOG_PROTOCOL_WARN while an option transition is happening. + */ +static int protocol_warning_severity_level = LOG_WARN; + +/** Return the severity level that should be used for warnings of severity + * LOG_PROTOCOL_WARN. */ +int +get_protocol_warning_severity_level(void) +{ + return protocol_warning_severity_level; +} + /** List of default directory authorities */ static const char *default_authorities[] = { @@ -1228,13 +1283,13 @@ options_act_reversible(const or_options_t *old_options, char **msg) "on this OS/with this build."); goto rollback; } -#else +#else /* !(!defined(HAVE_SYS_UN_H)) */ if (options->ControlSocketsGroupWritable && !options->ControlSocket) { *msg = tor_strdup("Setting ControlSocketGroupWritable without setting" "a ControlSocket makes no sense."); goto rollback; } -#endif +#endif /* !defined(HAVE_SYS_UN_H) */ if (running_tor) { int n_ports=0; @@ -1311,7 +1366,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) goto rollback; } } -#endif +#endif /* defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) */ /* Attempt to lock all current and future memory with mlockall() only once */ if (options->DisableAllSwap) { @@ -1363,7 +1418,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) options->DataDirectory, strerror(errno)); } } -#endif +#endif /* !defined(_WIN32) */ /* Bail out at this point if we're not going to be a client or server: * we don't run Tor itself. */ @@ -1590,6 +1645,10 @@ options_act(const or_options_t *old_options) const int transition_affects_guards = old_options && options_transition_affects_guards(old_options, options); + if (options->NoExec || options->Sandbox) { + tor_disable_spawning_background_processes(); + } + /* disable ptrace and later, other basic debugging techniques */ { /* Remember if we already disabled debugger attachment */ @@ -1624,6 +1683,11 @@ options_act(const or_options_t *old_options) return -1; } + if (options->ProtocolWarnings) + protocol_warning_severity_level = LOG_WARN; + else + protocol_warning_severity_level = LOG_INFO; + if (consider_adding_dir_servers(options, old_options) < 0) return -1; @@ -1641,7 +1705,7 @@ options_act(const or_options_t *old_options) return -1; } /* LCOV_EXCL_STOP */ -#else +#else /* !(defined(ENABLE_TOR2WEB_MODE)) */ if (options->Tor2webMode) { log_err(LD_CONFIG, "This copy of Tor was not compiled to run in " "'tor2web mode'. It cannot be run with the Tor2webMode torrc " @@ -1649,7 +1713,7 @@ options_act(const or_options_t *old_options) "--enable-tor2web-mode option."); return -1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* If we are a bridge with a pluggable transport proxy but no Extended ORPort, inform the user that they are missing out. */ @@ -1678,7 +1742,7 @@ options_act(const or_options_t *old_options) sweep_bridge_list(); } - if (running_tor && rend_config_services(options, 0)<0) { + if (running_tor && hs_config_service_all(options, 0)<0) { log_warn(LD_BUG, "Previously validated hidden services line could not be added!"); return -1; @@ -1761,9 +1825,13 @@ 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 */ + * will log a warning and exit. */ if (options->PidFile && !sandbox_is_active()) { - write_pidfile(options->PidFile); + if (write_pidfile(options->PidFile) < 0) { + log_err(LD_CONFIG, "Unable to write PIDFile %s", + escaped(options->PidFile)); + return -1; + } } /* Register addressmap directives */ @@ -1795,16 +1863,14 @@ options_act(const or_options_t *old_options) monitor_owning_controller_process(options->OwningControllerProcess); /* reload keys as needed for rendezvous services. */ - if (rend_service_load_all_keys(NULL)<0) { + if (hs_service_load_all_keys() < 0) { log_warn(LD_GENERAL,"Error loading rendezvous service keys"); return -1; } - /* Set up scheduler thresholds */ - scheduler_set_watermarks((uint32_t)options->SchedulerLowWaterMark__, - (uint32_t)options->SchedulerHighWaterMark__, - (options->SchedulerMaxFlushCells__ > 0) ? - options->SchedulerMaxFlushCells__ : 1000); + /* Inform the scheduler subsystem that a configuration changed happened. It + * might be a change of scheduler or parameter. */ + scheduler_conf_changed(); /* Set up accounting */ if (accounting_parse_options(options, 0)<0) { @@ -2132,6 +2198,7 @@ static const struct { { "--dump-config", ARGUMENT_OPTIONAL }, { "--list-fingerprint", TAKES_NO_ARGUMENT }, { "--keygen", TAKES_NO_ARGUMENT }, + { "--key-expiration", ARGUMENT_OPTIONAL }, { "--newpass", TAKES_NO_ARGUMENT }, { "--no-passphrase", TAKES_NO_ARGUMENT }, { "--passphrase-fd", ARGUMENT_NECESSARY }, @@ -2292,24 +2359,36 @@ options_trial_assign(config_line_t *list, unsigned flags, char **msg) return r; } - if (options_validate(get_options_mutable(), trial_options, + setopt_err_t rv; + or_options_t *cur_options = get_options_mutable(); + + in_option_validation = 1; + + if (options_validate(cur_options, trial_options, global_default_options, 1, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + rv = SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + goto done; } - if (options_transition_allowed(get_options(), trial_options, msg) < 0) { + if (options_transition_allowed(cur_options, trial_options, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_TRANSITION; + rv = SETOPT_ERR_TRANSITION; + goto done; } + in_option_validation = 0; if (set_options(trial_options, msg)<0) { or_options_free(trial_options); - return SETOPT_ERR_SETTING; + rv = SETOPT_ERR_SETTING; + goto done; } /* we liked it. put it in place. */ - return SETOPT_OK; + rv = SETOPT_OK; + done: + in_option_validation = 0; + return rv; } /** Print a usage message for tor. */ @@ -2801,10 +2880,6 @@ compute_publishserverdescriptor(or_options_t *options) * will generate too many circuits and potentially overload the network. */ #define MIN_CIRCUIT_STREAM_TIMEOUT 10 -/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might - * expose more information than we're comfortable with. */ -#define MIN_HEARTBEAT_PERIOD (30*60) - /** Lowest recommended value for CircuitBuildTimeout; if it is set too low * and LearnCircuitBuildTimeout is off, the failure rate for circuit * construction may be very high. In that case, if it is set below this @@ -2816,8 +2891,11 @@ 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, + in_option_validation = 1; + int rv = options_validate(old_options, options, default_options, from_setconf, msg); + in_option_validation = 0; + return rv; } #define REJECT(arg) \ @@ -2828,15 +2906,17 @@ options_validate_cb(void *old_options, void *options, void *default_options, #else #define COMPLAIN(args, ...) \ STMT_BEGIN log_warn(LD_CONFIG, args, ##__VA_ARGS__); STMT_END -#endif +#endif /* defined(__GNUC__) && __GNUC__ <= 3 */ /** Log a warning message iff <b>filepath</b> is not absolute. * Warning message must contain option name <b>option</b> and * an absolute path that <b>filepath</b> will resolve to. * * In case <b>filepath</b> is absolute, do nothing. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_if_option_path_is_relative(const char *option, char *filepath) { @@ -2845,39 +2925,100 @@ warn_if_option_path_is_relative(const char *option, COMPLAIN("Path for %s (%s) is relative and will resolve to %s." " Is this what you wanted?", option, filepath, abs_path); tor_free(abs_path); + return 1; } + return 0; } /** Scan <b>options</b> for occurances of relative file/directory * path and log a warning whenever it is found. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_about_relative_paths(or_options_t *options) { tor_assert(options); + int n = 0; - warn_if_option_path_is_relative("CookieAuthFile", - options->CookieAuthFile); - warn_if_option_path_is_relative("ExtORPortCookieAuthFile", - options->ExtORPortCookieAuthFile); - warn_if_option_path_is_relative("DirPortFrontPage", - options->DirPortFrontPage); - warn_if_option_path_is_relative("V3BandwidthsFile", - options->V3BandwidthsFile); - warn_if_option_path_is_relative("ControlPortWriteToFile", - options->ControlPortWriteToFile); - warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); - warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); - warn_if_option_path_is_relative("Log",options->DebugLogFile); - warn_if_option_path_is_relative("AccelDir",options->AccelDir); - warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); - warn_if_option_path_is_relative("PidFile",options->PidFile); + n += warn_if_option_path_is_relative("CookieAuthFile", + options->CookieAuthFile); + n += warn_if_option_path_is_relative("ExtORPortCookieAuthFile", + options->ExtORPortCookieAuthFile); + n += warn_if_option_path_is_relative("DirPortFrontPage", + options->DirPortFrontPage); + n += warn_if_option_path_is_relative("V3BandwidthsFile", + options->V3BandwidthsFile); + n += warn_if_option_path_is_relative("ControlPortWriteToFile", + options->ControlPortWriteToFile); + n += warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); + n += warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); + n += warn_if_option_path_is_relative("Log",options->DebugLogFile); + n += warn_if_option_path_is_relative("AccelDir",options->AccelDir); + n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); + n += warn_if_option_path_is_relative("PidFile",options->PidFile); for (config_line_t *hs_line = options->RendConfigLines; hs_line; hs_line = hs_line->next) { if (!strcasecmp(hs_line->key, "HiddenServiceDir")) - warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); + n += warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); + } + return n != 0; +} + +/* Validate options related to the scheduler. From the Schedulers list, the + * SchedulerTypes_ list is created with int values so once we select the + * scheduler, which can happen anytime at runtime, we don't have to parse + * strings and thus be quick. + * + * Return 0 on success else -1 and msg is set with an error message. */ +static int +options_validate_scheduler(or_options_t *options, char **msg) +{ + tor_assert(options); + tor_assert(msg); + + if (!options->Schedulers || smartlist_len(options->Schedulers) == 0) { + REJECT("Empty Schedulers list. Either remove the option so the defaults " + "can be used or set at least one value."); + } + /* Ok, we do have scheduler types, validate them. */ + options->SchedulerTypes_ = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(options->Schedulers, const char *, type) { + int *sched_type; + if (!strcasecmp("KISTLite", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_KIST_LITE; + smartlist_add(options->SchedulerTypes_, sched_type); + } else if (!strcasecmp("KIST", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_KIST; + smartlist_add(options->SchedulerTypes_, sched_type); + } else if (!strcasecmp("Vanilla", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_VANILLA; + smartlist_add(options->SchedulerTypes_, sched_type); + } else { + tor_asprintf(msg, "Unknown type %s in option Schedulers. " + "Possible values are KIST, KISTLite and Vanilla.", + escaped(type)); + return -1; + } + } SMARTLIST_FOREACH_END(type); + + if (options->KISTSockBufSizeFactor < 0) { + REJECT("KISTSockBufSizeFactor must be at least 0"); + } + + /* Don't need to validate that the Interval is less than anything because + * zero is valid and all negative values are valid. */ + if (options->KISTSchedRunInterval > KIST_SCHED_RUN_INTERVAL_MAX) { + tor_asprintf(msg, "KISTSchedRunInterval must not be more than %d (ms)", + KIST_SCHED_RUN_INTERVAL_MAX); + return -1; } + + return 0; } /* Validate options related to single onion services. @@ -2908,7 +3049,8 @@ options_validate_single_onion(or_options_t *options, char **msg) const int client_port_set = (options->SocksPort_set || options->TransPort_set || options->NATDPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); if (rend_service_non_anonymous_mode_enabled(options) && client_port_set && !options->Tor2webMode) { REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as " @@ -2980,7 +3122,11 @@ options_validate(or_options_t *old_options, or_options_t *options, * Always use the value of UseEntryGuards, not UseEntryGuards_option. */ options->UseEntryGuards = options->UseEntryGuards_option; - warn_about_relative_paths(options); + if (warn_about_relative_paths(options) && options->RunAsDaemon) { + REJECT("You have specified at least one relative path (see above) " + "with the RunAsDaemon option. RunAsDaemon is not compatible " + "with relative paths."); + } if (server_mode(options) && (!strcmpstart(uname, "Windows 95") || @@ -3063,7 +3209,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "and OS X/Darwin-specific feature."); #else options->TransProxyType_parsed = TPT_PF_DIVERT; -#endif +#endif /* !defined(OpenBSD) && !defined( DARWIN ) */ } else if (!strcasecmp(options->TransProxyType, "tproxy")) { #if !defined(__linux__) REJECT("TPROXY is a Linux-specific feature."); @@ -3077,7 +3223,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "and OS X/Darwin-specific feature."); #else options->TransProxyType_parsed = TPT_IPFW; -#endif +#endif /* !defined(KERNEL_MAY_SUPPORT_IPFW) */ } else { REJECT("Unrecognized value for TransProxyType"); } @@ -3087,10 +3233,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Cannot use TransProxyType without any valid TransPort."); } } -#else +#else /* !(defined(USE_TRANSPARENT)) */ if (options->TransPort_set) REJECT("TransPort is disabled in this build."); -#endif +#endif /* defined(USE_TRANSPARENT) */ if (options->TokenBucketRefillInterval <= 0 || options->TokenBucketRefillInterval > 1000) { @@ -3103,17 +3249,6 @@ options_validate(or_options_t *old_options, or_options_t *options, 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) { @@ -3158,7 +3293,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "UseEntryGuards. Disabling."); options->UseEntryGuards = 0; } - if (!options->DownloadExtraInfo && authdir_mode_any_main(options)) { + if (!options->DownloadExtraInfo && authdir_mode_v3(options)) { log_info(LD_CONFIG, "Authoritative directories always try to download " "extra-info documents. Setting DownloadExtraInfo."); options->DownloadExtraInfo = 1; @@ -3376,6 +3511,15 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Relays cannot set ReducedConnectionPadding. "); } + if (options->BridgeDistribution) { + if (!options->BridgeRelay) { + REJECT("You set BridgeDistribution, but you didn't set BridgeRelay!"); + } + if (check_bridge_distribution_setting(options->BridgeDistribution) < 0) { + REJECT("Invalid BridgeDistribution value."); + } + } + if (options->MinUptimeHidServDirectoryV2 < 0) { log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at " "least 0 seconds. Changing to 0."); @@ -3388,7 +3532,7 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->RendPostPeriod < min_rendpostperiod) { log_warn(LD_CONFIG, "RendPostPeriod option is too short; " "raising to %d seconds.", min_rendpostperiod); - options->RendPostPeriod = min_rendpostperiod;; + options->RendPostPeriod = min_rendpostperiod; } if (options->RendPostPeriod > MAX_DIR_PERIOD) { @@ -3422,7 +3566,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "Tor2WebMode is enabled; disabling UseEntryGuards."); options->UseEntryGuards = 0; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ if (options->Tor2webRendezvousPoints && !options->Tor2webMode) { REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode."); @@ -3568,6 +3712,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("PortForwarding is not compatible with Sandbox; at most one can " "be set"); } + if (options->PortForwarding && options->NoExec) { + COMPLAIN("Both PortForwarding and NoExec are set; PortForwarding will " + "be ignored."); + } if (ensure_bandwidth_cap(&options->BandwidthRate, "BandwidthRate", msg) < 0) @@ -4006,7 +4154,7 @@ options_validate(or_options_t *old_options, or_options_t *options, COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours."); } - if (rend_config_services(options, 1) < 0) + if (hs_config_service_all(options, 1) < 0) REJECT("Failed to configure rendezvous options. See logs for details."); /* Parse client-side authorization for hidden services. */ @@ -4050,6 +4198,7 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingServerConsensusDownloadSchedule); CHECK_DEFAULT(TestingClientConsensusDownloadSchedule); CHECK_DEFAULT(TestingBridgeDownloadSchedule); + CHECK_DEFAULT(TestingBridgeBootstrapDownloadSchedule); CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest); CHECK_DEFAULT(TestingDirConnectionMaxStall); CHECK_DEFAULT(TestingConsensusMaxDownloadTries); @@ -4063,6 +4212,10 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingLinkKeySlop); #undef CHECK_DEFAULT + if (!options->ClientDNSRejectInternalAddresses && + !(options->DirAuthorities || + (options->AlternateDirAuthority && options->AlternateBridgeAuthority))) + REJECT("ClientDNSRejectInternalAddresses used for default network."); if (options->SigningKeyLifetime < options->TestingSigningKeySlop*2) REJECT("SigningKeyLifetime is too short."); if (options->TestingLinkCertLifetime < options->TestingAuthKeySlop*2) @@ -4226,6 +4379,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid " "combination."); + if (options_validate_scheduler(options, msg) < 0) { + return -1; + } + return 0; } @@ -4260,7 +4417,7 @@ compute_real_max_mem_in_queues(const uint64_t val, int log_guess) #else /* (presumably) 32-bit system. Let's hope for 1 GB. */ result = ONE_GIGABYTE; -#endif +#endif /* SIZEOF_VOID_P >= 8 */ } else { /* We detected it, so let's pick 3/4 of the total RAM as our limit. */ const uint64_t avail = (ram / 4) * 3; @@ -4445,6 +4602,12 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->NoExec && !new_val->NoExec) { + *msg = tor_strdup("While Tor is running, disabling " + "NoExec is not allowed."); + return -1; + } + if (sandbox_is_active()) { #define SB_NOCHANGE_STR(opt) \ do { \ @@ -4535,6 +4698,8 @@ options_transition_affects_descriptor(const or_options_t *old_options, get_effective_bwburst(old_options) != get_effective_bwburst(new_options) || !opt_streq(old_options->ContactInfo, new_options->ContactInfo) || + !opt_streq(old_options->BridgeDistribution, + new_options->BridgeDistribution) || !config_lines_eq(old_options->MyFamily, new_options->MyFamily) || !opt_streq(old_options->AccountingStart, new_options->AccountingStart) || old_options->AccountingMax != new_options->AccountingMax || @@ -4588,7 +4753,7 @@ get_windows_conf_root(void) path[sizeof(path)-1] = '\0'; #else strlcpy(path,tpath,sizeof(path)); -#endif +#endif /* defined(UNICODE) */ /* Now we need to free the memory that the path-idl was stored in. In * typical Windows fashion, we can't just call 'free()' on it. */ @@ -4604,7 +4769,7 @@ get_windows_conf_root(void) is_set = 1; return path; } -#endif +#endif /* defined(_WIN32) */ /** Return the default location for our torrc file (if <b>defaults_file</b> is * false), or for the torrc-defaults file (if <b>defaults_file</b> is true). */ @@ -4628,7 +4793,7 @@ get_default_conf_file(int defaults_file) } #else return defaults_file ? CONFDIR "/torrc-defaults" : CONFDIR "/torrc"; -#endif +#endif /* defined(DISABLE_SYSTEM_TORRC) || ... */ } /** Verify whether lst is a list of strings containing valid-looking @@ -4779,9 +4944,9 @@ find_torrc_filename(config_line_t *cmd_arg, } else { fname = dflt ? tor_strdup(dflt) : NULL; } -#else +#else /* !(!defined(_WIN32)) */ fname = dflt ? tor_strdup(dflt) : NULL; -#endif +#endif /* !defined(_WIN32) */ } } return fname; @@ -4932,6 +5097,9 @@ options_init_from_torrc(int argc, char **argv) for (p_index = cmdline_only_options; p_index; p_index = p_index->next) { if (!strcmp(p_index->key,"--keygen")) { command = CMD_KEYGEN; + } else if (!strcmp(p_index->key, "--key-expiration")) { + command = CMD_KEY_EXPIRATION; + command_arg = p_index->value; } else if (!strcmp(p_index->key,"--list-fingerprint")) { command = CMD_LIST_FINGERPRINT; } else if (!strcmp(p_index->key, "--hash-password")) { @@ -5179,6 +5347,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, } newoptions->IncludeUsed = cf_has_include; + in_option_validation = 1; /* Validate newoptions */ if (options_validate(oldoptions, newoptions, newdefaultoptions, @@ -5191,17 +5360,20 @@ options_init_from_string(const char *cf_defaults, const char *cf, err = SETOPT_ERR_TRANSITION; goto err; } + in_option_validation = 0; if (set_options(newoptions, msg)) { err = SETOPT_ERR_SETTING; goto err; /* frees and replaces old options */ } + or_options_free(global_default_options); global_default_options = newdefaultoptions; return SETOPT_OK; err: + in_option_validation = 0; or_options_free(newoptions); or_options_free(newdefaultoptions); if (*msg) { @@ -5405,7 +5577,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options, } #else log_warn(LD_CONFIG, "Syslog is not supported on this system. Sorry."); -#endif +#endif /* defined(HAVE_SYSLOG_H) */ goto cleanup; } @@ -5722,6 +5894,15 @@ parse_transport_line(const or_options_t *options, goto err; } + if (is_managed && options->NoExec) { + log_warn(LD_CONFIG, + "Managed proxies are not compatible with NoExec mode; ignoring." + "(%sTransportPlugin line was %s)", + server ? "Server" : "Client", escaped(line)); + r = 0; + goto done; + } + if (is_managed) { /* managed */ @@ -5992,6 +6173,8 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, dirinfo_type_t type = 0; double weight = 1.0; + memset(v3_digest, 0, sizeof(v3_digest)); + items = smartlist_new(); smartlist_split_string(items, line, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); @@ -6241,7 +6424,6 @@ port_cfg_new(size_t namelen) cfg->entry_cfg.ipv6_traffic = 1; cfg->entry_cfg.dns_request = 1; cfg->entry_cfg.onion_traffic = 1; - cfg->entry_cfg.cache_ipv4_answers = 1; cfg->entry_cfg.prefer_ipv6_virtaddr = 1; return cfg; } @@ -6256,8 +6438,9 @@ port_cfg_free(port_cfg_t *port) /** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is * on a publicly routable address. */ static void -warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname, - int listener_type) +warn_nonlocal_client_ports(const smartlist_t *ports, + const char *portname, + const int listener_type) { SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { if (port->type != listener_type) @@ -6414,6 +6597,55 @@ warn_client_dns_cache(const char *option, int disabling) } /** + * Validate the configured bridge distribution method from a BridgeDistribution + * config line. + * + * The input <b>bd</b>, is a string taken from the BridgeDistribution config + * line (if present). If the option wasn't set, return 0 immediately. The + * BridgeDistribution option is then validated. Currently valid, recognised + * options are: + * + * - "none" + * - "any" + * - "https" + * - "email" + * - "moat" + * - "hyphae" + * + * If the option string is unrecognised, a warning will be logged and 0 is + * returned. If the option string contains an invalid character, -1 is + * returned. + **/ +STATIC int +check_bridge_distribution_setting(const char *bd) +{ + if (bd == NULL) + return 0; + + const char *RECOGNIZED[] = { + "none", "any", "https", "email", "moat", "hyphae" + }; + unsigned i; + for (i = 0; i < ARRAY_LENGTH(RECOGNIZED); ++i) { + if (!strcmp(bd, RECOGNIZED[i])) + return 0; + } + + const char *cp = bd; + // Method = (KeywordChar | "_") + + while (TOR_ISALNUM(*cp) || *cp == '-' || *cp == '_') + ++cp; + + if (*cp == 0) { + log_warn(LD_CONFIG, "Unrecognized BridgeDistribution value %s. I'll " + "assume you know what you are doing...", escaped(bd)); + return 0; // we reached the end of the string; all is well + } else { + return -1; // we found a bad character in the string. + } +} + +/** * Parse port configuration for a single port type. * * Read entries of the "FooPort" type from the list <b>ports</b>. Syntax is @@ -6507,7 +6739,7 @@ parse_port_config(smartlist_t *out, bind_ipv4_only = 0, bind_ipv6_only = 0, ipv4_traffic = 1, ipv6_traffic = 1, prefer_ipv6 = 0, dns_request = 1, onion_traffic = 1, - cache_ipv4 = 1, use_cached_ipv4 = 0, + cache_ipv4 = 0, use_cached_ipv4 = 0, cache_ipv6 = 0, use_cached_ipv6 = 0, prefer_ipv6_automap = 1, world_writable = 0, group_writable = 0, relax_dirmode_check = 0, @@ -6606,7 +6838,7 @@ parse_port_config(smartlist_t *out, } else if (!strcasecmp(elt, "AllAddrs")) { all_addrs = 1; -#endif +#endif /* 0 */ } else if (!strcasecmp(elt, "IPv4Only")) { bind_ipv4_only = 1; } else if (!strcasecmp(elt, "IPv6Only")) { @@ -6942,7 +7174,8 @@ parse_ports(or_options_t *options, int validate_only, options->SocksPort_lines, "Socks", CONN_TYPE_AP_LISTENER, "127.0.0.1", 9050, - CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) { + ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL) + | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) { *msg = tor_strdup("Invalid SocksPort configuration"); goto err; } @@ -6970,6 +7203,15 @@ parse_ports(or_options_t *options, int validate_only, *msg = tor_strdup("Invalid NatdPort configuration"); goto err; } + if (parse_port_config(ports, + options->HTTPTunnelPort_lines, + "HTTP Tunnel", CONN_TYPE_AP_HTTP_CONNECT_LISTENER, + "127.0.0.1", 0, + ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL) + | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) { + *msg = tor_strdup("Invalid HTTPTunnelPort configuration"); + goto err; + } { unsigned control_port_flags = CL_PORT_NO_STREAM_OPTIONS | CL_PORT_WARN_NONLOCAL; @@ -7047,6 +7289,8 @@ parse_ports(or_options_t *options, int validate_only, !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1); options->NATDPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1); + options->HTTPTunnelPort_set = + !! count_real_listeners(ports, CONN_TYPE_AP_HTTP_CONNECT_LISTENER, 1); /* Use options->ControlSocket to test if a control socket is set */ options->ControlPort_set = !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0); @@ -7377,7 +7621,7 @@ normalize_data_directory(or_options_t *options) strlcpy(p,get_windows_conf_root(),MAX_PATH); options->DataDirectory = p; return 0; -#else +#else /* !(defined(_WIN32)) */ const char *d = options->DataDirectory; if (!d) d = "~/.tor"; @@ -7403,7 +7647,7 @@ normalize_data_directory(or_options_t *options) options->DataDirectory = fn; } return 0; -#endif +#endif /* defined(_WIN32) */ } /** Check and normalize the value of options->DataDirectory; return 0 if it @@ -7891,13 +8135,28 @@ parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) memset(&options->OutboundBindAddresses, 0, sizeof(options->OutboundBindAddresses)); } - parse_outbound_address_lines(options->OutboundBindAddress, - OUTBOUND_ADDR_EXIT_AND_OR, options, validate_only, msg); - parse_outbound_address_lines(options->OutboundBindAddressOR, - OUTBOUND_ADDR_OR, options, validate_only, msg); - parse_outbound_address_lines(options->OutboundBindAddressExit, - OUTBOUND_ADDR_EXIT, options, validate_only, msg); + + if (parse_outbound_address_lines(options->OutboundBindAddress, + OUTBOUND_ADDR_EXIT_AND_OR, options, + validate_only, msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressOR, + OUTBOUND_ADDR_OR, options, validate_only, + msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressExit, + OUTBOUND_ADDR_EXIT, options, validate_only, + msg) < 0) { + goto err; + } + return 0; + err: + return -1; } /** Load one of the geoip files, <a>family</a> determining which @@ -7919,10 +8178,10 @@ config_load_geoip_file_(sa_family_t family, } geoip_load_file(family, fname); tor_free(free_fname); -#else +#else /* !(defined(_WIN32)) */ (void)default_fname; geoip_load_file(family, fname); -#endif +#endif /* defined(_WIN32) */ } /** Load geoip files for IPv4 and IPv6 if <a>options</a> and @@ -7997,9 +8256,9 @@ init_cookie_authentication(const char *fname, const char *header, log_warn(LD_FS,"Unable to make %s group-readable.", escaped(fname)); } } -#else +#else /* !(!defined(_WIN32)) */ (void) group_readable; -#endif +#endif /* !defined(_WIN32) */ /* Success! */ log_info(LD_GENERAL, "Generated auth cookie file in '%s'.", escaped(fname)); diff --git a/src/or/config.h b/src/or/config.h index 27aec7fe3d..efdd8c59b0 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -18,6 +18,10 @@ #define KERNEL_MAY_SUPPORT_IPFW #endif +/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might + * expose more information than we're comfortable with. */ +#define MIN_HEARTBEAT_PERIOD (30*60) + MOCK_DECL(const char*, get_dirportfrontpage, (void)); MOCK_DECL(const or_options_t *, get_options, (void)); MOCK_DECL(or_options_t *, get_options_mutable, (void)); @@ -27,6 +31,7 @@ const char *safe_str_client(const char *address); const char *safe_str(const char *address); const char *escaped_safe_str_client(const char *address); const char *escaped_safe_str(const char *address); +int get_protocol_warning_severity_level(void); const char *get_version(void); const char *get_short_version(void); setopt_err_t options_trial_assign(config_line_t *list, unsigned flags, @@ -198,7 +203,9 @@ STATIC int parse_port_config(smartlist_t *out, const char *defaultaddr, int defaultport, const unsigned flags); -#endif -#endif +STATIC int check_bridge_distribution_setting(const char *bd); +#endif /* defined(CONFIG_PRIVATE) */ + +#endif /* !defined(TOR_CONFIG_H) */ diff --git a/src/or/confparse.h b/src/or/confparse.h index 9c4205d07c..6f0b3b325c 100644 --- a/src/or/confparse.h +++ b/src/or/confparse.h @@ -40,6 +40,36 @@ typedef enum config_type_t { CONFIG_TYPE_OBSOLETE, /**< Obsolete (ignored) option. */ } config_type_t; +#ifdef TOR_UNIT_TESTS +/** + * Union used when building in test mode typechecking the members of a type + * used with confparse.c. See CONF_CHECK_VAR_TYPE for a description of how + * it is used. */ +typedef union { + char **STRING; + char **FILENAME; + int *UINT; /* yes, really: Even though the confparse type is called + * "UINT", it still uses the C int type -- it just enforces that + * the values are in range [0,INT_MAX]. + */ + int *INT; + int *PORT; + int *INTERVAL; + int *MSEC_INTERVAL; + uint64_t *MEMUNIT; + double *DOUBLE; + int *BOOL; + int *AUTOBOOL; + time_t *ISOTIME; + smartlist_t **CSV; + smartlist_t **CSV_INTERVAL; + config_line_t **LINELIST; + config_line_t **LINELIST_S; + config_line_t **LINELIST_V; + routerset_t **ROUTERSET; +} confparse_dummy_values_t; +#endif + /** An abbreviation for a configuration option allowed on the command line. */ typedef struct config_abbrev_t { const char *abbreviated; @@ -64,8 +94,52 @@ typedef struct config_var_t { * value. */ off_t var_offset; /**< Offset of the corresponding member of or_options_t. */ const char *initvalue; /**< String (or null) describing initial value. */ + +#ifdef TOR_UNIT_TESTS + /** Used for compiler-magic to typecheck the corresponding field in the + * corresponding struct. Only used in unit test mode, at compile-time. */ + confparse_dummy_values_t var_ptr_dummy; +#endif } config_var_t; +/* Macros to define extra members inside config_var_t fields, and at the + * end of a list of them. + */ +#ifdef TOR_UNIT_TESTS +/* This is a somewhat magic type-checking macro for users of confparse.c. + * It initializes a union member "confparse_dummy_values_t.conftype" with + * the address of a static member "tp_dummy.member". This + * will give a compiler warning unless the member field is of the correct + * type. + * + * (This warning is mandatory, because a type mismatch here violates the type + * compatibility constraint for simple assignment, and requires a diagnostic, + * according to the C spec.) + * + * For example, suppose you say: + * "CONF_CHECK_VAR_TYPE(or_options_t, STRING, Address)". + * Then this macro will evaluate to: + * { .STRING = &or_options_t_dummy.Address } + * And since confparse_dummy_values_t.STRING has type "char **", that + * expression will create a warning unless or_options_t.Address also + * has type "char *". + */ +#define CONF_CHECK_VAR_TYPE(tp, conftype, member) \ + { . conftype = &tp ## _dummy . member } +#define CONF_TEST_MEMBERS(tp, conftype, member) \ + , CONF_CHECK_VAR_TYPE(tp, conftype, member) +#define END_OF_CONFIG_VARS \ + { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL, { .INT=NULL } } +#define DUMMY_TYPECHECK_INSTANCE(tp) \ + static tp tp ## _dummy +#else +#define CONF_TEST_MEMBERS(tp, conftype, member) +#define END_OF_CONFIG_VARS { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } +/* Repeatedly declarable incomplete struct to absorb redundant semicolons */ +#define DUMMY_TYPECHECK_INSTANCE(tp) \ + struct tor_semicolon_eater +#endif + /** Type of a callback to validate whether a given configuration is * well-formed and consistent. See options_trial_assign() for documentation * of arguments. */ @@ -129,5 +203,5 @@ const char *config_expand_abbrev(const config_format_t *fmt, int command_line, int warn_obsolete); void warn_deprecated_option(const char *what, const char *why); -#endif +#endif /* !defined(TOR_CONFPARSE_H) */ diff --git a/src/or/connection.c b/src/or/connection.c index 192d705b5b..d2cf4fb416 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -37,7 +37,7 @@ * they call connection_stop_reading() or connection_stop_writing(). * * To queue data to be written on a connection, call - * connection_write_to_buf(). When data arrives, the + * connection_buf_add(). When data arrives, the * connection_process_inbuf() callback is invoked, which dispatches to a * type-specific function (such as connection_edge_process_inbuf() for * example). Connection types that need notice of when data has been written @@ -58,6 +58,7 @@ #include "or.h" #include "bridges.h" #include "buffers.h" +#include "buffers_tls.h" /* * Define this so we get channel internal functions, since we're implementing * part of a subclass (channel_tls_t). @@ -84,7 +85,10 @@ #include "geoip.h" #include "main.h" #include "hs_common.h" +#include "hs_ident.h" #include "nodelist.h" +#include "proto_http.h" +#include "proto_socks.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -123,8 +127,9 @@ static int connection_finished_flushing(connection_t *conn); static int connection_flushed_some(connection_t *conn); static int connection_finished_connecting(connection_t *conn); static int connection_reached_eof(connection_t *conn); -static int connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, - int *socket_error); +static int connection_buf_read_from_socket(connection_t *conn, + ssize_t *max_to_read, + int *socket_error); static int connection_process_inbuf(connection_t *conn, int package_partial); static void client_check_address_changed(tor_socket_t sock); static void set_constrained_socket_buffers(tor_socket_t sock, int size); @@ -157,7 +162,8 @@ static smartlist_t *outgoing_addrs = NULL; case CONN_TYPE_CONTROL_LISTENER: \ case CONN_TYPE_AP_TRANS_LISTENER: \ case CONN_TYPE_AP_NATD_LISTENER: \ - case CONN_TYPE_AP_DNS_LISTENER + case CONN_TYPE_AP_DNS_LISTENER: \ + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER /**************************************************************/ @@ -184,6 +190,7 @@ conn_type_to_string(int type) case CONN_TYPE_CONTROL: return "Control"; case CONN_TYPE_EXT_OR: return "Extended OR"; case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener"; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return "HTTP tunnel listener"; default: log_warn(LD_BUG, "unknown connection type %d", type); tor_snprintf(buf, sizeof(buf), "unknown [%d]", type); @@ -605,6 +612,7 @@ connection_free_(connection_t *conn) } if (CONN_IS_EDGE(conn)) { rend_data_free(TO_EDGE_CONN(conn)->rend_data); + hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident); } if (conn->type == CONN_TYPE_CONTROL) { control_connection_t *control_conn = TO_CONTROL_CONN(conn); @@ -636,6 +644,7 @@ connection_free_(connection_t *conn) } rend_data_free(dir_conn->rend_data); + hs_ident_dir_conn_free(dir_conn->hs_ident); if (dir_conn->guard_state) { /* Cancel before freeing, if it's still there. */ entry_guard_cancel(&dir_conn->guard_state); @@ -695,7 +704,7 @@ connection_free,(connection_t *conn)) connection_ap_warn_and_unmark_if_pending_circ(TO_ENTRY_CONN(conn), "connection_free"); } -#endif +#endif /* 1 */ connection_unregister_events(conn); connection_free_(conn); } @@ -807,7 +816,7 @@ connection_mark_for_close_(connection_t *conn, int line, const char *file) * CONN_TYPE_OR checks; this should be called when you either are sure that * if this is an or_connection_t the controlling channel has been notified * (e.g. with connection_or_notify_error()), or you actually are the - * connection_or_close_for_error() or connection_or_close_normally function. + * connection_or_close_for_error() or connection_or_close_normally() function. * For all other cases, use connection_mark_and_flush() instead, which * checks for or_connection_t properly, instead. See below. */ @@ -923,7 +932,7 @@ create_unix_sockaddr(const char *listenaddress, char **readable_address, *len_out = sizeof(struct sockaddr_un); return sockaddr; } -#else +#else /* !(defined(HAVE_SYS_UN_H) || defined(RUNNING_DOXYGEN)) */ static struct sockaddr * create_unix_sockaddr(const char *listenaddress, char **readable_address, socklen_t *len_out) @@ -936,7 +945,7 @@ create_unix_sockaddr(const char *listenaddress, char **readable_address, tor_fragile_assert(); return NULL; } -#endif /* HAVE_SYS_UN_H */ +#endif /* defined(HAVE_SYS_UN_H) || defined(RUNNING_DOXYGEN) */ /** 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 @@ -1051,7 +1060,7 @@ check_location_for_unix_socket(const or_options_t *options, const char *path, tor_free(p); return r; } -#endif +#endif /* defined(HAVE_SYS_UN_H) */ /** Tell the TCP stack that it shouldn't wait for a long time after * <b>sock</b> has closed before reusing its port. Return 0 on success, @@ -1074,7 +1083,7 @@ make_socket_reuseable(tor_socket_t sock) return -1; } return 0; -#endif +#endif /* defined(_WIN32) */ } #ifdef _WIN32 @@ -1095,12 +1104,12 @@ make_win32_socket_exclusive(tor_socket_t sock) return -1; } return 0; -#else +#else /* !(defined(SO_EXCLUSIVEADDRUSE)) */ (void) sock; return 0; -#endif +#endif /* defined(SO_EXCLUSIVEADDRUSE) */ } -#endif +#endif /* defined(_WIN32) */ /** Max backlog to pass to listen. We start at */ static int listen_limit = INT_MAX; @@ -1190,7 +1199,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, conn_type_to_string(type), tor_socket_strerror(errno)); } -#endif +#endif /* defined(_WIN32) */ #if defined(USE_TRANSPARENT) && defined(IP_TRANSPARENT) if (options->TransProxyType_parsed == TPT_TPROXY && @@ -1207,7 +1216,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_socket_strerror(e), extra); } } -#endif +#endif /* defined(USE_TRANSPARENT) && defined(IP_TRANSPARENT) */ #ifdef IPV6_V6ONLY if (listensockaddr->sa_family == AF_INET6) { @@ -1222,7 +1231,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, /* Keep going; probably not harmful. */ } } -#endif +#endif /* defined(IPV6_V6ONLY) */ if (bind(s,listensockaddr,socklen) < 0) { const char *helpfulhint = ""; @@ -1325,7 +1334,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, goto err; } } -#endif +#endif /* defined(HAVE_PWD_H) */ { unsigned mode; @@ -1356,7 +1365,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_socket_strerror(tor_socket_errno(s))); goto err; } -#endif /* HAVE_SYS_UN_H */ +#endif /* defined(HAVE_SYS_UN_H) */ } else { log_err(LD_BUG, "Got unexpected address family %d.", listensockaddr->sa_family); @@ -1703,6 +1712,8 @@ connection_init_accepted_conn(connection_t *conn, TO_ENTRY_CONN(conn)->is_transparent_ap = 1; conn->state = AP_CONN_STATE_NATD_WAIT; break; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: + conn->state = AP_CONN_STATE_HTTP_CONNECT_WAIT; } break; case CONN_TYPE_DIR: @@ -2141,7 +2152,7 @@ connection_proxy_connect(connection_t *conn, int type) fmt_addrport(&conn->addr, conn->port)); } - connection_write_to_buf(buf, strlen(buf), conn); + connection_buf_add(buf, strlen(buf), conn); conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK; break; } @@ -2207,7 +2218,7 @@ connection_proxy_connect(connection_t *conn, int type) buf[8] = 0; /* no userid */ } - connection_write_to_buf((char *)buf, buf_size, conn); + connection_buf_add((char *)buf, buf_size, conn); tor_free(buf); conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; @@ -2238,7 +2249,7 @@ connection_proxy_connect(connection_t *conn, int type) conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE; } - connection_write_to_buf((char *)buf, 2 + buf[1], conn); + connection_buf_add((char *)buf, 2 + buf[1], conn); break; } @@ -2344,7 +2355,7 @@ connection_send_socks5_connect(connection_t *conn) memcpy(buf + 20, &port, 2); } - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK; } @@ -2470,7 +2481,7 @@ connection_read_proxy_handshake(connection_t *conn) if (socks_args_string) tor_free(socks_args_string); - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK; ret = 0; @@ -2619,7 +2630,7 @@ retry_listener_ports(smartlist_t *old_conns, if (port->is_unix_addr && !geteuid() && (options->User) && strcmp(options->User, "root")) continue; -#endif +#endif /* !defined(_WIN32) */ if (port->is_unix_addr) { listensockaddr = (struct sockaddr *) @@ -3055,9 +3066,11 @@ connection_buckets_decrement(connection_t *conn, time_t now, (unsigned long)num_read, (unsigned long)num_written, conn_type_to_string(conn->type), conn_state_to_string(conn->type, conn->state)); - if (num_written >= INT_MAX) num_written = 1; - if (num_read >= INT_MAX) num_read = 1; - tor_fragile_assert(); + tor_assert_nonfatal_unreached(); + if (num_written >= INT_MAX) + num_written = 1; + if (num_read >= INT_MAX) + num_read = 1; } record_num_bytes_transferred_impl(conn, now, num_read, num_written); @@ -3369,7 +3382,7 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn) /** Read bytes from conn-\>s and process them. * - * It calls connection_read_to_buf() to bring in any new bytes, + * It calls connection_buf_read_from_socket() to bring in any new bytes, * and then calls connection_process_inbuf() to process them. * * Mark the connection and return -1 if you want to close it, else @@ -3395,6 +3408,7 @@ connection_handle_read_impl(connection_t *conn) case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: case CONN_TYPE_AP_NATD_LISTENER: + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_AP); case CONN_TYPE_DIR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_DIR); @@ -3411,7 +3425,7 @@ connection_handle_read_impl(connection_t *conn) tor_assert(!conn->marked_for_close); before = buf_datalen(conn->inbuf); - if (connection_read_to_buf(conn, &max_to_read, &socket_error) < 0) { + if (connection_buf_read_from_socket(conn, &max_to_read, &socket_error) < 0) { /* There's a read error; kill the connection.*/ if (conn->type == CONN_TYPE_OR) { connection_or_notify_error(TO_OR_CONN(conn), @@ -3508,7 +3522,7 @@ connection_handle_read(connection_t *conn) * Return -1 if we want to break conn, else return 0. */ static int -connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, +connection_buf_read_from_socket(connection_t *conn, ssize_t *max_to_read, int *socket_error) { int result; @@ -3549,7 +3563,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, initial_size = buf_datalen(conn->inbuf); /* else open, or closing */ - result = read_to_buf_tls(or_conn->tls, at_most, conn->inbuf); + result = buf_read_from_tls(conn->inbuf, or_conn->tls, at_most); if (TOR_TLS_IS_ERROR(result) || result == TOR_TLS_CLOSE) or_conn->tls_error = result; else @@ -3585,10 +3599,8 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, connection_start_reading(conn); } /* we're already reading, one hopes */ - result = 0; break; case TOR_TLS_DONE: /* no data read, so nothing to process */ - result = 0; break; /* so we call bucket_decrement below */ default: break; @@ -3598,7 +3610,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* If we have any pending bytes, we read them now. This *can* * take us over our read allotment, but really we shouldn't be * believing that SSL bytes are the same as TCP bytes anyway. */ - int r2 = read_to_buf_tls(or_conn->tls, pending, conn->inbuf); + int r2 = buf_read_from_tls(conn->inbuf, or_conn->tls, pending); if (BUG(r2<0)) { log_warn(LD_BUG, "apparently, reading pending bytes can fail."); return -1; @@ -3610,7 +3622,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, result, (long)n_read, (long)n_written); } else if (conn->linked) { if (conn->linked_conn) { - result = move_buf_to_buf(conn->inbuf, conn->linked_conn->outbuf, + result = buf_move_to_buf(conn->inbuf, conn->linked_conn->outbuf, &conn->linked_conn->outbuf_flushlen); } else { result = 0; @@ -3628,8 +3640,10 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* !connection_speaks_cells, !conn->linked_conn. */ int reached_eof = 0; CONN_LOG_PROTECT(conn, - result = read_to_buf(conn->s, at_most, conn->inbuf, &reached_eof, - socket_error)); + result = buf_read_from_socket(conn->inbuf, conn->s, + at_most, + &reached_eof, + socket_error)); if (reached_eof) conn->inbuf_reached_eof = 1; @@ -3698,17 +3712,17 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /** A pass-through to fetch_from_buf. */ int -connection_fetch_from_buf(char *string, size_t len, connection_t *conn) +connection_buf_get_bytes(char *string, size_t len, connection_t *conn) { - return fetch_from_buf(string, len, conn->inbuf); + return buf_get_bytes(conn->inbuf, string, len); } -/** As fetch_from_buf_line(), but read from a connection's input buffer. */ +/** As buf_get_line(), but read from a connection's input buffer. */ int -connection_fetch_from_buf_line(connection_t *conn, char *data, +connection_buf_get_line(connection_t *conn, char *data, size_t *data_len) { - return fetch_from_buf_line(conn->inbuf, data, data_len); + return buf_get_line(conn->inbuf, data, data_len); } /** As fetch_from_buf_http, but fetches from a connection's input buffer_t as @@ -3745,7 +3759,7 @@ connection_outbuf_too_full(connection_t *conn) * * 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. + * from connection_buf_add() when an entire TLS record is ready. * * Update <b>conn</b>-\>timestamp_lastwritten to now, and call flush_buf * or flush_buf_tls appropriately. If it succeeds and there are no more @@ -3856,7 +3870,7 @@ connection_handle_write_impl(connection_t *conn, int force) /* else open, or closing */ initial_size = buf_datalen(conn->outbuf); - result = flush_buf_tls(or_conn->tls, conn->outbuf, + result = buf_flush_to_tls(conn->outbuf, or_conn->tls, max_to_write, &conn->outbuf_flushlen); /* If we just flushed the last bytes, tell the channel on the @@ -3919,8 +3933,8 @@ connection_handle_write_impl(connection_t *conn, int force) result = (int)(initial_size-buf_datalen(conn->outbuf)); } else { CONN_LOG_PROTECT(conn, - result = flush_buf(conn->s, conn->outbuf, - max_to_write, &conn->outbuf_flushlen)); + result = buf_flush_to_socket(conn->outbuf, conn->s, + max_to_write, &conn->outbuf_flushlen)); if (result < 0) { if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); @@ -4060,11 +4074,11 @@ connection_write_to_buf_impl_,(const char *string, size_t len, if (zlib) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); int done = zlib < 0; - CONN_LOG_PROTECT(conn, r = write_to_buf_compress(conn->outbuf, + CONN_LOG_PROTECT(conn, r = buf_add_compress(conn->outbuf, dir_conn->compress_state, string, len, done)); } else { - CONN_LOG_PROTECT(conn, r = write_to_buf(string, len, conn->outbuf)); + CONN_LOG_PROTECT(conn, r = buf_add(conn->outbuf, string, len)); } if (r < 0) { if (CONN_IS_EDGE(conn)) { @@ -4103,6 +4117,38 @@ connection_write_to_buf_impl_,(const char *string, size_t len, } } +#define CONN_GET_ALL_TEMPLATE(var, test) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + smartlist_t *ret_conns = smartlist_new(); \ + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, var) { \ + if (var && (test) && !var->marked_for_close) \ + smartlist_add(ret_conns, var); \ + } SMARTLIST_FOREACH_END(var); \ + return ret_conns; \ + STMT_END + +/* Return a list of connections that aren't close and matches the given type + * and state. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ +smartlist_t * +connection_list_by_type_state(int type, int state) +{ + CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state)); +} + +/* Return a list of connections that aren't close and matches the given type + * and purpose. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ +smartlist_t * +connection_list_by_type_purpose(int type, int purpose) +{ + CONN_GET_ALL_TEMPLATE(conn, + (conn->type == type && conn->purpose == purpose)); +} + /** Return a connection_t * from get_connection_array() that satisfies test on * var, and that is not marked for close. */ #define CONN_GET_TEMPLATE(var, test) \ @@ -4287,6 +4333,7 @@ connection_is_listener(connection_t *conn) conn->type == CONN_TYPE_AP_TRANS_LISTENER || conn->type == CONN_TYPE_AP_DNS_LISTENER || conn->type == CONN_TYPE_AP_NATD_LISTENER || + conn->type == CONN_TYPE_AP_HTTP_CONNECT_LISTENER || conn->type == CONN_TYPE_DIR_LISTENER || conn->type == CONN_TYPE_CONTROL_LISTENER) return 1; @@ -4954,9 +5001,9 @@ assert_connection_ok(connection_t *conn, time_t now) /* buffers */ if (conn->inbuf) - assert_buf_ok(conn->inbuf); + buf_assert_ok(conn->inbuf); if (conn->outbuf) - assert_buf_ok(conn->outbuf); + buf_assert_ok(conn->outbuf); if (conn->type == CONN_TYPE_OR) { or_connection_t *or_conn = TO_OR_CONN(conn); @@ -5178,7 +5225,7 @@ clock_skew_warning(const connection_t *conn, long apparent_skew, int trusted, const char *source) { char dbuf[64]; - char *ext_source = NULL; + char *ext_source = NULL, *warn = NULL; format_time_interval(dbuf, sizeof(dbuf), apparent_skew); if (conn) tor_asprintf(&ext_source, "%s:%s:%d", source, conn->address, conn->port); @@ -5192,9 +5239,14 @@ clock_skew_warning(const connection_t *conn, long apparent_skew, int trusted, apparent_skew > 0 ? "ahead" : "behind", dbuf, apparent_skew > 0 ? "behind" : "ahead", (!conn || trusted) ? "" : ", or they are sending us the wrong time"); - if (trusted) + if (trusted) { control_event_general_status(LOG_WARN, "CLOCK_SKEW SKEW=%ld SOURCE=%s", apparent_skew, ext_source); + tor_asprintf(&warn, "Clock skew %ld in %s from %s", apparent_skew, + received, source); + control_event_bootstrap_problem(warn, "CLOCK_SKEW", conn, 1); + } + tor_free(warn); tor_free(ext_source); } diff --git a/src/or/connection.h b/src/or/connection.h index 36e45aef38..4a5bd6971b 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -123,8 +123,8 @@ void connection_bucket_refill(int seconds_elapsed, time_t now); int connection_handle_read(connection_t *conn); -int connection_fetch_from_buf(char *string, size_t len, connection_t *conn); -int connection_fetch_from_buf_line(connection_t *conn, char *data, +int connection_buf_get_bytes(char *string, size_t len, connection_t *conn); +int connection_buf_get_line(connection_t *conn, char *data, size_t *data_len); int connection_fetch_from_buf_http(connection_t *conn, char **headers_out, size_t max_headerlen, @@ -139,18 +139,18 @@ int connection_flush(connection_t *conn); 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, +static void connection_buf_add(const char *string, size_t len, connection_t *conn); /* DOCDOC connection_write_to_buf_compress */ -static void connection_write_to_buf_compress(const char *string, size_t len, +static void connection_buf_add_compress(const char *string, size_t len, dir_connection_t *conn, int done); static inline void -connection_write_to_buf(const char *string, size_t len, connection_t *conn) +connection_buf_add(const char *string, size_t len, connection_t *conn) { connection_write_to_buf_impl_(string, len, conn, 0); } static inline void -connection_write_to_buf_compress(const char *string, size_t len, +connection_buf_add_compress(const char *string, size_t len, dir_connection_t *conn, int done) { connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1); @@ -182,6 +182,8 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type, connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); +smartlist_t *connection_list_by_type_state(int type, int state); +smartlist_t *connection_list_by_type_purpose(int type, int purpose); smartlist_t *connection_dir_list_by_purpose_and_resource( int purpose, const char *resource); @@ -284,7 +286,7 @@ MOCK_DECL(STATIC int,connection_connect_sockaddr, MOCK_DECL(STATIC void, kill_conn_list_for_oos, (smartlist_t *conns)); MOCK_DECL(STATIC smartlist_t *, pick_oos_victims, (int n)); -#endif +#endif /* defined(CONNECTION_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONNECTION_H) */ diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 8480a35458..f178917f0b 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -76,9 +76,15 @@ #include "dirserv.h" #include "hibernate.h" #include "hs_common.h" +#include "hs_cache.h" +#include "hs_client.h" +#include "hs_circuit.h" #include "main.h" +#include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "proto_http.h" +#include "proto_socks.h" #include "reasons.h" #include "relay.h" #include "rendclient.h" @@ -109,7 +115,7 @@ #define TRANS_NETFILTER #define TRANS_NETFILTER_IPV6 #endif -#endif +#endif /* defined(HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H) */ #if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) #include <net/if.h> @@ -152,7 +158,9 @@ connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason, * but we should fix it someday anyway. */ if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) && connection_edge_is_rendezvous_stream(edge_conn)) { - rend_client_note_connection_attempt_ended(edge_conn->rend_data); + if (edge_conn->rend_data) { + rend_client_note_connection_attempt_ended(edge_conn->rend_data); + } } if (base_conn->marked_for_close) { @@ -236,6 +244,11 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) return -1; } return 0; + case AP_CONN_STATE_HTTP_CONNECT_WAIT: + if (connection_ap_process_http_connect(EDGE_TO_ENTRY_CONN(conn)) < 0) { + return -1; + } + return 0; case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) { @@ -485,6 +498,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) case AP_CONN_STATE_CONNECT_WAIT: case AP_CONN_STATE_CONTROLLER_WAIT: case AP_CONN_STATE_RESOLVE_WAIT: + case AP_CONN_STATE_HTTP_CONNECT_WAIT: return 0; default: log_warn(LD_BUG, "Called in unexpected state %d.",conn->base_.state); @@ -647,7 +661,7 @@ connection_ap_about_to_close(entry_connection_t *entry_conn) connection_ap_warn_and_unmark_if_pending_circ(entry_conn, "about_to_close"); } -#endif +#endif /* 1 */ control_event_stream_bandwidth(edge_conn); control_event_stream_status(entry_conn, STREAM_EVENT_CLOSED, @@ -857,9 +871,9 @@ connection_ap_rescan_and_attach_pending(void) entry_conn->marked_pending_circ_line = 0; \ entry_conn->marked_pending_circ_file = 0; \ } while (0) -#else +#else /* !(defined(DEBUGGING_17659)) */ #define UNMARK() do { } while (0) -#endif +#endif /* defined(DEBUGGING_17659) */ /** Tell any AP streams that are listed as waiting for a new circuit to try * again. If there is an available circuit for a stream, attach it. Otherwise, @@ -965,7 +979,7 @@ connection_ap_mark_as_pending_circuit_(entry_connection_t *entry_conn, log_warn(LD_BUG, "(Previously called from %s:%d.)\n", f2 ? f2 : "<NULL>", entry_conn->marked_pending_circ_line); -#endif +#endif /* defined(DEBUGGING_17659) */ log_backtrace(LOG_WARN, LD_BUG, "To debug, this may help"); return; } @@ -1073,7 +1087,8 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) if (!entry_conn->chosen_exit_optional && !entry_conn->chosen_exit_retries) continue; - r1 = node_get_by_nickname(entry_conn->chosen_exit_name, 0); + r1 = node_get_by_nickname(entry_conn->chosen_exit_name, + NNF_NO_WARN_UNNAMED); r2 = node_get_by_id(info->identity_digest); if (!r1 || !r2 || r1 != r2) continue; @@ -1176,10 +1191,10 @@ consider_plaintext_ports(entry_connection_t *conn, uint16_t port) * See connection_ap_handshake_rewrite_and_attach()'s * documentation for arguments and return value. */ -int -connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath) +MOCK_IMPL(int, +connection_ap_rewrite_and_attach_if_allowed,(entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)) { const or_options_t *options = get_options(); @@ -1222,10 +1237,9 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, /* 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) { + if (!strcmpend(socks->address, ".exit")) { 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)."); + "security risks."); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); out->end_reason = END_STREAM_REASON_TORPROTOCOL; @@ -1391,6 +1405,199 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } } +/** We just received a SOCKS request in <b>conn</b> to an onion address of type + * <b>addresstype</b>. Start connecting to the onion service. */ +static int +connection_ap_handle_onion(entry_connection_t *conn, + socks_request_t *socks, + origin_circuit_t *circ, + hostname_type_t addresstype) +{ + time_t now = approx_time(); + connection_t *base_conn = ENTRY_TO_CONN(conn); + + /* If .onion address requests are disabled, refuse the request */ + if (!conn->entry_cfg.onion_traffic) { + log_warn(LD_APP, "Onion address %s requested from a port with .onion " + "disabled", safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } + + /* 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. */ + log_warn(LD_APP, + "Resolve requests to hidden services not allowed. Failing."); + 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; + } + + /* 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."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + } + + /* Interface: Regardless of HS version after the block below we should have + set onion_address, rend_cache_lookup_result, and descriptor_is_usable. */ + const char *onion_address = NULL; + int rend_cache_lookup_result = -ENOENT; + int descriptor_is_usable = 0; + + if (addresstype == ONION_V2_HOSTNAME) { /* it's a v2 hidden service */ + rend_cache_entry_t *entry = NULL; + /* 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(socks->address); + + const uint8_t *cookie = NULL; + rend_auth_type_t auth_type = REND_NO_AUTH; + if (client_auth) { + log_info(LD_REND, "Using previously configured client authorization " + "for hidden service request."); + auth_type = client_auth->auth_type; + cookie = client_auth->descriptor_cookie; + } + + /* 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 = + rend_data_client_create(socks->address, NULL, (char *) cookie, + auth_type); + if (rend_data == NULL) { + return -1; + } + onion_address = rend_data_get_address(rend_data); + log_info(LD_REND,"Got a hidden service request for ID '%s'", + safe_str_client(onion_address)); + + rend_cache_lookup_result = rend_cache_lookup_entry(onion_address,-1, + &entry); + if (!rend_cache_lookup_result && entry) { + descriptor_is_usable = rend_client_any_intro_points_usable(entry); + } + } else { /* it's a v3 hidden service */ + tor_assert(addresstype == ONION_V3_HOSTNAME); + const hs_descriptor_t *cached_desc = NULL; + int retval; + /* Create HS conn identifier with HS pubkey */ + hs_ident_edge_conn_t *hs_conn_ident = + tor_malloc_zero(sizeof(hs_ident_edge_conn_t)); + + retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk, + NULL, NULL); + if (retval < 0) { + log_warn(LD_GENERAL, "failed to parse hs address"); + tor_free(hs_conn_ident); + return -1; + } + ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident; + + onion_address = socks->address; + + /* Check the v3 desc cache */ + cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk); + if (cached_desc) { + rend_cache_lookup_result = 0; + descriptor_is_usable = + hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk, + cached_desc); + log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.", + (descriptor_is_usable) ? "usable" : "unusable", + safe_str_client(onion_address), + (descriptor_is_usable) ? "Not fetching." : "Refecting."); + } else { + rend_cache_lookup_result = -ENOENT; + } + } + + /* Lookup the given onion address. If invalid, stop right now. + * Otherwise, we might have it in the cache or not. */ + unsigned int refetch_desc = 0; + if (rend_cache_lookup_result < 0) { + switch (-rend_cache_lookup_result) { + case EINVAL: + /* We should already have rejected this address! */ + log_warn(LD_BUG,"Invalid service name '%s'", + safe_str_client(onion_address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + case ENOENT: + /* We didn't have this; we should look it up. */ + log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.", + safe_str_client(onion_address)); + refetch_desc = 1; + break; + default: + log_warn(LD_BUG, "Unknown cache lookup error %d", + rend_cache_lookup_result); + return -1; + } + } + + /* Help predict that we'll want to do hidden service circuits in the + * future. We're not sure if it will need a stable circuit yet, but + * we know we'll need *something*. */ + rep_hist_note_used_internal(now, 0, 1); + + /* Now we have a descriptor but is it usable or not? If not, refetch. + * Also, a fetch could have been requested if the onion address was not + * found in the cache previously. */ + if (refetch_desc || !descriptor_is_usable) { + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + connection_ap_mark_as_non_pending_circuit(conn); + base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; + if (addresstype == ONION_V2_HOSTNAME) { + tor_assert(edge_conn->rend_data); + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + /* Whatever the result of the refetch, we don't go further. */ + return 0; + } else { + tor_assert(addresstype == ONION_V3_HOSTNAME); + tor_assert(edge_conn->hs_ident); + /* Attempt to fetch the hsv3 descriptor. Check the retval to see how it + * went and act accordingly. */ + int ret = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + switch (ret) { + case HS_CLIENT_FETCH_MISSING_INFO: + /* Keeping the connection in descriptor wait state is fine because + * once we get enough dirinfo or a new live consensus, the HS client + * subsystem is notified and every connection in that state will + * trigger a fetch for the service key. */ + case HS_CLIENT_FETCH_LAUNCHED: + case HS_CLIENT_FETCH_PENDING: + case HS_CLIENT_FETCH_HAVE_DESC: + return 0; + case HS_CLIENT_FETCH_ERROR: + case HS_CLIENT_FETCH_NO_HSDIRS: + case HS_CLIENT_FETCH_NOT_ALLOWED: + /* Can't proceed further and better close the SOCKS request. */ + return -1; + } + } + } + + /* We have the descriptor! So launch a connection to the HS. */ + log_info(LD_REND, "Descriptor is here. Great."); + + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + /* We'll try to attach it at the next event loop, or whenever + * we call connection_ap_attach_pending() */ + connection_ap_mark_as_pending_circuit(conn); + return 0; +} + /** 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 @@ -1466,23 +1673,23 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, 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. */ - log_warn(LD_APP,"Stale automapped address for '%s.exit', with " - "AllowDotExit disabled. Refusing.", + * a user. That's not safe. */ + if (exit_source == ADDRMAPSRC_AUTOMAP) { + /* Whoops; this one is stale. It must have gotten added earlier? + * (Probably this is not possible, since AllowDotExit no longer + * exists.) */ + log_warn(LD_APP,"Stale automapped address for '%s.exit'. Refusing.", safe_str_client(socks->address)); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + tor_assert_nonfatal_unreached(); 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)) { + if (exit_source == ADDRMAPSRC_DNS || exit_source == ADDRMAPSRC_NONE) { /* It shouldn't be possible to get a .exit address from any of these * sources. */ log_warn(LD_BUG,"Address '%s.exit', with impossible source for the " @@ -1507,7 +1714,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, 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); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (exit_source == ADDRMAPSRC_TRACKEXIT) { /* We 5 tries before it expires the addressmap */ @@ -1528,7 +1735,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, * 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); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (node) { *socks->address = 0; node_get_address_string(node, socks->address, sizeof(socks->address)); @@ -1557,7 +1764,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* Now, we handle everything that isn't a .onion address. */ - if (addresstype != ONION_HOSTNAME) { + if (addresstype != ONION_V2_HOSTNAME && addresstype != ONION_V3_HOSTNAME) { /* Not a hidden-service request. It's either a hostname or an IP, * possibly with a .exit that we stripped off. We're going to check * if we're allowed to connect/resolve there, and then launch the @@ -1584,7 +1791,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* socks->address is a non-onion hostname or IP address. * If we can't do any non-onion requests, refuse the connection. @@ -1835,116 +2042,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return 0; } else { /* If we get here, it's a request for a .onion address! */ + tor_assert(addresstype == ONION_V2_HOSTNAME || + addresstype == ONION_V3_HOSTNAME); tor_assert(!automap); - - /* If .onion address requests are disabled, refuse the request */ - if (!conn->entry_cfg.onion_traffic) { - log_warn(LD_APP, "Onion address %s requested from a port with .onion " - "disabled", safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; - } - - /* 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. */ - log_warn(LD_APP, - "Resolve requests to hidden services not allowed. Failing."); - 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; - } - - /* 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."); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - } - - /* 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(socks->address); - - const uint8_t *cookie = NULL; - rend_auth_type_t auth_type = REND_NO_AUTH; - if (client_auth) { - log_info(LD_REND, "Using previously configured client authorization " - "for hidden service request."); - auth_type = client_auth->auth_type; - cookie = client_auth->descriptor_cookie; - } - - /* 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 = - rend_data_client_create(socks->address, NULL, (char *) cookie, - auth_type); - if (rend_data == NULL) { - return -1; - } - const char *onion_address = rend_data_get_address(rend_data); - log_info(LD_REND,"Got a hidden service request for ID '%s'", - safe_str_client(onion_address)); - - /* Lookup the given onion address. If invalid, stop right now. - * Otherwise, we might have it in the cache or not. */ - unsigned int refetch_desc = 0; - rend_cache_entry_t *entry = NULL; - const int rend_cache_lookup_result = - rend_cache_lookup_entry(onion_address, -1, &entry); - if (rend_cache_lookup_result < 0) { - switch (-rend_cache_lookup_result) { - case EINVAL: - /* We should already have rejected this address! */ - log_warn(LD_BUG,"Invalid service name '%s'", - safe_str_client(onion_address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - case ENOENT: - /* We didn't have this; we should look it up. */ - refetch_desc = 1; - break; - default: - log_warn(LD_BUG, "Unknown cache lookup error %d", - rend_cache_lookup_result); - return -1; - } - } - - /* Help predict that we'll want to do hidden service circuits in the - * future. We're not sure if it will need a stable circuit yet, but - * we know we'll need *something*. */ - rep_hist_note_used_internal(now, 0, 1); - - /* Now we have a descriptor but is it usable or not? If not, refetch. - * Also, a fetch could have been requested if the onion address was not - * found in the cache previously. */ - if (refetch_desc || !rend_client_any_intro_points_usable(entry)) { - connection_ap_mark_as_non_pending_circuit(conn); - base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; - log_info(LD_REND, "Unknown descriptor %s. Fetching.", - safe_str_client(onion_address)); - rend_client_refetch_v2_renddesc(rend_data); - return 0; - } - - /* We have the descriptor! So launch a connection to the HS. */ - base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - log_info(LD_REND, "Descriptor is here. Great."); - - /* We'll try to attach it at the next event loop, or whenever - * we call connection_ap_attach_pending() */ - connection_ap_mark_as_pending_circuit(conn); - return 0; + return connection_ap_handle_onion(conn, socks, circ, addresstype); } return 0; /* unreached but keeps the compiler happy */ @@ -1966,7 +2067,7 @@ get_pf_socket(void) #else /* works on NetBSD and FreeBSD */ pf = tor_open_cloexec("/dev/pf", O_RDWR, 0); -#endif +#endif /* defined(OpenBSD) */ if (pf < 0) { log_warn(LD_NET, "open(\"/dev/pf\") failed: %s", strerror(errno)); @@ -1976,9 +2077,10 @@ get_pf_socket(void) pf_socket = pf; return pf_socket; } -#endif +#endif /* defined(TRANS_PF) */ -#if defined(TRANS_NETFILTER) || defined(TRANS_PF) || defined(TRANS_TPROXY) +#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 @@ -1998,7 +2100,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) } goto done; } -#endif +#endif /* defined(TRANS_TPROXY) */ #ifdef TRANS_NETFILTER int rv = -1; @@ -2008,13 +2110,13 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) rv = getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*)&orig_dst, &orig_dst_len); break; -#endif +#endif /* defined(TRANS_NETFILTER_IPV4) */ #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 +#endif /* defined(TRANS_NETFILTER_IPV6) */ default: log_warn(LD_BUG, "Received transparent data from an unsuported socket family %d", @@ -2040,7 +2142,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) (void)req; log_warn(LD_BUG, "Unable to determine destination from socket."); return -1; -#endif +#endif /* defined(TRANS_NETFILTER) || ... */ done: tor_addr_from_sockaddr(&addr, (struct sockaddr*)&orig_dst, &req->port); @@ -2048,7 +2150,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(TRANS_NETFILTER) || defined(TRANS_PF) || ... */ #ifdef TRANS_PF static int @@ -2082,7 +2184,7 @@ destination_from_pf(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(__FreeBSD__) */ memset(&pnl, 0, sizeof(pnl)); pnl.proto = IPPROTO_TCP; @@ -2131,7 +2233,7 @@ destination_from_pf(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(TRANS_PF) */ /** Fetch the original destination address and port from a * system-specific interface and put them into a @@ -2167,7 +2269,7 @@ connection_ap_get_original_destination(entry_connection_t *conn, log_warn(LD_BUG, "Called connection_ap_get_original_destination, but no " "transparent proxy method was configured."); return -1; -#endif +#endif /* defined(TRANS_NETFILTER) || ... */ } /** connection_edge_process_inbuf() found a conn in state @@ -2202,7 +2304,7 @@ connection_ap_handshake_process_socks(entry_connection_t *conn) if (socks->replylen) { had_reply = 1; - connection_write_to_buf((const char*)socks->reply, socks->replylen, + connection_buf_add((const char*)socks->reply, socks->replylen, base_conn); socks->replylen = 0; if (sockshere == -1) { @@ -2299,7 +2401,7 @@ connection_ap_process_natd(entry_connection_t *conn) /* look for LF-terminated "[DEST ip_addr port]" * where ip_addr is a dotted-quad and port is in string form */ - err = connection_fetch_from_buf_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); + err = connection_buf_get_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); if (err == 0) return 0; if (err < 0) { @@ -2348,6 +2450,108 @@ connection_ap_process_natd(entry_connection_t *conn) return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); } +/** Called on an HTTP CONNECT entry connection when some bytes have arrived, + * but we have not yet received a full HTTP CONNECT request. Try to parse an + * HTTP CONNECT request from the connection's inbuf. On success, set up the + * connection's socks_request field and try to attach the connection. On + * failure, send an HTTP reply, and mark the connection. + */ +STATIC int +connection_ap_process_http_connect(entry_connection_t *conn) +{ + if (BUG(ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_HTTP_CONNECT_WAIT)) + return -1; + + char *headers = NULL, *body = NULL; + char *command = NULL, *addrport = NULL; + char *addr = NULL; + size_t bodylen = 0; + + const char *errmsg = NULL; + int rv = 0; + + const int http_status = + fetch_from_buf_http(ENTRY_TO_CONN(conn)->inbuf, &headers, 8192, + &body, &bodylen, 1024, 0); + if (http_status < 0) { + /* Bad http status */ + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } else if (http_status == 0) { + /* no HTTP request yet. */ + goto done; + } + + const int cmd_status = parse_http_command(headers, &command, &addrport); + if (cmd_status < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + tor_assert(command); + tor_assert(addrport); + if (strcasecmp(command, "connect")) { + errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; + goto err; + } + + tor_assert(conn->socks_request); + socks_request_t *socks = conn->socks_request; + uint16_t port; + if (tor_addr_port_split(LOG_WARN, addrport, &addr, &port) < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + if (strlen(addr) >= MAX_SOCKS_ADDR_LEN) { + errmsg = "HTTP/1.0 414 Request-URI Too Long\r\n\r\n"; + goto err; + } + + /* Abuse the 'username' and 'password' fields here. They are already an + * abuse. */ + { + char *authorization = http_get_header(headers, "Proxy-Authorization: "); + if (authorization) { + socks->username = authorization; // steal reference + socks->usernamelen = strlen(authorization); + } + char *isolation = http_get_header(headers, "X-Tor-Stream-Isolation: "); + if (isolation) { + socks->password = isolation; // steal reference + socks->passwordlen = strlen(isolation); + } + } + + socks->command = SOCKS_COMMAND_CONNECT; + socks->listener_type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; + strlcpy(socks->address, addr, sizeof(socks->address)); + socks->port = port; + + control_event_stream_status(conn, STREAM_EVENT_NEW, 0); + + rv = connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); + + // XXXX send a "100 Continue" message? + + goto done; + + err: + if (BUG(errmsg == NULL)) + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + log_warn(LD_EDGE, "Saying %s", escaped(errmsg)); + connection_buf_add(errmsg, strlen(errmsg), ENTRY_TO_CONN(conn)); + connection_mark_unattached_ap(conn, + END_STREAM_REASON_HTTPPROTOCOL| + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + + done: + tor_free(headers); + tor_free(body); + tor_free(command); + tor_free(addrport); + tor_free(addr); + return rv; +} + /** Iterate over the two bytes of stream_id until we get one that is not * already in use; return it. Return 0 if can't get a unique stream_id. */ @@ -2455,8 +2659,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) * * If ap_conn is broken, mark it for close and return -1. Else return 0. */ -int -connection_ap_handshake_send_begin(entry_connection_t *ap_conn) +MOCK_IMPL(int, +connection_ap_handshake_send_begin,(entry_connection_t *ap_conn)) { char payload[CELL_PAYLOAD_SIZE]; int payload_len; @@ -2967,15 +3171,22 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, return; } if (replylen) { /* we already have a reply in mind */ - connection_write_to_buf(reply, replylen, ENTRY_TO_CONN(conn)); + connection_buf_add(reply, replylen, ENTRY_TO_CONN(conn)); conn->socks_request->has_finished = 1; return; } - if (conn->socks_request->socks_version == 4) { + if (conn->socks_request->listener_type == + CONN_TYPE_AP_HTTP_CONNECT_LISTENER) { + const char *response = end_reason_to_http_connect_response_line(endreason); + if (!response) { + response = "HTTP/1.0 400 Bad Request\r\n\r\n"; + } + connection_buf_add(response, strlen(response), ENTRY_TO_CONN(conn)); + } else if (conn->socks_request->socks_version == 4) { memset(buf,0,SOCKS4_NETWORK_LEN); buf[1] = (status==SOCKS5_SUCCEEDED ? SOCKS4_GRANTED : SOCKS4_REJECT); /* leave version, destport, destip zero */ - connection_write_to_buf(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); + connection_buf_add(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); } else if (conn->socks_request->socks_version == 5) { size_t buf_len; memset(buf,0,sizeof(buf)); @@ -2994,7 +3205,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, /* 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)); + connection_buf_add(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. */ @@ -3007,7 +3218,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, * <0 and set *<b>end_reason_out</b> to the end reason we should send back to * the client. * - * Return -1 in the case where want to send a RELAY_END cell, and < -1 when + * Return -1 in the case where we want to send a RELAY_END cell, and < -1 when * we don't. **/ STATIC int @@ -3066,6 +3277,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, return 0; } +/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the + * connection, attach it to the circ and connect it. Return 0 on success + * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port + * where the caller should close the circuit. */ +static int +handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) +{ + int ret; + origin_circuit_t *origin_circ; + + assert_circuit_ok(circ); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(conn); + + log_debug(LD_REND, "Connecting the hidden service rendezvous circuit " + "to the service destination."); + + origin_circ = TO_ORIGIN_CIRCUIT(circ); + conn->base_.address = tor_strdup("(rendezvous)"); + conn->base_.state = EXIT_CONN_STATE_CONNECTING; + + /* The circuit either has an hs identifier for v3+ or a rend_data for legacy + * service. */ + if (origin_circ->rend_data) { + conn->rend_data = rend_data_dup(origin_circ->rend_data); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = rend_service_set_connection_addr_port(conn, origin_circ); + } else if (origin_circ->hs_ident) { + /* Setup the identifier to be the one for the circuit service. */ + conn->hs_ident = + hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = hs_service_set_conn_addr_port(origin_circ, conn); + } else { + /* We should never get here if the circuit's purpose is rendezvous. */ + tor_assert_nonfatal_unreached(); + return -1; + } + if (ret < 0) { + log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)", + fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->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(conn->stream_id, circ, + END_STREAM_REASON_DONE, + origin_circ->cpath->prev); + connection_free(TO_CONN(conn)); + + /* 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 (ret < -1) { + return END_CIRC_AT_ORIGIN; + } else { + return 0; + } + } + + /* Link the circuit and the connection crypt path. */ + conn->cpath_layer = origin_circ->cpath->prev; + + /* Add it into the linked list of p_streams on this circuit */ + conn->next_stream = origin_circ->p_streams; + origin_circ->p_streams = conn; + conn->on_circuit = circ; + assert_circuit_ok(circ); + + hs_inc_rdv_stream_counter(origin_circ); + + /* Connect tor to the hidden service destination. */ + connection_exit_connect(conn); + + /* For path bias: This circuit was used successfully */ + pathbias_mark_use_success(origin_circ); + return 0; +} + /** A relay 'begin' or 'begin_dir' cell has arrived, and either we are * an exit hop for the circuit, or we are the origin and it is a * rendezvous begin. @@ -3141,7 +3434,8 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) port = bcell.port; if (or_circ && or_circ->p_chan) { - if ((or_circ->is_first_hop || + const int client_chan = channel_is_client(or_circ->p_chan); + if ((client_chan || (!connection_or_digest_is_known_relay( or_circ->p_chan->identity_digest) && should_refuse_unknown_exits(options)))) { @@ -3151,10 +3445,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Attempt by %s to open a stream %s. Closing.", safe_str(channel_get_canonical_remote_descr(or_circ->p_chan)), - or_circ->is_first_hop ? "on first hop of circuit" : - "from unknown relay"); + client_chan ? "on first hop of circuit" : + "from unknown relay"); relay_send_end_cell_from_edge(rh.stream_id, circ, - or_circ->is_first_hop ? + client_chan ? END_STREAM_REASON_TORPROTOCOL : END_STREAM_REASON_MISC, NULL); @@ -3217,58 +3511,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->deliver_window = STREAMWINDOW_START; if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ); - log_info(LD_REND,"begin is for rendezvous. configuring stream."); - n_stream->base_.address = tor_strdup("(rendezvous)"); - n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; - n_stream->rend_data = rend_data_dup(origin_circ->rend_data); - tor_assert(connection_edge_is_rendezvous_stream(n_stream)); - assert_circuit_ok(circ); - - 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_DONE, - layer_hint); - connection_free(TO_CONN(n_stream)); - tor_free(address); - - /* 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"); - n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */ - - /* add it into the linked list of p_streams on this circuit */ - n_stream->next_stream = origin_circ->p_streams; - n_stream->on_circuit = circ; - origin_circ->p_streams = n_stream; - assert_circuit_ok(circ); - - origin_circ->rend_data->nr_streams++; - - connection_exit_connect(n_stream); - - /* For path bias: This circuit was used successfully */ - pathbias_mark_use_success(origin_circ); - tor_free(address); - return 0; + /* We handle this circuit and stream in this function for all supported + * hidden service version. */ + return handle_hs_exit_conn(circ, n_stream); } tor_strlower(address); n_stream->base_.address = address; @@ -3566,8 +3812,12 @@ int connection_edge_is_rendezvous_stream(const edge_connection_t *conn) { tor_assert(conn); - if (conn->rend_data) + /* It should not be possible to set both of these structs */ + tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident)); + + if (conn->rend_data || conn->hs_ident) { return 1; + } return 0; } @@ -3591,7 +3841,7 @@ connection_ap_can_use_exit(const entry_connection_t *conn, */ if (conn->chosen_exit_name) { const node_t *chosen_exit = - node_get_by_nickname(conn->chosen_exit_name, 1); + node_get_by_nickname(conn->chosen_exit_name, 0); if (!chosen_exit || tor_memneq(chosen_exit->identity, exit_node->identity, DIGEST_LEN)) { /* doesn't match */ @@ -3640,10 +3890,12 @@ connection_ap_can_use_exit(const entry_connection_t *conn, } /** If address is of the form "y.onion" with a well-formed handle y: - * Put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Put a NUL after y, lower-case it, and return ONION_V2_HOSTNAME or + * ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "x.y.onion" with a well-formed handle x: - * Drop "x.", put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Drop "x.", put a NUL after y, lower-case it, and return + * ONION_V2_HOSTNAME or ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "y.onion" with a badly-formed handle y: * Return BAD_HOSTNAME and log a message. @@ -3659,7 +3911,7 @@ parse_extended_hostname(char *address) { char *s; char *q; - char query[REND_SERVICE_ID_LEN_BASE32+1]; + char query[HS_SERVICE_ADDR_LEN_BASE32+1]; s = strrchr(address,'.'); if (!s) @@ -3679,14 +3931,17 @@ parse_extended_hostname(char *address) goto failed; /* reject sub-domain, as DNS does */ } q = (NULL == q) ? address : q + 1; - if (strlcpy(query, q, REND_SERVICE_ID_LEN_BASE32+1) >= - REND_SERVICE_ID_LEN_BASE32+1) + if (strlcpy(query, q, HS_SERVICE_ADDR_LEN_BASE32+1) >= + HS_SERVICE_ADDR_LEN_BASE32+1) goto failed; if (q != address) { memmove(address, q, strlen(q) + 1 /* also get \0 */); } - if (rend_valid_service_id(query)) { - return ONION_HOSTNAME; /* success */ + if (rend_valid_v2_service_id(query)) { + return ONION_V2_HOSTNAME; /* success */ + } + if (hs_address_is_valid(query)) { + return ONION_V3_HOSTNAME; } failed: /* otherwise, return to previous state and return 0 */ diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index e4780b3c7d..c6583d3845 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn); void connection_ap_about_to_close(entry_connection_t *edge_conn); void connection_exit_about_to_close(edge_connection_t *edge_conn); -int connection_ap_handshake_send_begin(entry_connection_t *ap_conn); +MOCK_DECL(int, + connection_ap_handshake_send_begin,(entry_connection_t *ap_conn)); int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn); entry_connection_t *connection_ap_make_link(connection_t *partner, @@ -88,16 +89,18 @@ int connection_ap_process_transparent(entry_connection_t *conn); int address_is_invalid_destination(const char *address, int client); -int connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath); +MOCK_DECL(int, connection_ap_rewrite_and_attach_if_allowed, + (entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)); int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); /** Possible return values for parse_extended_hostname. */ typedef enum hostname_type_t { - NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME + NORMAL_HOSTNAME, ONION_V2_HOSTNAME, ONION_V3_HOSTNAME, + EXIT_HOSTNAME, BAD_HOSTNAME } hostname_type_t; hostname_type_t parse_extended_hostname(char *address); @@ -186,7 +189,9 @@ typedef struct { STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn, rewrite_result_t *out); -#endif -#endif +STATIC int connection_ap_process_http_connect(entry_connection_t *conn); +#endif /* defined(CONNECTION_EDGE_PRIVATE) */ + +#endif /* !defined(TOR_CONNECTION_EDGE_H) */ diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 59a9bdffda..9e34063609 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -46,6 +46,7 @@ #include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" +#include "proto_cell.h" #include "reasons.h" #include "relay.h" #include "rephist.h" @@ -472,7 +473,7 @@ var_cell_pack_header(const var_cell_t *cell, char *hdr_out, int wide_circ_ids) var_cell_t * var_cell_new(uint16_t payload_len) { - size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len; + size_t size = offsetof(var_cell_t, payload) + payload_len; var_cell_t *cell = tor_malloc_zero(size); cell->payload_len = payload_len; cell->command = 0; @@ -491,7 +492,7 @@ var_cell_copy(const var_cell_t *src) size_t size = 0; if (src != NULL) { - size = STRUCT_OFFSET(var_cell_t, payload) + src->payload_len; + size = offsetof(var_cell_t, payload) + src->payload_len; copy = tor_malloc_zero(size); copy->payload_len = src->payload_len; copy->command = src->command; @@ -726,7 +727,7 @@ connection_or_about_to_close(or_connection_t *or_conn) control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED, reason); if (!authdir_mode_tests_reachability(options)) - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( orconn_end_reason_to_control_string(reason), reason, or_conn); } @@ -1112,7 +1113,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, conn); + control_event_bootstrap_prob_or(msg, reason, conn); } /** <b>conn</b> got an error in connection_handle_read_impl() or @@ -1235,7 +1236,7 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port, fmt_addrport(&TO_CONN(conn)->addr, TO_CONN(conn)->port), transport_name, transport_name); - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( "Can't connect to bridge", END_OR_CONN_REASON_PT_MISSING, conn); @@ -1369,7 +1370,6 @@ connection_tls_start_handshake,(or_connection_t *conn, int receiving)) connection_start_reading(TO_CONN(conn)); log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT, conn->base_.s); - note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C); if (connection_tls_continue_handshake(conn) < 0) return -1; @@ -1550,6 +1550,7 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, if (identity_rcvd) { if (crypto_pk_get_digest(identity_rcvd, digest_rcvd_out) < 0) { + crypto_pk_free(identity_rcvd); return -1; } } else { @@ -1713,7 +1714,7 @@ connection_or_client_learned_peer_id(or_connection_t *conn, control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, END_OR_CONN_REASON_OR_IDENTITY); if (!authdir_mode_tests_reachability(options)) - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( "Unexpected identity in router certificate", END_OR_CONN_REASON_OR_IDENTITY, conn); @@ -1742,8 +1743,9 @@ connection_or_client_learned_peer_id(or_connection_t *conn, return 0; } -/** Return when a client used this, for connection.c, since client_used - * is now one of the timestamps of channel_t */ +/** Return when we last used this channel for client activity (origin + * circuits). This is called from connection.c, since client_used is now one + * of the timestamps in channel_t */ time_t connection_or_client_used(or_connection_t *conn) @@ -1757,7 +1759,7 @@ connection_or_client_used(or_connection_t *conn) /** The v1/v2 TLS handshake is finished. * - * Make sure we are happy with the person we just handshaked with. + * Make sure we are happy with the peer we just handshaked with. * * If they initiated the connection, make sure they're not already connected, * then initialize conn from the information in router. @@ -1978,7 +1980,7 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn) if (cell->command == CELL_PADDING) rep_hist_padding_count_write(PADDING_TYPE_CELL); - connection_write_to_buf(networkcell.body, cell_network_size, TO_CONN(conn)); + connection_buf_add(networkcell.body, cell_network_size, TO_CONN(conn)); /* Touch the channel's active timestamp if there is one */ if (conn->chan) { @@ -2008,8 +2010,8 @@ connection_or_write_var_cell_to_buf,(const var_cell_t *cell, tor_assert(cell); tor_assert(conn); n = var_cell_pack_header(cell, hdr, conn->wide_circ_ids); - connection_write_to_buf(hdr, n, TO_CONN(conn)); - connection_write_to_buf((char*)cell->payload, + connection_buf_add(hdr, n, TO_CONN(conn)); + connection_buf_add((char*)cell->payload, 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); @@ -2084,7 +2086,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); circuit_build_times_network_is_live(get_circuit_build_times_mutable()); - connection_fetch_from_buf(buf, cell_network_size, TO_CONN(conn)); + connection_buf_get_bytes(buf, cell_network_size, TO_CONN(conn)); /* retrieve cell info from buf (create the host-order struct from the * network-order string) */ diff --git a/src/or/connection_or.h b/src/or/connection_or.h index fe85a3f5fd..ee66b7c528 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -118,5 +118,5 @@ void connection_or_group_set_badness_(smartlist_t *group, int force); extern int certs_cell_ed25519_disabled_for_testing; #endif -#endif +#endif /* !defined(TOR_CONNECTION_OR_H) */ diff --git a/src/or/conscache.c b/src/or/conscache.c index 33a5495974..1a15126f97 100644 --- a/src/or/conscache.c +++ b/src/or/conscache.c @@ -15,7 +15,7 @@ * changes throughout our logic. */ #define MUST_UNMAP_TO_UNLINK -#endif +#endif /* defined(_WIN32) */ /** * A consensus_cache_entry_t is a reference-counted handle to an @@ -90,11 +90,11 @@ consensus_cache_open(const char *subdir, int max_entries) */ #define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; -#else +#else /* !(defined(MUST_UNMAP_TO_UNLINK)) */ /* Otherwise, we can just tell the storagedir to use the same limits * as this cache. */ storagedir_max_entries = max_entries; -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ cache->dir = storage_dir_new(directory, storagedir_max_entries); tor_free(directory); @@ -145,7 +145,7 @@ consensus_cache_register_with_sandbox(consensus_cache_t *cache, * conditional. */ tor_assert_nonfatal_unreached(); -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ return storage_dir_register_with_sandbox(cache->dir, cfg); } @@ -474,7 +474,7 @@ consensus_cache_get_n_filenames_available(consensus_cache_t *cache) return 0; #else tor_assert_nonfatal(max >= used); -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ return max - used; } @@ -495,7 +495,7 @@ consensus_cache_delete_pending(consensus_cache_t *cache, int force) if (ent->map) { force_ent = 0; } -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ if (! force_ent) { if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) { /* Somebody is using this entry right now */ @@ -622,5 +622,5 @@ consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent) return 0; } } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/conscache.h b/src/or/conscache.h index a0d74c4e08..3c89dedf43 100644 --- a/src/or/conscache.h +++ b/src/or/conscache.h @@ -58,5 +58,5 @@ int consensus_cache_entry_get_body(const consensus_cache_entry_t *ent, int consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent); #endif -#endif +#endif /* !defined(TOR_CONSCACHE_H) */ diff --git a/src/or/consdiff.h b/src/or/consdiff.h index d05df74b75..eb772c0b2b 100644 --- a/src/or/consdiff.h +++ b/src/or/consdiff.h @@ -92,7 +92,7 @@ MOCK_DECL(STATIC int, MOCK_DECL(STATIC int, consensus_digest_eq,(const uint8_t *d1, const uint8_t *d2)); -#endif +#endif /* defined(CONSDIFF_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONSDIFF_H) */ diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c index c5f55b6f3f..e539b61484 100644 --- a/src/or/consdiffmgr.c +++ b/src/or/consdiffmgr.c @@ -663,7 +663,7 @@ consdiffmgr_find_diff_from(consensus_cache_entry_t **entry_out, smartlist_free(matches); return result; -#endif +#endif /* 0 */ } /** diff --git a/src/or/consdiffmgr.h b/src/or/consdiffmgr.h index 079f9fe2d2..df569c8e23 100644 --- a/src/or/consdiffmgr.h +++ b/src/or/consdiffmgr.h @@ -68,7 +68,7 @@ STATIC int cdm_entry_get_sha3_value(uint8_t *digest_out, const char *label); STATIC int uncompress_or_copy(char **out, size_t *outlen, consensus_cache_entry_t *ent); -#endif +#endif /* defined(CONSDIFFMGR_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONSDIFFMGR_H) */ diff --git a/src/or/control.c b/src/or/control.c index 1837d49025..202366aaec 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -60,9 +60,12 @@ #include "hibernate.h" #include "hs_common.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "proto_control0.h" +#include "proto_http.h" #include "reasons.h" #include "rendclient.h" #include "rendcommon.h" @@ -354,7 +357,7 @@ static inline void connection_write_str_to_buf(const char *s, control_connection_t *conn) { size_t len = strlen(s); - connection_write_to_buf(s, len, TO_CONN(conn)); + connection_buf_add(s, len, TO_CONN(conn)); } /** Given a <b>len</b>-character string in <b>data</b>, made of lines @@ -367,16 +370,23 @@ connection_write_str_to_buf(const char *s, control_connection_t *conn) STATIC size_t write_escaped_data(const char *data, size_t len, char **out) { - size_t sz_out = len+8; + tor_assert(len < SIZE_MAX - 9); + size_t sz_out = len+8+1; char *outp; const char *start = data, *end; - int i; + size_t i; int start_of_line; - for (i=0; i<(int)len; ++i) { - if (data[i]== '\n') + for (i=0; i < len; ++i) { + if (data[i] == '\n') { sz_out += 2; /* Maybe add a CR; maybe add a dot. */ + if (sz_out >= SIZE_T_CEILING) { + log_warn(LD_BUG, "Input to write_escaped_data was too long"); + *out = tor_strdup(".\r\n"); + return 3; + } + } } - *out = outp = tor_malloc(sz_out+1); + *out = outp = tor_malloc(sz_out); end = data+len; start_of_line = 1; while (data < end) { @@ -402,7 +412,8 @@ write_escaped_data(const char *data, size_t len, char **out) *outp++ = '\r'; *outp++ = '\n'; *outp = '\0'; /* NUL-terminate just in case. */ - tor_assert((outp - *out) <= (int)sz_out); + tor_assert(outp >= *out); + tor_assert((size_t)(outp - *out) <= sz_out); return outp - *out; } @@ -556,7 +567,7 @@ connection_printf_to_buf(control_connection_t *conn, const char *format, ...) tor_assert(0); } - connection_write_to_buf(buf, (size_t)len, TO_CONN(conn)); + connection_buf_add(buf, (size_t)len, TO_CONN(conn)); tor_free(buf); } @@ -582,7 +593,7 @@ control_ports_write_to_file(void) smartlist_add_asprintf(lines, "UNIX_PORT=%s\n", conn->address); continue; } -#endif +#endif /* defined(AF_UNIX) */ smartlist_add_asprintf(lines, "PORT=%s:%d\n", conn->address, conn->port); } SMARTLIST_FOREACH_END(conn); @@ -599,7 +610,7 @@ control_ports_write_to_file(void) options->ControlPortWriteToFile); } } -#endif +#endif /* !defined(_WIN32) */ tor_free(joined); SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); smartlist_free(lines); @@ -781,7 +792,7 @@ queued_events_flush_all(int force) SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, control_conn) { if (control_conn->event_mask & bit) { - connection_write_to_buf(ev->msg, msg_len, TO_CONN(control_conn)); + connection_buf_add(ev->msg, msg_len, TO_CONN(control_conn)); } } SMARTLIST_FOREACH_END(control_conn); @@ -802,7 +813,7 @@ queued_events_flush_all(int force) } /** Libevent callback: Flushes pending events to controllers that are - * interested in them */ + * interested in them. */ static void flush_queued_events_cb(evutil_socket_t fd, short what, void *arg) { @@ -1063,7 +1074,7 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, tor_assert(strlen(tmp)>4); tmp[3] = ' '; msg = smartlist_join_strings(answers, "", 0, &msg_len); - connection_write_to_buf(msg, msg_len, TO_CONN(conn)); + connection_buf_add(msg, msg_len, TO_CONN(conn)); } else { connection_write_str_to_buf("250 OK\r\n", conn); } @@ -1145,7 +1156,6 @@ static const struct control_event_t control_event_table[] = { { EVENT_ERR_MSG, "ERR" }, { EVENT_NEW_DESC, "NEWDESC" }, { EVENT_ADDRMAP, "ADDRMAP" }, - { EVENT_AUTHDIR_NEWDESCS, "AUTHDIR_NEWDESCS" }, { EVENT_DESCCHANGED, "DESCCHANGED" }, { EVENT_NS, "NS" }, { EVENT_STATUS_GENERAL, "STATUS_GENERAL" }, @@ -1185,7 +1195,10 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(events, const char *, ev) { - if (!strcasecmp(ev, "EXTENDED")) { + if (!strcasecmp(ev, "EXTENDED") || + !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { + log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " + "supported.", ev); continue; } else { int i; @@ -1645,12 +1658,12 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, if (smartlist_len(reply)) { ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; r = smartlist_join_strings(reply, "\r\n", 1, &sz); - connection_write_to_buf(r, sz, TO_CONN(conn)); + connection_buf_add(r, sz, TO_CONN(conn)); tor_free(r); } else { const char *response = "512 syntax error: not enough arguments to mapaddress.\r\n"; - connection_write_to_buf(response, strlen(response), TO_CONN(conn)); + connection_buf_add(response, strlen(response), TO_CONN(conn)); } SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); @@ -1736,7 +1749,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, #else int myUid = geteuid(); tor_asprintf(answer, "%d", myUid); - #endif +#endif /* defined(_WIN32) */ } else if (!strcmp(question, "process/user")) { #ifdef _WIN32 *answer = tor_strdup(""); @@ -1749,7 +1762,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, } else { *answer = tor_strdup(""); } - #endif +#endif /* defined(_WIN32) */ } else if (!strcmp(question, "process/descriptor-limit")) { int max_fds = get_max_sockets(); tor_asprintf(answer, "%d", max_fds); @@ -1885,27 +1898,42 @@ getinfo_helper_dir(control_connection_t *control_conn, (void) control_conn; if (!strcmpstart(question, "desc/id/")) { const routerinfo_t *ri = NULL; - const node_t *node = node_get_by_hex_id(question+strlen("desc/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0); if (node) ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/id/* " + "instead of desc/id/*."; + return 0; } } else if (!strcmpstart(question, "desc/name/")) { const routerinfo_t *ri = NULL; /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ const node_t *node = - node_get_by_nickname(question+strlen("desc/name/"), 1); + node_get_by_nickname(question+strlen("desc/name/"), 0); if (node) ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/name/* " + "instead of desc/name/*."; + return 0; } + } else if (!strcmp(question, "desc/download-enabled")) { + int r = we_fetch_router_descriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmp(question, "desc/all-recent")) { routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_new(); @@ -1975,7 +2003,7 @@ getinfo_helper_dir(control_connection_t *control_conn, return -1; } } else if (!strcmpstart(question, "md/id/")) { - const node_t *node = node_get_by_hex_id(question+strlen("md/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0); const microdesc_t *md = NULL; if (node) md = node->md; if (md && md->body) { @@ -1984,17 +2012,20 @@ getinfo_helper_dir(control_connection_t *control_conn, } else if (!strcmpstart(question, "md/name/")) { /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ - const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 1); + const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0); /* XXXX duplicated code */ const microdesc_t *md = NULL; if (node) md = node->md; if (md && md->body) { *answer = tor_strndup(md->body, md->bodylen); } + } else if (!strcmp(question, "md/download-enabled")) { + int r = we_fetch_microdescriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmpstart(question, "desc-annotations/id/")) { const routerinfo_t *ri = NULL; const node_t *node = - node_get_by_hex_id(question+strlen("desc-annotations/id/")); + node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0); if (node) ri = node->ri; if (ri) { @@ -2127,7 +2158,7 @@ download_status_to_string(const download_status_t *dl) if (dl) { /* Get some substrings of the eventual output ready */ - format_iso_time(tbuf, dl->next_attempt_at); + format_iso_time(tbuf, download_status_get_next_attempt_at(dl)); switch (dl->schedule) { case DL_SCHED_GENERIC: @@ -2907,7 +2938,8 @@ getinfo_helper_sr(control_connection_t *control_conn, * *<b>a</b>. If an internal error occurs, return -1 and optionally set * *<b>error_out</b> to point to an error message to be delivered to the * controller. On success, _or if the key is not recognized_, return 0. Do not - * set <b>a</b> if the key is not recognized. + * set <b>a</b> if the key is not recognized but you may set <b>error_out</b> + * to improve the error message. */ typedef int (*getinfo_helper_t)(control_connection_t *, const char *q, char **a, @@ -3012,9 +3044,13 @@ static const getinfo_item_t getinfo_items[] = { PREFIX("desc/name/", dir, "Router descriptors by nickname."), ITEM("desc/all-recent", dir, "All non-expired, non-superseded router descriptors."), + ITEM("desc/download-enabled", dir, + "Do we try to download router descriptors?"), ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */ PREFIX("md/id/", dir, "Microdescriptors by ID"), PREFIX("md/name/", dir, "Microdescriptors by name"), + ITEM("md/download-enabled", dir, + "Do we try to download microdescriptors?"), PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."), PREFIX("hs/client/desc/id", dir, "Hidden Service descriptor in client's cache by onion."), @@ -3162,7 +3198,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, smartlist_t *questions = smartlist_new(); smartlist_t *answers = smartlist_new(); smartlist_t *unrecognized = smartlist_new(); - char *msg = NULL, *ans = NULL; + char *ans = NULL; int i; (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ @@ -3177,20 +3213,26 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, goto done; } if (!ans) { - smartlist_add(unrecognized, (char*)q); + if (errmsg) /* use provided error message */ + smartlist_add_strdup(unrecognized, errmsg); + else /* use default error message */ + smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q); } else { smartlist_add_strdup(answers, q); smartlist_add(answers, ans); } } SMARTLIST_FOREACH_END(q); + if (smartlist_len(unrecognized)) { + /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */ for (i=0; i < smartlist_len(unrecognized)-1; ++i) connection_printf_to_buf(conn, - "552-Unrecognized key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); + "552-%s\r\n", + (char *)smartlist_get(unrecognized, i)); + connection_printf_to_buf(conn, - "552 Unrecognized key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); + "552 %s\r\n", + (char *)smartlist_get(unrecognized, i)); goto done; } @@ -3206,7 +3248,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, size_t esc_len; esc_len = write_escaped_data(v, strlen(v), &esc); connection_printf_to_buf(conn, "250+%s=\r\n", k); - connection_write_to_buf(esc, esc_len, TO_CONN(conn)); + connection_buf_add(esc, esc_len, TO_CONN(conn)); tor_free(esc); } } @@ -3217,8 +3259,8 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, smartlist_free(answers); SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); smartlist_free(questions); + SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp)); smartlist_free(unrecognized); - tor_free(msg); return 0; } @@ -3364,7 +3406,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, nodes = smartlist_new(); SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { - const node_t *node = node_get_by_nickname(n, 1); + const node_t *node = node_get_by_nickname(n, 0); if (!node) { connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); goto done; @@ -4105,7 +4147,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, /* Extract the first argument (either HSAddress or DescID). */ arg1 = smartlist_get(args, 0); /* Test if it's an HS address without the .onion part. */ - if (rend_valid_service_id(arg1)) { + if (rend_valid_v2_service_id(arg1)) { hsaddress = arg1; } else if (strcmpstart(arg1, v2_str) == 0 && rend_valid_descriptor_id(arg1 + v2_str_len) && @@ -4131,7 +4173,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, const char *server; server = arg + strlen(opt_server); - node = node_get_by_hex_id(server); + node = node_get_by_hex_id(server, 0); if (!node) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", server); @@ -4217,7 +4259,7 @@ handle_control_hspost(control_connection_t *conn, SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { if (!strcasecmpstart(arg, opt_server)) { const char *server = arg + strlen(opt_server); - const node_t *node = node_get_by_hex_id(server); + const node_t *node = node_get_by_hex_id(server, 0); if (!node || !node->rs) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", @@ -4749,7 +4791,7 @@ handle_control_del_onion(control_connection_t *conn, return 0; const char *service_id = smartlist_get(args, 0); - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); goto out; } @@ -4890,6 +4932,38 @@ peek_connection_has_control0_command(connection_t *conn) return peek_buf_has_control0_command(conn->inbuf); } +static int +peek_connection_has_http_command(connection_t *conn) +{ + return peek_buf_has_http_command(conn->inbuf); +} + +static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy" + "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>Tor's ControlPort is not an HTTP proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>Tor's ControlPort is not an HTTP proxy</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use Tor's control port" + " as an HTTP proxy.\n" + "This is not correct: Tor's default SOCKS proxy port is 9050.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "<!-- Plus this comment, to make the body response more than 512 bytes, so " + " IE will be willing to display it. Comment comment comment comment " + " comment comment comment comment comment comment comment comment.-->\n" + "</p>\n" + "</body>\n" + "</html>\n"; + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -4923,12 +4997,21 @@ connection_control_process_inbuf(control_connection_t *conn) sizeof(buf)-6); body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ set_uint16(buf+0, htons(body_len)); - connection_write_to_buf(buf, 4+body_len, TO_CONN(conn)); + connection_buf_add(buf, 4+body_len, TO_CONN(conn)); connection_mark_and_flush(TO_CONN(conn)); return 0; } + /* If the user has the HTTP proxy port and the control port confused. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_http_command(TO_CONN(conn))) { + connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); + log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); + connection_mark_and_flush(TO_CONN(conn)); + return 0; + } + again: while (1) { size_t last_idx; @@ -4936,7 +5019,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* First, fetch a line. */ do { data_len = conn->incoming_cmd_len - conn->incoming_cmd_cur_len; - r = connection_fetch_from_buf_line(TO_CONN(conn), + r = connection_buf_get_line(TO_CONN(conn), conn->incoming_cmd+conn->incoming_cmd_cur_len, &data_len); if (r == 0) @@ -5504,15 +5587,20 @@ control_event_stream_bandwidth(edge_connection_t *edge_conn) { circuit_t *circ; origin_circuit_t *ocirc; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { if (!edge_conn->n_read && !edge_conn->n_written) return 0; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", + "650 STREAM_BW "U64_FORMAT" %lu %lu %s\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written); + (unsigned long)edge_conn->n_written, + tbuf); circ = circuit_get_by_edge_conn(edge_conn); if (circ && CIRCUIT_IS_ORIGIN(circ)) { @@ -5534,6 +5622,8 @@ control_event_stream_bandwidth_used(void) if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { smartlist_t *conns = get_connection_array(); edge_connection_t *edge_conn; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { @@ -5543,11 +5633,14 @@ control_event_stream_bandwidth_used(void) if (!edge_conn->n_read && !edge_conn->n_written) continue; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", + "650 STREAM_BW "U64_FORMAT" %lu %lu %s\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written); + (unsigned long)edge_conn->n_written, + tbuf); edge_conn->n_written = edge_conn->n_read = 0; } @@ -5563,6 +5656,8 @@ int control_event_circ_bandwidth_used(void) { origin_circuit_t *ocirc; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) return 0; @@ -5572,11 +5667,15 @@ control_event_circ_bandwidth_used(void) ocirc = TO_ORIGIN_CIRCUIT(circ); if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) continue; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_CIRC_BANDWIDTH_USED, - "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu\r\n", + "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu " + "TIME=%s\r\n", ocirc->global_identifier, (unsigned long)ocirc->n_read_circ_bw, - (unsigned long)ocirc->n_written_circ_bw); + (unsigned long)ocirc->n_written_circ_bw, + tbuf); ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; } SMARTLIST_FOREACH_END(circ); @@ -5747,7 +5846,7 @@ control_event_circuit_cell_stats(void) if (!get_options()->TestingEnableCellStatsEvent || !EVENT_IS_INTERESTING(EVENT_CELL_STATS)) return 0; - cell_stats = tor_malloc(sizeof(cell_stats_t));; + cell_stats = tor_malloc(sizeof(cell_stats_t)); SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circ->testing_cell_stats) continue; @@ -5982,47 +6081,6 @@ control_event_address_mapped(const char *from, const char *to, time_t expires, return 0; } -/** The authoritative dirserver has received a new descriptor that - * has passed basic syntax checks and is properly self-signed. - * - * Notify any interested party of the new descriptor and what has - * been done with it, and also optionally give an explanation/reason. */ -int -control_event_or_authdir_new_descriptor(const char *action, - const char *desc, size_t desclen, - const char *msg) -{ - char firstline[1024]; - char *buf; - size_t totallen; - char *esc = NULL; - size_t esclen; - - if (!EVENT_IS_INTERESTING(EVENT_AUTHDIR_NEWDESCS)) - return 0; - - tor_snprintf(firstline, sizeof(firstline), - "650+AUTHDIR_NEWDESC=\r\n%s\r\n%s\r\n", - action, - msg ? msg : ""); - - /* Escape the server descriptor properly */ - esclen = write_escaped_data(desc, desclen, &esc); - - totallen = strlen(firstline) + esclen + 1; - buf = tor_malloc(totallen); - strlcpy(buf, firstline, totallen); - strlcpy(buf+strlen(firstline), esc, totallen); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - buf); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - "650 OK\r\n"); - tor_free(esc); - tor_free(buf); - - return 0; -} - /** Cached liveness for network liveness events and GETINFO */ @@ -6712,80 +6770,112 @@ control_event_bootstrap(bootstrap_status_t status, int progress) } /** 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>or_conn</b> - * is the connection that caused this problem. + * that indicates a problem. <b>warn</b> gives a human-readable hint + * as to why, and <b>reason</b> provides a controller-facing short + * tag. <b>conn</b> is the connection that caused this problem and + * can be NULL if a connection cannot be easily identified. */ -MOCK_IMPL(void, -control_event_bootstrap_problem, (const char *warn, int reason, - or_connection_t *or_conn)) +void +control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn) { int status = bootstrap_percent; const char *tag = "", *summary = ""; char buf[BOOTSTRAP_MSG_LEN]; const char *recommendation = "ignore"; int severity; + char *or_id = NULL, *hostaddr = NULL; + or_connection_t *or_conn = NULL; /* 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. */ bootstrap_problems++; if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD) - recommendation = "warn"; - - if (reason == END_OR_CONN_REASON_NO_ROUTE) - recommendation = "warn"; - - /* 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"; + dowarn = 1; if (we_are_hibernating()) - recommendation = "ignore"; + dowarn = 0; while (status>=0 && bootstrap_status_to_string(status, &tag, &summary) < 0) status--; /* find a recognized status string based on current progress */ status = bootstrap_percent; /* set status back to the actual number */ - severity = !strcmp(recommendation, "warn") ? LOG_WARN : LOG_INFO; + severity = dowarn ? LOG_WARN : LOG_INFO; + + if (dowarn) + recommendation = "warn"; + + if (conn && conn->type == CONN_TYPE_OR) { + /* XXX TO_OR_CONN can't deal with const */ + or_conn = TO_OR_CONN((connection_t *)conn); + or_id = tor_strdup(hex_str(or_conn->identity_digest, DIGEST_LEN)); + } else { + or_id = tor_strdup("?"); + } + + if (conn) + tor_asprintf(&hostaddr, "%s:%d", conn->address, (int)conn->port); + else + hostaddr = tor_strdup("?"); log_fn(severity, LD_CONTROL, "Problem bootstrapping. Stuck at %d%%: %s. (%s; %s; " - "count %d; recommendation %s; host %s at %s:%d)", - status, summary, warn, - orconn_end_reason_to_control_string(reason), + "count %d; recommendation %s; host %s at %s)", + status, summary, warn, reason, bootstrap_problems, recommendation, - hex_str(or_conn->identity_digest, DIGEST_LEN), - or_conn->base_.address, - or_conn->base_.port); + or_id, hostaddr); 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 HOSTID=\"%s\" HOSTADDR=\"%s:%d\"", - bootstrap_percent, tag, summary, warn, - orconn_end_reason_to_control_string(reason), bootstrap_problems, + "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s\"", + bootstrap_percent, tag, summary, warn, reason, bootstrap_problems, recommendation, - hex_str(or_conn->identity_digest, DIGEST_LEN), - or_conn->base_.address, - (int)or_conn->base_.port); + or_id, hostaddr); tor_snprintf(last_sent_bootstrap_message, sizeof(last_sent_bootstrap_message), "WARN %s", buf); control_event_client_status(LOG_WARN, "%s", buf); + + tor_free(hostaddr); + tor_free(or_id); +} + +/** 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>or_conn</b> + * is the connection that caused this problem. + */ +MOCK_IMPL(void, +control_event_bootstrap_prob_or, (const char *warn, int reason, + or_connection_t *or_conn)) +{ + int dowarn = 0; + + if (or_conn->have_noted_bootstrap_problem) + return; + + or_conn->have_noted_bootstrap_problem = 1; + + if (reason == END_OR_CONN_REASON_NO_ROUTE) + dowarn = 1; + + /* 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)) + dowarn = 1; + + control_event_bootstrap_problem(warn, + orconn_end_reason_to_control_string(reason), + TO_CONN(or_conn), dowarn); } /** We just generated a new summary of which countries we've seen clients @@ -7194,7 +7284,7 @@ control_event_hs_descriptor_upload_failed(const char *id_digest, id_digest); return; } - control_event_hs_descriptor_upload_end("UPLOAD_FAILED", onion_address, + control_event_hs_descriptor_upload_end("FAILED", onion_address, id_digest, reason); } @@ -7227,5 +7317,5 @@ control_testing_set_global_event_mask(uint64_t mask) { global_event_mask = mask; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/control.h b/src/or/control.h index 41a194bfcb..e957b593a6 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -33,7 +33,6 @@ void connection_control_closed(control_connection_t *conn); int connection_control_process_inbuf(control_connection_t *conn); -#define EVENT_AUTHDIR_NEWDESCS 0x000D #define EVENT_NS 0x000F int control_event_is_interesting(int event); @@ -64,10 +63,6 @@ int control_event_descriptors_changed(smartlist_t *routers); int control_event_address_mapped(const char *from, const char *to, time_t expires, const char *error, const int cached); -int control_event_or_authdir_new_descriptor(const char *action, - const char *desc, - size_t desclen, - const char *msg); int control_event_my_descriptor_changed(void); int control_event_network_liveness_update(int liveness); int control_event_networkstatus_changed(smartlist_t *statuses); @@ -104,9 +99,11 @@ void enable_control_logging(void); void monitor_owning_controller_process(const char *process_spec); int control_event_bootstrap(bootstrap_status_t status, int progress); -MOCK_DECL(void, control_event_bootstrap_problem,(const char *warn, +MOCK_DECL(void, control_event_bootstrap_prob_or,(const char *warn, int reason, or_connection_t *or_conn)); +void control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn); void control_event_clients_seen(const char *controller_str); void control_event_transport_launched(const char *mode, @@ -169,8 +166,8 @@ void control_free_all(void); #define EVENT_WARN_MSG 0x000A #define EVENT_ERR_MSG 0x000B #define EVENT_ADDRMAP 0x000C -/* Exposed above */ -// #define EVENT_AUTHDIR_NEWDESCS 0x000D +/* There was an AUTHDIR_NEWDESCS event, but it no longer exists. We + can reclaim 0x000D. */ #define EVENT_DESCCHANGED 0x000E /* Exposed above */ // #define EVENT_NS 0x000F @@ -228,7 +225,7 @@ MOCK_DECL(STATIC void, queue_control_event_string,(uint16_t event, char *msg)); void control_testing_set_global_event_mask(uint64_t mask); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Helper structure: temporarily stores cell statistics for a circuit. */ typedef struct cell_stats_t { @@ -295,7 +292,7 @@ STATIC int getinfo_helper_dir( const char *question, char **answer, const char **errmsg); -#endif +#endif /* defined(CONTROL_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONTROL_H) */ diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index f5fff2b331..d9371b3446 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -382,7 +382,7 @@ cpuworker_onion_handshake_replyfn(void *work_) if (onionskin_answer(circ, &rpl.created_cell, - (const char*)rpl.keys, + (const char*)rpl.keys, sizeof(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); @@ -484,7 +484,7 @@ queue_pending_tasks(void) if (!circ) return; - if (assign_onionskin_to_cpuworker(circ, onionskin)) + if (assign_onionskin_to_cpuworker(circ, onionskin) < 0) log_info(LD_OR,"assign_to_cpuworker failed. Ignoring."); } } diff --git a/src/or/cpuworker.h b/src/or/cpuworker.h index 320de9532f..d39851325f 100644 --- a/src/or/cpuworker.h +++ b/src/or/cpuworker.h @@ -33,5 +33,5 @@ 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 +#endif /* !defined(TOR_CPUWORKER_H) */ diff --git a/src/or/dircollate.c b/src/or/dircollate.c index 172364c5f5..d34ebe8af5 100644 --- a/src/or/dircollate.c +++ b/src/or/dircollate.c @@ -53,7 +53,7 @@ ddmap_entry_free(ddmap_entry_t *e) static ddmap_entry_t * ddmap_entry_new(int n_votes) { - return tor_malloc_zero(STRUCT_OFFSET(ddmap_entry_t, vrs_lst) + + return tor_malloc_zero(offsetof(ddmap_entry_t, vrs_lst) + sizeof(vote_routerstatus_t *) * n_votes); } diff --git a/src/or/dircollate.h b/src/or/dircollate.h index 52214282b9..7932e18998 100644 --- a/src/or/dircollate.h +++ b/src/or/dircollate.h @@ -62,7 +62,7 @@ struct dircollator_s { * identity digests .*/ smartlist_t *all_rsa_sha1_lst; }; -#endif +#endif /* defined(DIRCOLLATE_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRCOLLATE_H) */ diff --git a/src/or/directory.c b/src/or/directory.c index 5ceea2fb32..f14bdde90b 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -18,7 +18,6 @@ #include "consdiffmgr.h" #include "control.h" #include "compat.h" -#define DIRECTORY_PRIVATE #include "directory.h" #include "dirserv.h" #include "dirvote.h" @@ -26,6 +25,7 @@ #include "geoip.h" #include "hs_cache.h" #include "hs_common.h" +#include "hs_client.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -104,7 +104,6 @@ static void directory_send_command(dir_connection_t *conn, int direct, const directory_request_t *request); static int body_is_plausible(const char *body, size_t body_len, int purpose); -static char *http_get_header(const char *headers, const char *which); static void http_set_address_origin(const char *headers, connection_t *conn); static void connection_dir_download_routerdesc_failed(dir_connection_t *conn); static void connection_dir_bridge_routerdesc_failed(dir_connection_t *conn); @@ -185,9 +184,12 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, case DIR_PURPOSE_FETCH_EXTRAINFO: case DIR_PURPOSE_FETCH_MICRODESC: return 0; + case DIR_PURPOSE_HAS_FETCHED_HSDESC: case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2: case DIR_PURPOSE_UPLOAD_RENDDESC_V2: case DIR_PURPOSE_FETCH_RENDDESC_V2: + case DIR_PURPOSE_FETCH_HSDESC: + case DIR_PURPOSE_UPLOAD_HSDESC: return 1; case DIR_PURPOSE_SERVER: default: @@ -246,6 +248,10 @@ dir_conn_purpose_to_string(int purpose) return "hidden-service v2 descriptor fetch"; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: return "hidden-service v2 descriptor upload"; + case DIR_PURPOSE_FETCH_HSDESC: + return "hidden-service descriptor fetch"; + case DIR_PURPOSE_UPLOAD_HSDESC: + return "hidden-service descriptor upload"; case DIR_PURPOSE_FETCH_MICRODESC: return "microdescriptor fetch"; } @@ -1006,47 +1012,6 @@ directory_must_use_begindir(const or_options_t *options) return !public_server_mode(options); } -struct directory_request_t { - /** - * These fields specify which directory we're contacting. Routerstatus, - * if present, overrides the other fields. - * - * @{ */ - tor_addr_port_t or_addr_port; - tor_addr_port_t dir_addr_port; - char digest[DIGEST_LEN]; - - const routerstatus_t *routerstatus; - /** @} */ - /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what - * kind of operation we'll be doing (upload/download), and of what kind - * of document. */ - uint8_t dir_purpose; - /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo - * and extrainfo docs. */ - uint8_t router_purpose; - /** Enum: determines whether to anonymize, and whether to use dirport or - * orport. */ - dir_indirection_t indirection; - /** Alias to the variable part of the URL for this request */ - const char *resource; - /** Alias to the payload to upload (if any) */ - const char *payload; - /** Number of bytes to upload from payload</b> */ - size_t payload_len; - /** Value to send in an if-modified-since header, or 0 for none. */ - time_t if_modified_since; - /** Hidden-service-specific information */ - const rend_data_t *rend_query; - /** Extra headers to append to the request */ - config_line_t *additional_headers; - /** */ - /** Used internally to directory.c: gets informed when the attempt to - * connect to the directory succeeds or fails, if that attempt bears on the - * directory's usability as a directory guard. */ - circuit_guard_state_t *guard_state; -}; - /** Evaluate the situation and decide if we should use an encrypted * "begindir-style" connection for this directory request. * 0) If there is no DirPort, yes. @@ -1120,6 +1085,7 @@ directory_request_new(uint8_t dir_purpose) tor_assert(dir_purpose <= DIR_PURPOSE_MAX_); tor_assert(dir_purpose != DIR_PURPOSE_SERVER); tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2); + tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_HSDESC); directory_request_t *result = tor_malloc_zero(sizeof(*result)); tor_addr_make_null(&result->or_addr_port.addr, AF_INET); @@ -1270,6 +1236,34 @@ directory_request_set_rend_query(directory_request_t *req, } req->rend_query = query; } +/** + * Set an object containing HS connection identifier to be associated with + * this request. Note that only an alias to <b>ident</b> is stored, so the + * <b>ident</b> object must outlive the request. + */ +void +directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC); + } + req->hs_ident = ident; +} +/** + * Set an object containing HS connection identifier to be associated with + * this fetch request. Note that only an alias to <b>ident</b> is stored, so + * the <b>ident</b> object must outlive the request. + */ +void +directory_request_fetch_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC); + } + req->hs_ident = ident; +} /** Set a static circuit_guard_state_t object to affliate with the request in * <b>req</b>. This object will receive notification when the attempt to * connect to the guard either succeeds or fails. */ @@ -1391,6 +1385,7 @@ directory_initiate_request,(directory_request_t *request)) const dir_indirection_t indirection = request->indirection; const char *resource = request->resource; const rend_data_t *rend_query = request->rend_query; + const hs_ident_dir_conn_t *hs_ident = request->hs_ident; circuit_guard_state_t *guard_state = request->guard_state; tor_assert(or_addr_port->port || dir_addr_port->port); @@ -1478,8 +1473,16 @@ directory_initiate_request,(directory_request_t *request)) conn->dirconn_direct = !anonymized_connection; /* copy rendezvous data, if any */ - if (rend_query) + if (rend_query) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!hs_ident); conn->rend_data = rend_data_dup(rend_query); + } + if (hs_ident) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!rend_query); + conn->hs_ident = hs_ident_dir_conn_dup(hs_ident); + } if (!anonymized_connection && !use_begindir) { /* then we want to connect to dirport directly */ @@ -1831,12 +1834,25 @@ directory_send_command(dir_connection_t *conn, httpcommand = "GET"; tor_asprintf(&url, "/tor/rendezvous2/%s", resource); break; + case DIR_PURPOSE_FETCH_HSDESC: + tor_assert(resource); + tor_assert(strlen(resource) <= ED25519_BASE64_LEN); + tor_assert(!payload); + httpcommand = "GET"; + tor_asprintf(&url, "/tor/hs/3/%s", resource); + break; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: tor_assert(!resource); tor_assert(payload); httpcommand = "POST"; url = tor_strdup("/tor/rendezvous2/publish"); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + tor_assert(resource); + tor_assert(payload); + httpcommand = "POST"; + tor_asprintf(&url, "/tor/hs/%s/publish", resource); + break; default: tor_assert(0); return; @@ -1854,11 +1870,11 @@ directory_send_command(dir_connection_t *conn, request_len = strlen(request); total_request_len += request_len; - connection_write_to_buf(request, request_len, TO_CONN(conn)); + connection_buf_add(request, request_len, TO_CONN(conn)); url_len = strlen(url); total_request_len += url_len; - connection_write_to_buf(url, url_len, TO_CONN(conn)); + connection_buf_add(url, url_len, TO_CONN(conn)); tor_free(url); if (!strcmp(httpcommand, "POST") || payload) { @@ -1875,11 +1891,11 @@ directory_send_command(dir_connection_t *conn, request_len = strlen(request); total_request_len += request_len; - connection_write_to_buf(request, request_len, TO_CONN(conn)); + connection_buf_add(request, request_len, TO_CONN(conn)); if (payload) { /* then send the payload afterwards too */ - connection_write_to_buf(payload, payload_len, TO_CONN(conn)); + connection_buf_add(payload, payload_len, TO_CONN(conn)); total_request_len += payload_len; } @@ -1908,15 +1924,41 @@ directory_send_command(dir_connection_t *conn, STATIC int parse_http_url(const char *headers, char **url) { + char *command = NULL; + if (parse_http_command(headers, &command, url) < 0) { + return -1; + } + if (strcmpstart(*url, "/tor/")) { + char *new_url = NULL; + tor_asprintf(&new_url, "/tor%s%s", + *url[0] == '/' ? "" : "/", + *url); + tor_free(*url); + *url = new_url; + } + tor_free(command); + return 0; +} + +/** Parse an HTTP request line at the start of a headers string. On failure, + * return -1. On success, set *<b>command_out</b> to a copy of the HTTP + * command ("get", "post", etc), set *<b>url_out</b> to a copy of the URL, and + * return 0. */ +int +parse_http_command(const char *headers, char **command_out, char **url_out) +{ + const char *command, *end_of_command; char *s, *start, *tmp; s = (char *)eat_whitespace_no_nl(headers); if (!*s) return -1; + command = s; s = (char *)find_whitespace(s); /* get past GET/POST */ if (!*s) return -1; + end_of_command = s; s = (char *)eat_whitespace_no_nl(s); if (!*s) return -1; - start = s; /* this is it, assuming it's valid */ + start = s; /* this is the URL, assuming it's valid */ s = (char *)find_whitespace(start); if (!*s) return -1; @@ -1947,13 +1989,8 @@ parse_http_url(const char *headers, char **url) 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); - strlcat((*url)+4, start, s-start+1); - } else { - *url = tor_strndup(start, s-start); - } + *url_out = tor_memdup_nulterm(start, s-start); + *command_out = tor_memdup_nulterm(command, end_of_command - command); return 0; } @@ -1961,7 +1998,7 @@ parse_http_url(const char *headers, char **url) * <b>which</b>. The key should be given with a terminating colon and space; * this function copies everything after, up to but not including the * following \\r\\n. */ -static char * +char * http_get_header(const char *headers, const char *which) { const char *cp = headers; @@ -2159,16 +2196,6 @@ load_downloaded_routers(const char *body, smartlist_t *which, return added; } -/** A structure to hold arguments passed into each directory response - * handler */ -typedef struct response_handler_args_t { - int status_code; - const char *reason; - const char *body; - size_t body_len; - const char *headers; -} response_handler_args_t; - static int handle_response_fetch_consensus(dir_connection_t *, const response_handler_args_t *); static int handle_response_fetch_certificate(dir_connection_t *, @@ -2189,6 +2216,8 @@ static int handle_response_fetch_renddesc_v2(dir_connection_t *, const response_handler_args_t *); static int handle_response_upload_renddesc_v2(dir_connection_t *, const response_handler_args_t *); +static int handle_response_upload_hsdesc(dir_connection_t *, + const response_handler_args_t *); static int dir_client_decompress_response_body(char **bodyp, size_t *bodylenp, @@ -2489,6 +2518,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case DIR_PURPOSE_UPLOAD_RENDDESC_V2: rv = handle_response_upload_renddesc_v2(conn, &args); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + rv = handle_response_upload_hsdesc(conn, &args); + break; + case DIR_PURPOSE_FETCH_HSDESC: + rv = handle_response_fetch_hsdesc_v3(conn, &args); + break; default: tor_assert_nonfatal_unreached(); rv = -1; @@ -2811,7 +2846,7 @@ handle_response_fetch_desc(dir_connection_t *conn, conn->router_purpose, conn->base_.address)) { time_t now = approx_time(); - directory_info_has_arrived(now, 0, 0); + directory_info_has_arrived(now, 0, 1); } } } @@ -3035,6 +3070,60 @@ handle_response_upload_signatures(dir_connection_t *conn, } /** + * Handler function: processes a response to a request for a v3 hidden service + * descriptor. + **/ +STATIC int +handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + tor_assert(conn->hs_ident); + + log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))", + (int)body_len, status_code, escaped(reason)); + + switch (status_code) { + case 200: + /* We got something: Try storing it in the cache. */ + if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) { + log_warn(LD_REND, "Failed to store hidden service descriptor"); + } else { + log_info(LD_REND, "Stored hidden service descriptor successfully."); + TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; + hs_client_desc_has_arrived(conn->hs_ident); + } + break; + case 404: + /* Not there. We'll retry when connection_about_to_close_connection() + * tries to clean this conn up. */ + log_info(LD_REND, "Fetching hidden service v3 descriptor not found: " + "Retrying at another directory."); + /* TODO: Inform the control port */ + break; + case 400: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status 400 (%s). Dirserver didn't like our " + "query? Retrying at another directory.", + escaped(reason)); + break; + default: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status %d (%s) response unexpected from HSDir server " + "'%s:%d'. Retrying at another directory.", + status_code, escaped(reason), TO_CONN(conn)->address, + TO_CONN(conn)->port); + break; + } + + return 0; +} + +/** * Handler function: processes a response to a request for a v2 hidden service * descriptor. **/ @@ -3181,6 +3270,53 @@ handle_response_upload_renddesc_v2(dir_connection_t *conn, return 0; } +/** + * Handler function: processes a response to a POST request to upload an + * hidden service descriptor. + **/ +static int +handle_response_upload_hsdesc(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + + tor_assert(conn); + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC); + + log_info(LD_REND, "Uploaded hidden service descriptor (status %d " + "(%s))", + status_code, escaped(reason)); + /* For this directory response, it MUST have an hidden service identifier on + * this connection. */ + tor_assert(conn->hs_ident); + switch (status_code) { + case 200: + log_info(LD_REND, "Uploading hidden service descriptor: " + "finished with status 200 (%s)", escaped(reason)); + /* XXX: Trigger control event. */ + break; + case 400: + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Uploading hidden service descriptor: http " + "status 400 (%s) response from dirserver " + "'%s:%d'. Malformed hidden service descriptor?", + escaped(reason), conn->base_.address, conn->base_.port); + /* XXX: Trigger control event. */ + break; + default: + log_warn(LD_REND, "Uploading hidden service descriptor: http " + "status %d (%s) response unexpected (server " + "'%s:%d').", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + /* XXX: Trigger control event. */ + break; + } + + return 0; +} + /** Called when a directory connection reaches EOF. */ int connection_dir_reached_eof(dir_connection_t *conn) @@ -3252,6 +3388,33 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } +/** We are closing a dir connection: If <b>dir_conn</b> is a dir connection + * that tried to fetch an HS descriptor, check if it successfuly fetched it, + * or if we need to try again. */ +static void +refetch_hsdesc_if_needed(dir_connection_t *dir_conn) +{ + connection_t *conn = TO_CONN(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_V2 or DIR_PURPOSE_HAS_FETCHED_HSDESC to + * mark that refetching is unnecessary.) */ + if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && + dir_conn->rend_data && + rend_valid_v2_service_id( + rend_data_get_address(dir_conn->rend_data))) { + rend_client_refetch_v2_renddesc(dir_conn->rend_data); + } + + /* Check for v3 rend desc fetch */ + if (conn->purpose == DIR_PURPOSE_FETCH_HSDESC && + dir_conn->hs_ident && + !ed25519_public_key_is_zero(&dir_conn->hs_ident->identity_pk)) { + hs_client_refetch_hsdesc(&dir_conn->hs_ident->identity_pk); + } +} + /** Called when we're about to finally unlink and free a directory connection: * perform necessary accounting and cleanup */ void @@ -3264,29 +3427,38 @@ connection_dir_about_to_close(dir_connection_t *dir_conn) * failed: forget about this router, and maybe try again. */ connection_dir_request_failed(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_V2 to mark that - * refetching is unnecessary.) */ - if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && - dir_conn->rend_data && - strlen(rend_data_get_address(dir_conn->rend_data)) == - REND_SERVICE_ID_LEN_BASE32) - rend_client_refetch_v2_renddesc(dir_conn->rend_data); + + refetch_hsdesc_if_needed(dir_conn); } /** Create an http response for the client <b>conn</b> out of * <b>status</b> and <b>reason_phrase</b>. Write it to <b>conn</b>. */ static void -write_http_status_line(dir_connection_t *conn, int status, +write_short_http_response(dir_connection_t *conn, int status, const char *reason_phrase) { char *buf = NULL; - tor_asprintf(&buf, "HTTP/1.0 %d %s\r\n\r\n", - status, reason_phrase ? reason_phrase : "OK"); + char *datestring = NULL; + + IF_BUG_ONCE(!reason_phrase) { /* bullet-proofing */ + reason_phrase = "unspecified"; + } + + if (server_mode(get_options())) { + /* include the Date: header, but only if we're a relay or bridge */ + char datebuf[RFC1123_TIME_LEN+1]; + format_rfc1123_time(datebuf, time(NULL)); + tor_asprintf(&datestring, "Date: %s\r\n", datebuf); + } + + tor_asprintf(&buf, "HTTP/1.0 %d %s\r\n%s\r\n", + status, reason_phrase, datestring?datestring:""); + log_debug(LD_DIRSERV,"Wrote status 'HTTP/1.0 %d %s'", status, reason_phrase); - connection_write_to_buf(buf, strlen(buf), TO_CONN(conn)); + connection_buf_add(buf, strlen(buf), TO_CONN(conn)); + + tor_free(datestring); tor_free(buf); } @@ -3360,7 +3532,7 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, memcpy(cp, "\r\n", 3); else tor_assert(0); - connection_write_to_buf(tmp, strlen(tmp), TO_CONN(conn)); + connection_buf_add(tmp, strlen(tmp), TO_CONN(conn)); } /** As write_http_response_header_impl, but sets encoding and content-typed @@ -3624,7 +3796,6 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, char *url, *url_mem, *header; time_t if_modified_since = 0; int zlib_compressed_in_url; - size_t url_len; unsigned compression_methods_supported; /* We ignore the body of a GET request. */ @@ -3636,7 +3807,7 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, conn->base_.state = DIR_CONN_STATE_SERVER_WRITING; if (parse_http_url(headers, &url) < 0) { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); return 0; } if ((header = http_get_header(headers, "If-Modified-Since: "))) { @@ -3655,12 +3826,13 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); url_mem = url; - url_len = strlen(url); + { + size_t url_len = strlen(url); - zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z"); - if (zlib_compressed_in_url) { - url[url_len-2] = '\0'; - url_len -= 2; + zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z"); + if (zlib_compressed_in_url) { + url[url_len-2] = '\0'; + } } if ((header = http_get_header(headers, "Accept-Encoding: "))) { @@ -3697,7 +3869,7 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, } /* we didn't recognize the url */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); result = 0; done: @@ -3723,9 +3895,9 @@ handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args) * this page no matter what.] */ write_http_response_header_impl(conn, dlen, "text/html", "identity", NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME); - connection_write_to_buf(frontpage, dlen, TO_CONN(conn)); + connection_buf_add(frontpage, dlen, TO_CONN(conn)); } else { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); } return 0; } @@ -4107,13 +4279,13 @@ handle_get_current_consensus(dir_connection_t *conn, parsed_consensus_request_t req; if (parse_consensus_request(&req, args) < 0) { - write_http_status_line(conn, 404, "Couldn't parse request"); + write_short_http_response(conn, 404, "Couldn't parse request"); goto done; } if (digest_list_contains_best_consensus(req.flav, req.diff_from_digests)) { - write_http_status_line(conn, 304, "Not modified"); + write_short_http_response(conn, 304, "Not modified"); geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); goto done; } @@ -4128,7 +4300,7 @@ handle_get_current_consensus(dir_connection_t *conn, } if (req.diff_only && !cached_consensus) { - write_http_status_line(conn, 404, "No such diff available"); + write_short_http_response(conn, 404, "No such diff available"); // XXXX warn_consensus_is_too_old(v, req.flavor, now); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; @@ -4151,7 +4323,7 @@ handle_get_current_consensus(dir_connection_t *conn, if (cached_consensus && have_valid_until && !networkstatus_valid_until_is_reasonably_live(valid_until, now)) { - write_http_status_line(conn, 404, "Consensus is too old"); + write_short_http_response(conn, 404, "Consensus is too old"); warn_consensus_is_too_old(cached_consensus, req.flavor, now); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; @@ -4159,7 +4331,7 @@ handle_get_current_consensus(dir_connection_t *conn, if (cached_consensus && req.want_fps && !client_likes_consensus(cached_consensus, req.want_fps)) { - write_http_status_line(conn, 404, "Consensus not signed by sufficient " + write_short_http_response(conn, 404, "Consensus not signed by sufficient " "number of requested authorities"); geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS); goto done; @@ -4185,11 +4357,11 @@ handle_get_current_consensus(dir_connection_t *conn, &n_expired); if (!smartlist_len(conn->spool) && !n_expired) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; } else if (!smartlist_len(conn->spool)) { - write_http_status_line(conn, 304, "Not modified"); + write_short_http_response(conn, 304, "Not modified"); geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); goto done; } @@ -4198,7 +4370,7 @@ handle_get_current_consensus(dir_connection_t *conn, log_debug(LD_DIRSERV, "Client asked for network status lists, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); geoip_note_ns_response(GEOIP_REJECT_BUSY); goto done; } @@ -4311,7 +4483,7 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) smartlist_free(fps); } if (!smartlist_len(dir_items) && !smartlist_len(items)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto vote_done; } @@ -4348,7 +4520,7 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) }); if (global_write_bucket_low(TO_CONN(conn), estimated_len, 2)) { - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); goto vote_done; } write_http_response_header(conn, body_len ? body_len : -1, @@ -4360,15 +4532,15 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(estimated_len)); SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf_compress(c, strlen(c), conn, 0)); - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress(c, strlen(c), conn, 0)); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf(c, strlen(c), TO_CONN(conn))); + connection_buf_add(c, strlen(c), TO_CONN(conn))); } } else { SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, - connection_write_to_buf(compress_method != NO_METHOD ? + connection_buf_add(compress_method != NO_METHOD ? d->dir_compressed : d->dir, compress_method != NO_METHOD ? d->dir_compressed_len : d->dir_len, @@ -4405,14 +4577,14 @@ handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args) compress_method != NO_METHOD, &size_guess, NULL); if (smartlist_len(conn->spool) == 0) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto done; } if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); goto done; } @@ -4504,13 +4676,14 @@ handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args) if (res < 0 || size_guess == 0 || smartlist_len(conn->spool) == 0) { if (msg == NULL) msg = "Not found"; - write_http_status_line(conn, 404, msg); + write_short_http_response(conn, 404, msg); } else { if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, + "Directory busy, try again later"); dir_conn_clear_spool(conn); goto done; } @@ -4583,18 +4756,18 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) }); smartlist_free(fp_sks); } else { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); goto keys_done; } if (!smartlist_len(certs)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto keys_done; } SMARTLIST_FOREACH(certs, authority_cert_t *, c, if (c->cache_info.published_on < if_modified_since) SMARTLIST_DEL_CURRENT(certs, c)); if (!smartlist_len(certs)) { - write_http_status_line(conn, 304, "Not modified"); + write_short_http_response(conn, 304, "Not modified"); goto keys_done; } len = 0; @@ -4604,7 +4777,7 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) if (global_write_bucket_low(TO_CONN(conn), compress_method != NO_METHOD ? len/2 : len, 2)) { - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); goto keys_done; } @@ -4616,14 +4789,14 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(len)); SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf_compress( + connection_buf_add_compress( c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, conn, 0)); - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf(c->cache_info.signed_descriptor_body, + connection_buf_add(c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, TO_CONN(conn))); } @@ -4652,22 +4825,22 @@ handle_get_hs_descriptor_v2(dir_connection_t *conn, switch (rend_cache_lookup_v2_desc_as_dir(query, &descp)) { case 1: /* valid */ write_http_response_header(conn, strlen(descp), NO_METHOD, 0); - connection_write_to_buf(descp, strlen(descp), TO_CONN(conn)); + connection_buf_add(descp, strlen(descp), TO_CONN(conn)); break; case 0: /* well-formed but not present */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); break; case -1: /* not well-formed */ - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); break; } } else { /* not well-formed */ - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); } goto done; } else { /* Not encrypted! */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); } done: return 0; @@ -4686,7 +4859,7 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, /* Reject unencrypted dir connections */ if (!connection_dir_is_encrypted(conn)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto done; } @@ -4698,13 +4871,13 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, retval = hs_cache_lookup_as_dir(HS_VERSION_THREE, pubkey_str, &desc_str); if (retval <= 0 || desc_str == NULL) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto done; } /* Found requested descriptor! Pass it to this nice client. */ write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0); - connection_write_to_buf(desc_str, strlen(desc_str), TO_CONN(conn)); + connection_buf_add(desc_str, strlen(desc_str), TO_CONN(conn)); done: return 0; @@ -4733,7 +4906,7 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, if (!header || tor_memneq(digest, options->BridgePassword_AuthDigest_, DIGEST256_LEN)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); tor_free(header); goto done; } @@ -4743,7 +4916,7 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, status = networkstatus_getinfo_by_purpose("bridge", time(NULL)); size_t dlen = strlen(status); write_http_response_header(conn, dlen, NO_METHOD, 0); - connection_write_to_buf(status, dlen, TO_CONN(conn)); + connection_buf_add(status, dlen, TO_CONN(conn)); tor_free(status); goto done; } @@ -4760,7 +4933,7 @@ handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args) const char robots[] = "User-agent: *\r\nDisallow: /\r\n"; size_t len = strlen(robots); write_http_response_header(conn, len, NO_METHOD, ROBOTS_CACHE_LIFETIME); - connection_write_to_buf(robots, len, TO_CONN(conn)); + connection_buf_add(robots, len, TO_CONN(conn)); } return 0; } @@ -4868,12 +5041,12 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, if (!public_server_mode(options)) { log_info(LD_DIR, "Rejected dir post request from %s " "since we're not a public relay.", conn->base_.address); - write_http_status_line(conn, 503, "Not acting as a public relay"); + write_short_http_response(conn, 503, "Not acting as a public relay"); goto done; } if (parse_http_url(headers, &url) < 0) { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); return 0; } log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); @@ -4884,10 +5057,10 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, if (rend_cache_store_v2_desc_as_dir(body) < 0) { log_warn(LD_REND, "Rejected v2 rend descriptor (body size %d) from %s.", (int)body_len, conn->base_.address); - write_http_status_line(conn, 400, + write_short_http_response(conn, 400, "Invalid v2 service descriptor rejected"); } else { - write_http_status_line(conn, 200, "Service descriptor (v2) stored"); + write_short_http_response(conn, 200, "Service descriptor (v2) stored"); log_info(LD_REND, "Handled v2 rendezvous descriptor post: accepted"); } goto done; @@ -4904,19 +5077,19 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, if (code != 200) { msg = "Invalid HS descriptor. Rejected."; } - write_http_status_line(conn, code, msg); + write_short_http_response(conn, code, msg); goto done; } if (!authdir_mode(options)) { /* we just provide cached directories; we don't want to * receive anything. */ - write_http_status_line(conn, 400, "Nonauthoritative directory does not " + write_short_http_response(conn, 400, "Nonauthoritative directory does not " "accept posted server descriptors"); goto done; } - if (authdir_mode_handles_descs(options, -1) && + if (authdir_mode(options) && !strcmp(url,"/tor/")) { /* server descriptor post */ const char *msg = "[None]"; uint8_t purpose = authdir_mode_bridge(options) ? @@ -4926,7 +5099,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, tor_assert(msg); if (r == ROUTER_ADDED_SUCCESSFULLY) { - write_http_status_line(conn, 200, msg); + write_short_http_response(conn, 200, msg); } else if (WRA_WAS_OUTDATED(r)) { write_http_response_header_impl(conn, -1, NULL, NULL, "X-Descriptor-Not-New: Yes\r\n", -1); @@ -4935,7 +5108,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, "Rejected router descriptor or extra-info from %s " "(\"%s\").", conn->base_.address, msg); - write_http_status_line(conn, 400, msg); + write_short_http_response(conn, 400, msg); } goto done; } @@ -4945,12 +5118,12 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, const char *msg = "OK"; int status; if (dirvote_add_vote(body, &msg, &status)) { - write_http_status_line(conn, status, "Vote stored"); + write_short_http_response(conn, status, "Vote stored"); } else { tor_assert(msg); log_warn(LD_DIRSERV, "Rejected vote from %s (\"%s\").", conn->base_.address, msg); - write_http_status_line(conn, status, msg); + write_short_http_response(conn, status, msg); } goto done; } @@ -4959,17 +5132,18 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, !strcmp(url,"/tor/post/consensus-signature")) { /* sigs on consensus. */ const char *msg = NULL; if (dirvote_add_signatures(body, conn->base_.address, &msg)>=0) { - write_http_status_line(conn, 200, msg?msg:"Signatures stored"); + write_short_http_response(conn, 200, msg?msg:"Signatures stored"); } else { log_warn(LD_DIR, "Unable to store signatures posted by %s: %s", conn->base_.address, msg?msg:"???"); - write_http_status_line(conn, 400, msg?msg:"Unable to store signatures"); + write_short_http_response(conn, 400, + msg?msg:"Unable to store signatures"); } goto done; } /* we didn't recognize the url */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); done: tor_free(url); @@ -5122,7 +5296,7 @@ connection_dir_finished_connecting(dir_connection_t *conn) * Helper function for download_status_increment_failure(), * download_status_reset(), and download_status_increment_attempt(). */ STATIC const smartlist_t * -find_dl_schedule(download_status_t *dls, const or_options_t *options) +find_dl_schedule(const download_status_t *dls, const or_options_t *options) { switch (dls->schedule) { case DL_SCHED_GENERIC: @@ -5163,7 +5337,15 @@ find_dl_schedule(download_status_t *dls, const or_options_t *options) } } case DL_SCHED_BRIDGE: - return options->TestingBridgeDownloadSchedule; + if (options->UseBridges && num_bridges_usable(0) > 0) { + /* A bridge client that is sure that one or more of its bridges are + * running can afford to wait longer to update bridge descriptors. */ + return options->TestingBridgeDownloadSchedule; + } else { + /* A bridge client which might have no running bridges, must try to + * get bridge descriptors straight away. */ + return options->TestingBridgeBootstrapDownloadSchedule; + } default: tor_assert(0); } @@ -5191,59 +5373,76 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, const smartlist_t *schedule = find_dl_schedule(dls, options); tor_assert(schedule != NULL && smartlist_len(schedule) >= 2); *min = *((int *)(smartlist_get(schedule, 0))); + /* Increment on failure schedules always use exponential backoff, but they + * have a smaller limit when they're deterministic */ if (dls->backoff == DL_SCHED_DETERMINISTIC) *max = *((int *)((smartlist_get(schedule, smartlist_len(schedule) - 1)))); else *max = INT_MAX; } -/** Advance one delay step. The algorithm is to use the previous delay to - * compute an increment, we construct a value uniformly at random between - * delay and MAX(delay*2,delay+1). We then clamp that value to be no larger - * than max_delay, and return it. +/** As next_random_exponential_delay() below, but does not compute a random + * value. Instead, compute the range of values that + * next_random_exponential_delay() should use when computing its random value. + * Store the low bound into *<b>low_bound_out</b>, and the high bound into + * *<b>high_bound_out</b>. Guarantees that the low bound is strictly less + * than the high bound. */ +STATIC void +next_random_exponential_delay_range(int *low_bound_out, + int *high_bound_out, + int delay, + int base_delay) +{ + // This is the "decorrelated jitter" approach, from + // https://www.awsarchitectureblog.com/2015/03/backoff.html + // The formula is + // sleep = min(cap, random_between(base, sleep * 3)) + + const int delay_times_3 = delay < INT_MAX/3 ? delay * 3 : INT_MAX; + *low_bound_out = base_delay; + if (delay_times_3 > base_delay) { + *high_bound_out = delay_times_3; + } else { + *high_bound_out = base_delay+1; + } +} + +/** Advance one delay step. The algorithm will generate a random delay, + * such that each failure is possibly (random) longer than the ones before. + * + * We then clamp that value to be no larger than max_delay, and return it. + * + * The <b>base_delay</b> parameter is lowest possible delay time (can't be + * zero); the <b>backoff_position</b> parameter is the number of times we've + * generated a delay; and the <b>delay</b> argument is the most recently used + * delay. * * Requires that delay is less than INT_MAX, and delay is in [0,max_delay]. */ STATIC int -next_random_exponential_delay(int delay, int max_delay) +next_random_exponential_delay(int delay, + int base_delay, + int max_delay) { /* Check preconditions */ if (BUG(max_delay < 0)) max_delay = 0; if (BUG(delay > max_delay)) delay = max_delay; - if (delay == INT_MAX) - return INT_MAX; /* prevent overflow */ if (BUG(delay < 0)) delay = 0; - /* How much are we willing to add to the delay? */ - int max_increment; - int multiplier = 3; /* no more than quadruple the previous delay */ - if (get_options()->TestingTorNetwork) { - /* Decrease the multiplier in testing networks. This reduces the variance, - * so that bootstrap is more reliable. */ - multiplier = 2; /* no more than triple the previous delay */ - } + if (base_delay < 1) + base_delay = 1; - if (delay && delay < (INT_MAX-1) / multiplier) { - max_increment = delay * multiplier; - } else if (delay) { - max_increment = INT_MAX-1; - } else { - max_increment = 1; - } + int low_bound=0, high_bound=max_delay; - if (BUG(max_increment < 1)) - max_increment = 1; + next_random_exponential_delay_range(&low_bound, &high_bound, + delay, base_delay); - /* the + 1 here is so that we always wait longer than last time. */ - int increment = crypto_rand_int(max_increment)+1; + int rand_delay = crypto_rand_int_range(low_bound, high_bound); - if (increment < max_delay - delay) - return delay + increment; - else - return max_delay; + return MIN(rand_delay, max_delay); } /** Find the current delay for dls based on schedule or min_delay/ @@ -5292,7 +5491,7 @@ download_status_schedule_get_delay(download_status_t *dls, while (dls->last_backoff_position < dls_schedule_position) { /* Do one increment step */ - delay = next_random_exponential_delay(delay, max_delay); + delay = next_random_exponential_delay(delay, min_delay, max_delay); /* Update our position */ ++(dls->last_backoff_position); } @@ -5375,6 +5574,11 @@ download_status_increment_failure(download_status_t *dls, int status_code, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + /* count the failure */ if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) { ++dls->n_download_failures; @@ -5399,14 +5603,16 @@ download_status_increment_failure(download_status_t *dls, int status_code, download_status_log_helper(item, !dls->increment_on, "failed", "concurrently", dls->n_download_failures, - increment, dls->next_attempt_at, now); + increment, + download_status_get_next_attempt_at(dls), + now); if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) { /* stop this schedule retrying on failure, it will launch concurrent * connections instead */ return TIME_MAX; } else { - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); } } @@ -5427,6 +5633,11 @@ download_status_increment_attempt(download_status_t *dls, const char *item, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { /* this schedule should retry on failure, and not launch any concurrent attempts */ @@ -5445,9 +5656,19 @@ download_status_increment_attempt(download_status_t *dls, const char *item, download_status_log_helper(item, dls->increment_on, "attempted", "on failure", dls->n_download_attempts, - delay, dls->next_attempt_at, now); + delay, download_status_get_next_attempt_at(dls), + now); - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); +} + +static time_t +download_status_get_initial_delay_from_now(const download_status_t *dls) +{ + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + /* We use constant initial delays, even in exponential backoff + * schedules. */ + return time(NULL) + *(int *)smartlist_get(schedule, 0); } /** Reset <b>dls</b> so that it will be considered downloadable @@ -5459,8 +5680,8 @@ download_status_increment_attempt(download_status_t *dls, const char *item, * (We find the zeroth element of the download schedule, and set * next_attempt_at to be the appropriate offset from 'now'. In most * cases this means setting it to 'now', so the item will be immediately - * downloadable; in the case of bridge descriptors, the zeroth element - * is an hour from now.) */ + * downloadable; when using authorities with fallbacks, there is a few seconds' + * delay.) */ void download_status_reset(download_status_t *dls) { @@ -5468,11 +5689,9 @@ download_status_reset(download_status_t *dls) || dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD) return; /* Don't reset this. */ - const smartlist_t *schedule = find_dl_schedule(dls, get_options()); - dls->n_download_failures = 0; dls->n_download_attempts = 0; - dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); + dls->next_attempt_at = download_status_get_initial_delay_from_now(dls); dls->last_backoff_position = 0; dls->last_delay_used = 0; /* Don't reset dls->want_authority or dls->increment_on */ @@ -5499,6 +5718,12 @@ download_status_get_n_attempts(const download_status_t *dls) time_t download_status_get_next_attempt_at(const download_status_t *dls) { + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + /* so give the answer we would have given if it had been */ + return download_status_get_initial_delay_from_now(dls); + } + return dls->next_attempt_at; } diff --git a/src/or/directory.h b/src/or/directory.h index 571c30a0fc..5e6a91d3e7 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -12,6 +12,8 @@ #ifndef TOR_DIRECTORY_H #define TOR_DIRECTORY_H +#include "hs_ident.h" + 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, @@ -71,6 +73,10 @@ void directory_request_set_if_modified_since(directory_request_t *req, time_t if_modified_since); void directory_request_set_rend_query(directory_request_t *req, const rend_data_t *query); +void directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); +void directory_request_fetch_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); void directory_request_set_routerstatus(directory_request_t *req, const routerstatus_t *rs); @@ -81,6 +87,9 @@ MOCK_DECL(void, directory_initiate_request, (directory_request_t *request)); int parse_http_response(const char *headers, int *code, time_t *date, compress_method_t *compression, char **response); +int parse_http_command(const char *headers, + char **command_out, char **url_out); +char *http_get_header(const char *headers, const char *which); int connection_dir_is_encrypted(const dir_connection_t *conn); int connection_dir_reached_eof(dir_connection_t *conn); @@ -123,12 +132,19 @@ time_t download_status_increment_attempt(download_status_t *dls, void download_status_reset(download_status_t *dls); static int download_status_is_ready(download_status_t *dls, time_t now, int max_failures); +time_t download_status_get_next_attempt_at(const download_status_t *dls); + /** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is * ready to get its download reattempted. */ static inline int download_status_is_ready(download_status_t *dls, time_t now, int max_failures) { + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->backoff == DL_SCHED_DETERMINISTIC) { /* Deterministic schedules can hit an endpoint; exponential backoff * schedules just wait longer and longer. */ @@ -137,7 +153,7 @@ download_status_is_ready(download_status_t *dls, time_t now, if (!under_failure_limit) return 0; } - return dls->next_attempt_at <= now; + return download_status_get_next_attempt_at(dls) <= now; } static void download_status_mark_impossible(download_status_t *dl); @@ -151,13 +167,64 @@ download_status_mark_impossible(download_status_t *dl) int download_status_get_n_failures(const download_status_t *dls); int download_status_get_n_attempts(const download_status_t *dls); -time_t download_status_get_next_attempt_at(const download_status_t *dls); int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); #ifdef DIRECTORY_PRIVATE +/** A structure to hold arguments passed into each directory response + * handler */ +typedef struct response_handler_args_t { + int status_code; + const char *reason; + const char *body; + size_t body_len; + const char *headers; +} response_handler_args_t; + +struct directory_request_t { + /** + * These fields specify which directory we're contacting. Routerstatus, + * if present, overrides the other fields. + * + * @{ */ + tor_addr_port_t or_addr_port; + tor_addr_port_t dir_addr_port; + char digest[DIGEST_LEN]; + + const routerstatus_t *routerstatus; + /** @} */ + /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what + * kind of operation we'll be doing (upload/download), and of what kind + * of document. */ + uint8_t dir_purpose; + /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo + * and extrainfo docs. */ + uint8_t router_purpose; + /** Enum: determines whether to anonymize, and whether to use dirport or + * orport. */ + dir_indirection_t indirection; + /** Alias to the variable part of the URL for this request */ + const char *resource; + /** Alias to the payload to upload (if any) */ + const char *payload; + /** Number of bytes to upload from payload</b> */ + size_t payload_len; + /** Value to send in an if-modified-since header, or 0 for none. */ + time_t if_modified_since; + /** Hidden-service-specific information v2. */ + const rend_data_t *rend_query; + /** Extra headers to append to the request */ + config_line_t *additional_headers; + /** Hidden-service-specific information for v3+. */ + const hs_ident_dir_conn_t *hs_ident; + /** Used internally to directory.c: gets informed when the attempt to + * connect to the directory succeeds or fails, if that attempt bears on the + * directory's usability as a directory guard. */ + struct circuit_guard_state_t *guard_state; +}; + struct get_handler_args_t; STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, const struct get_handler_args_t *args); @@ -166,15 +233,15 @@ STATIC char *accept_encoding_header(void); STATIC int allowed_anonymous_connection_compression_method(compress_method_t); STATIC void warn_disallowed_anonymous_compression_method(compress_method_t); -struct response_handler_args_t; - +STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args); STATIC int handle_response_fetch_microdesc(dir_connection_t *conn, - const struct response_handler_args_t *args); + const response_handler_args_t *args); #endif /* defined(DIRECTORY_PRIVATE) */ #ifdef TOR_UNIT_TESTS -/* Used only by test_dir.c */ +/* Used only by test_dir.c and test_hs_cache.c */ STATIC int parse_http_url(const char *headers, char **url); STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, @@ -198,18 +265,36 @@ STATIC char* authdir_type_to_string(dirinfo_type_t auth); STATIC const char * dir_conn_purpose_to_string(int purpose); STATIC int should_use_directory_guards(const or_options_t *options); STATIC compression_level_t choose_compression_level(ssize_t n_bytes); -STATIC const smartlist_t *find_dl_schedule(download_status_t *dls, +STATIC const smartlist_t *find_dl_schedule(const download_status_t *dls, const or_options_t *options); STATIC void find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, int *min, int *max); -STATIC int next_random_exponential_delay(int delay, int max_delay); + +STATIC int next_random_exponential_delay(int delay, + int base_delay, + int max_delay); + +STATIC void next_random_exponential_delay_range(int *low_bound_out, + int *high_bound_out, + int delay, + int base_delay); STATIC int parse_hs_version_from_post(const char *url, const char *prefix, const char **end_pos); STATIC unsigned parse_accept_encoding_header(const char *h); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ + +#if defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE) +/* Used only by directory.c and test_dir.c */ + +/* no more than quadruple the previous delay (multiplier + 1) */ +#define DIR_DEFAULT_RANDOM_MULTIPLIER (3) +/* no more than triple the previous delay */ +#define DIR_TEST_NET_RANDOM_MULTIPLIER (2) + +#endif /* defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRECTORY_H) */ diff --git a/src/or/dirserv.c b/src/or/dirserv.c index acd00322a1..ddee92da55 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -338,7 +338,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, } return FP_REJECT; } -#endif +#endif /* defined(DISABLE_DISABLING_ED25519) */ } } @@ -673,9 +673,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) ri->nickname, source, (int)ri->cache_info.signed_descriptor_len, MAX_DESCRIPTOR_UPLOAD_SIZE); *msg = "Router descriptor was too large."; - control_event_or_authdir_new_descriptor("REJECTED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_AUTHDIR_REJECTS; goto fail; } @@ -694,9 +691,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) router_describe(ri), source); *msg = "Not replacing router descriptor; no information has changed since " "the last one with this identity."; - control_event_or_authdir_new_descriptor("DROPPED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_IS_ALREADY_KNOWN; goto fail; } @@ -704,10 +698,19 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) /* Do keypinning again ... this time, to add the pin if appropriate */ int keypin_status; if (ri->cache_info.signing_key_cert) { + ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key; + /* First let's validate this pubkey before pinning it */ + if (ed25519_validate_pubkey(pkey) < 0) { + log_warn(LD_DIRSERV, "Received bad key from %s (source %s)", + router_describe(ri), source); + routerinfo_free(ri); + return ROUTER_AUTHDIR_REJECTS; + } + + /* Now pin it! */ keypin_status = keypin_check_and_add( (const uint8_t*)ri->cache_info.identity_digest, - ri->cache_info.signing_key_cert->signing_key.pubkey, - ! key_pinning); + pkey->pubkey, ! key_pinning); } else { keypin_status = keypin_check_lone_rsa( (const uint8_t*)ri->cache_info.identity_digest); @@ -742,14 +745,11 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) r = router_add_to_routerlist(ri, msg, 0, 0); if (!WRA_WAS_ADDED(r)) { /* unless the routerinfo was fine, just out-of-date */ - if (WRA_WAS_REJECTED(r)) - control_event_or_authdir_new_descriptor("REJECTED", desc, desclen, *msg); log_info(LD_DIRSERV, "Did not add descriptor from '%s' (source: %s): %s.", nickname, source, *msg ? *msg : "(no message)"); } else { smartlist_t *changed; - control_event_or_authdir_new_descriptor("ACCEPTED", desc, desclen, *msg); changed = smartlist_new(); smartlist_add(changed, ri); @@ -2693,7 +2693,7 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) } cp+=strlen("bw="); - out->bw_kb = tor_parse_long(cp, 0, 0, LONG_MAX, &parse_ok, &endptr); + out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr); if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) { log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s", escaped(orig_line)); @@ -3605,9 +3605,9 @@ spooled_resource_flush_some(spooled_resource_t *spooled, return SRFS_DONE; } if (conn->compress_state) { - connection_write_to_buf_compress((const char*)body, bodylen, conn, 0); + connection_buf_add_compress((const char*)body, bodylen, conn, 0); } else { - connection_write_to_buf((const char*)body, bodylen, TO_CONN(conn)); + connection_buf_add((const char*)body, bodylen, TO_CONN(conn)); } return SRFS_DONE; } else { @@ -3644,11 +3644,11 @@ spooled_resource_flush_some(spooled_resource_t *spooled, return SRFS_ERR; ssize_t bytes = (ssize_t) MIN(DIRSERV_CACHED_DIR_CHUNK_SIZE, remaining); if (conn->compress_state) { - connection_write_to_buf_compress( + connection_buf_add_compress( ptr + spooled->cached_dir_offset, bytes, conn, 0); } else { - connection_write_to_buf(ptr + spooled->cached_dir_offset, + connection_buf_add(ptr + spooled->cached_dir_offset, bytes, TO_CONN(conn)); } spooled->cached_dir_offset += bytes; @@ -3913,7 +3913,7 @@ connection_dirserv_flushed_some(dir_connection_t *conn) if (conn->compress_state) { /* Flush the compression state: there could be more bytes pending in there, * and we don't want to omit bytes. */ - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress("", 0, conn, 1); tor_compress_free(conn->compress_state); conn->compress_state = NULL; } diff --git a/src/or/dirserv.h b/src/or/dirserv.h index 480174d5bb..46967a6cb2 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -182,7 +182,7 @@ 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 +#endif /* defined(DIRSERV_PRIVATE) */ int dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses); @@ -204,5 +204,5 @@ void dirserv_spool_remove_missing_and_guess_size(dir_connection_t *conn, void dirserv_spool_sort(dir_connection_t *conn); void dir_conn_clear_spool(dir_connection_t *conn); -#endif +#endif /* !defined(TOR_DIRSERV_H) */ diff --git a/src/or/dirvote.c b/src/or/dirvote.c index f5e29eb786..ce82a5ef4a 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -306,7 +306,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, signing_key_fingerprint); } - note_crypto_pk_op(SIGN_DIR); { char *sig = router_get_dirobj_signature(digest, DIGEST_LEN, private_signing_key); @@ -542,8 +541,8 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, if (cur_n > most_n || (cur && cur_n == most_n && cur->status.published_on > most_published)) { most = cur; - most_n = cur_n; - most_published = cur->status.published_on; + // most_n = cur_n; // unused after this point. + // most_published = cur->status.published_on; // unused after this point. } tor_assert(most); @@ -737,12 +736,12 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list, } } SMARTLIST_FOREACH_END(k_v_pair); - if (n_found == 1) + if (n_found == 1) { return value; - else if (BUG(n_found > 1)) - return default_val; - else + } else { + tor_assert_nonfatal(n_found == 0); return default_val; + } } /** Minimum number of directory authorities voting for a parameter to @@ -2788,48 +2787,10 @@ dirvote_get_start_of_next_interval(time_t now, int interval, int offset) return next; } -/* Using the time <b>now</b>, return the next voting valid-after time. */ -time_t -get_next_valid_after_time(time_t now) -{ - time_t next_valid_after_time; - const or_options_t *options = get_options(); - voting_schedule_t *new_voting_schedule = - get_voting_schedule(options, now, LOG_INFO); - tor_assert(new_voting_schedule); - - next_valid_after_time = new_voting_schedule->interval_starts; - voting_schedule_free(new_voting_schedule); - - return next_valid_after_time; -} - -static voting_schedule_t voting_schedule; - -/** Set voting_schedule to hold the timing for the next vote we should be - * doing. */ -void -dirvote_recalculate_timing(const or_options_t *options, time_t now) -{ - voting_schedule_t *new_voting_schedule; - - if (!authdir_mode_v3(options)) { - return; - } - - /* get the new voting schedule */ - new_voting_schedule = get_voting_schedule(options, now, LOG_NOTICE); - tor_assert(new_voting_schedule); - - /* Fill in the global static struct now */ - memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); - voting_schedule_free(new_voting_schedule); -} - /* Populate and return a new voting_schedule_t that can be used to schedule * voting. The object is allocated on the heap and it's the responsibility of * the caller to free it. Can't fail. */ -voting_schedule_t * +static voting_schedule_t * get_voting_schedule(const or_options_t *options, time_t now, int severity) { int interval, vote_delay, dist_delay; @@ -2884,7 +2845,7 @@ get_voting_schedule(const or_options_t *options, time_t now, int severity) /** Frees a voting_schedule_t. This should be used instead of the generic * tor_free. */ -void +static void voting_schedule_free(voting_schedule_t *voting_schedule_to_free) { if (!voting_schedule_to_free) @@ -2892,13 +2853,53 @@ voting_schedule_free(voting_schedule_t *voting_schedule_to_free) tor_free(voting_schedule_to_free); } +static voting_schedule_t voting_schedule; + +/* Using the time <b>now</b>, return the next voting valid-after time. */ +time_t +dirvote_get_next_valid_after_time(void) +{ + /* This is a safe guard in order to make sure that the voting schedule + * static object is at least initialized. Using this function with a zeroed + * voting schedule can lead to bugs. */ + if (tor_mem_is_zero((const char *) &voting_schedule, + sizeof(voting_schedule))) { + dirvote_recalculate_timing(get_options(), time(NULL)); + voting_schedule.created_on_demand = 1; + } + return voting_schedule.interval_starts; +} + +/** Set voting_schedule to hold the timing for the next vote we should be + * doing. All type of tor do that because HS subsystem needs the timing as + * well to function properly. */ +void +dirvote_recalculate_timing(const or_options_t *options, time_t now) +{ + voting_schedule_t *new_voting_schedule; + + /* get the new voting schedule */ + new_voting_schedule = get_voting_schedule(options, now, LOG_INFO); + tor_assert(new_voting_schedule); + + /* Fill in the global static struct now */ + memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); + voting_schedule_free(new_voting_schedule); +} + /** Entry point: Take whatever voting actions are pending as of <b>now</b>. */ void dirvote_act(const or_options_t *options, time_t now) { if (!authdir_mode_v3(options)) return; - if (!voting_schedule.voting_starts) { + tor_assert_nonfatal(voting_schedule.voting_starts); + /* If we haven't initialized this object through this codeflow, we need to + * recalculate the timings to match our vote. The reason to do that is if we + * have a voting schedule initialized 1 minute ago, the voting timings might + * not be aligned to what we should expect with "now". This is especially + * true for TestingTorNetwork using smaller timings. */ + if (voting_schedule.created_on_demand) { char *keys = list_v3_auth_ids(); authority_cert_t *c = get_my_v3_authority_cert(); log_notice(LD_DIR, "Scheduling voting. Known authority IDs are %s. " @@ -3994,14 +3995,15 @@ dirvote_format_all_microdesc_vote_lines(const routerinfo_t *ri, time_t now, while ((ep = entries)) { char buf[128]; vote_microdesc_hash_t *h; - dirvote_format_microdesc_vote_line(buf, sizeof(buf), ep->md, - ep->low, ep->high); - h = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); - h->microdesc_hash_line = tor_strdup(buf); - h->next = result; - result = h; - ep->md->last_listed = now; - smartlist_add(microdescriptors_out, ep->md); + if (dirvote_format_microdesc_vote_line(buf, sizeof(buf), ep->md, + ep->low, ep->high) >= 0) { + h = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); + h->microdesc_hash_line = tor_strdup(buf); + h->next = result; + result = h; + ep->md->last_listed = now; + smartlist_add(microdescriptors_out, ep->md); + } entries = ep->next; tor_free(ep); } diff --git a/src/or/dirvote.h b/src/or/dirvote.h index e342dc78ea..72a35fea6d 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -168,12 +168,14 @@ typedef struct { int have_fetched_missing_signatures; /* True iff we have published our consensus. */ int have_published_consensus; -} voting_schedule_t; - -voting_schedule_t *get_voting_schedule(const or_options_t *options, - time_t now, int severity); -void voting_schedule_free(voting_schedule_t *voting_schedule_to_free); + /* True iff this voting schedule was set on demand meaning not through the + * normal vote operation of a dirauth or when a consensus is set. This only + * applies to a directory authority that needs to recalculate the voting + * timings only for the first vote even though this object was initilized + * prior to voting. */ + int created_on_demand; +} voting_schedule_t; void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out); time_t dirvote_get_start_of_next_interval(time_t now, @@ -181,7 +183,7 @@ time_t dirvote_get_start_of_next_interval(time_t now, 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); -time_t get_next_valid_after_time(time_t now); +time_t dirvote_get_next_valid_after_time(void); /* invoked on timers and by outside triggers. */ struct pending_vote_t * dirvote_add_vote(const char *vote_body, @@ -242,7 +244,7 @@ STATIC int networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, int64_t M, int64_t E, int64_t D, int64_t T, int64_t weight_scale); -#endif +#endif /* defined(DIRVOTE_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRVOTE_H) */ diff --git a/src/or/dns.c b/src/or/dns.c index b5344469b5..7dc3575f53 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -102,7 +102,7 @@ static void assert_cache_ok_(void); #define assert_cache_ok() assert_cache_ok_() #else #define assert_cache_ok() STMT_NIL -#endif +#endif /* defined(DEBUG_DNS_CACHE) */ static void assert_resolve_ok(cached_resolve_t *resolve); /** Hash table of cached_resolve objects. */ @@ -182,6 +182,18 @@ evdns_log_cb(int warn, const char *msg) } else if (!strcmp(msg, "All nameservers have failed")) { control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN"); all_down = 1; + } else if (!strcmpstart(msg, "Address mismatch on received DNS")) { + static ratelim_t mismatch_limit = RATELIM_INIT(3600); + const char *src = strstr(msg, " Apparent source"); + if (!src || get_options()->SafeLogging) { + src = ""; + } + log_fn_ratelim(&mismatch_limit, severity, LD_EXIT, + "eventdns: Received a DNS packet from " + "an IP address to which we did not send a request. This " + "could be a DNS spoofing attempt, or some kind of " + "misconfiguration.%s", src); + return; } tor_log(severity, LD_EXIT, "eventdns: %s", msg); } @@ -366,7 +378,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires) resolve->expire = expires; smartlist_pqueue_add(cached_resolve_pqueue, compare_cached_resolves_by_expiry_, - STRUCT_OFFSET(cached_resolve_t, minheap_idx), + offsetof(cached_resolve_t, minheap_idx), resolve); } @@ -413,7 +425,7 @@ purge_expired_resolves(time_t now) break; smartlist_pqueue_pop(cached_resolve_pqueue, compare_cached_resolves_by_expiry_, - STRUCT_OFFSET(cached_resolve_t, minheap_idx)); + offsetof(cached_resolve_t, minheap_idx)); if (resolve->state == CACHE_STATE_PENDING) { log_debug(LD_EXIT, @@ -949,14 +961,14 @@ assert_connection_edge_not_dns_pending(edge_connection_t *conn) for (pend = resolve->pending_connections; pend; pend = pend->next) { tor_assert(pend->conn != conn); } -#else +#else /* !(1) */ cached_resolve_t **resolve; HT_FOREACH(resolve, cache_map, &cache_root) { for (pend = (*resolve)->pending_connections; pend; pend = pend->next) { tor_assert(pend->conn != conn); } } -#endif +#endif /* 1 */ } /** Log an error and abort if any connection waiting for a DNS resolve is @@ -1384,7 +1396,7 @@ configure_nameservers(int force) evdns_base_load_hosts(the_evdns_base, sandbox_intern_string("/etc/hosts")); } -#endif +#endif /* defined(DNS_OPTION_HOSTSFILE) && defined(USE_LIBSECCOMP) */ log_info(LD_EXIT, "Parsing resolver configuration in '%s'", conf_fname); if ((r = evdns_base_resolv_conf_parse(the_evdns_base, flags, sandbox_intern_string(conf_fname)))) { @@ -1422,7 +1434,7 @@ configure_nameservers(int force) tor_free(resolv_conf_fname); resolv_conf_mtime = 0; } -#endif +#endif /* defined(_WIN32) */ #define SET(k,v) evdns_base_set_option(the_evdns_base, (k), (v)) @@ -1566,10 +1578,11 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, escaped_safe_str(hostname)); tor_free(escaped_address); } else if (count) { - log_warn(LD_EXIT, "eventdns returned only non-IPv4 answers for %s.", + log_info(LD_EXIT, "eventdns returned only unrecognized answer types " + " for %s.", escaped_safe_str(string_address)); } else { - log_warn(LD_BUG, "eventdns returned no addresses or error for %s!", + log_info(LD_EXIT, "eventdns returned no addresses or error for %s.", escaped_safe_str(string_address)); } } @@ -1945,7 +1958,7 @@ dns_launch_wildcard_checks(void) launch_wildcard_check(8, 16, ipv6, ".com"); launch_wildcard_check(8, 16, ipv6, ".org"); launch_wildcard_check(8, 16, ipv6, ".net"); - } + } } } @@ -2038,7 +2051,7 @@ assert_resolve_ok(cached_resolve_t *resolve) tor_assert(!resolve->hostname); else tor_assert(!resolve->result_ipv4.addr_ipv4); -#endif +#endif /* 0 */ /*XXXXX ADD MORE */ } } @@ -2088,7 +2101,7 @@ assert_cache_ok_(void) smartlist_pqueue_assert_ok(cached_resolve_pqueue, compare_cached_resolves_by_expiry_, - STRUCT_OFFSET(cached_resolve_t, minheap_idx)); + offsetof(cached_resolve_t, minheap_idx)); SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, { @@ -2102,7 +2115,7 @@ assert_cache_ok_(void) }); } -#endif +#endif /* defined(DEBUG_DNS_CACHE) */ cached_resolve_t * dns_get_cache_entry(cached_resolve_t *query) diff --git a/src/or/dns.h b/src/or/dns.h index a81cbd20da..28d9f947b4 100644 --- a/src/or/dns.h +++ b/src/or/dns.h @@ -64,7 +64,7 @@ set_exitconn_info_from_resolve,(edge_connection_t *exitconn, MOCK_DECL(STATIC int, launch_resolve,(cached_resolve_t *resolve)); -#endif +#endif /* defined(DNS_PRIVATE) */ -#endif +#endif /* !defined(TOR_DNS_H) */ diff --git a/src/or/dns_structs.h b/src/or/dns_structs.h index dc00e9f7b9..e22f23ac15 100644 --- a/src/or/dns_structs.h +++ b/src/or/dns_structs.h @@ -98,5 +98,5 @@ typedef struct cached_resolve_t { int minheap_idx; } cached_resolve_t; -#endif +#endif /* !defined(TOR_DNS_STRUCTS_H) */ diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index 54a22a5150..d254717a54 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -226,10 +226,10 @@ dnsserv_launch_request(const char *name, int reverse, TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); } -#else +#else /* !(defined(AF_UNIX)) */ TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); -#endif +#endif /* defined(AF_UNIX) */ if (reverse) entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; diff --git a/src/or/dnsserv.h b/src/or/dnsserv.h index 6c0643b8dc..2af366eee5 100644 --- a/src/or/dnsserv.h +++ b/src/or/dnsserv.h @@ -23,5 +23,5 @@ void dnsserv_reject_request(entry_connection_t *conn); int dnsserv_launch_request(const char *name, int is_reverse, control_connection_t *control_conn); -#endif +#endif /* !defined(TOR_DNSSERV_H) */ diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 2cbc8019d4..67b0259243 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -814,7 +814,7 @@ STATIC entry_guard_t * entry_guard_add_to_sample(guard_selection_t *gs, const node_t *node) { - log_info(LD_GUARD, "Adding %s as to the entry guard sample set.", + log_info(LD_GUARD, "Adding %s to the entry guard sample set.", node_describe(node)); /* make sure that the guard is not already sampled. */ @@ -1841,7 +1841,7 @@ entry_guards_update_primary(guard_selection_t *gs) bool_eq(guard->is_primary, smartlist_contains(new_primary_guards, guard))); }); -#endif +#endif /* 1 */ int any_change = 0; if (smartlist_len(gs->primary_entry_guards) != @@ -3136,20 +3136,34 @@ entry_list_is_constrained(const or_options_t *options) } /** Return the number of bridges that have descriptors that are marked with - * purpose 'bridge' and are running. - */ -int -num_bridges_usable(void) + * purpose 'bridge' and are running. If use_maybe_reachable is + * true, include bridges that might be reachable in the count. + * Otherwise, if it is false, only include bridges that have recently been + * found running in the count. + * + * 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 + * directory "server/authority" requests finish. */ +MOCK_IMPL(int, +num_bridges_usable,(int use_maybe_reachable)) { int n_options = 0; - tor_assert(get_options()->UseBridges); + if (BUG(!get_options()->UseBridges)) { + return 0; + } guard_selection_t *gs = get_guard_selection_info(); - tor_assert(gs->type == GS_TYPE_BRIDGE); + if (BUG(gs->type != GS_TYPE_BRIDGE)) { + return 0; + } SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + /* Definitely not usable */ if (guard->is_reachable == GUARD_REACHABLE_NO) continue; + /* If we want to be really sure the bridges will work, skip maybes */ + if (!use_maybe_reachable && guard->is_reachable == GUARD_REACHABLE_MAYBE) + continue; if (tor_digest_is_zero(guard->identity)) continue; const node_t *node = node_get_by_id(guard->identity); @@ -3538,15 +3552,20 @@ guards_retry_optimistic(const or_options_t *options) } /** - * Return true iff we know enough directory information to construct - * circuits through all of the primary guards we'd currently use. - */ -int -guard_selection_have_enough_dir_info_to_build_circuits(guard_selection_t *gs) + * Check if we are missing any crucial dirinfo for the guard subsystem to + * work. Return NULL if everything went well, otherwise return a newly + * allocated string with an informative error message. In the latter case, use + * the genreal descriptor information <b>using_mds</b>, <b>num_present</b> and + * <b>num_usable</b> to improve the error message. */ +char * +guard_selection_get_err_str_if_dir_info_missing(guard_selection_t *gs, + int using_mds, + int num_present, int num_usable) { if (!gs->primary_guards_up_to_date) entry_guards_update_primary(gs); + char *ret_str = NULL; int n_missing_descriptors = 0; int n_considered = 0; int num_primary_to_check; @@ -3568,16 +3587,30 @@ guard_selection_have_enough_dir_info_to_build_circuits(guard_selection_t *gs) break; } SMARTLIST_FOREACH_END(guard); - return n_missing_descriptors == 0; + /* If we are not missing any descriptors, return NULL. */ + if (!n_missing_descriptors) { + return NULL; + } + + /* otherwise return a helpful error string */ + tor_asprintf(&ret_str, "We're missing descriptors for %d/%d of our " + "primary entry guards (total %sdescriptors: %d/%d).", + n_missing_descriptors, num_primary_to_check, + using_mds?"micro":"", num_present, num_usable); + + return ret_str; } /** As guard_selection_have_enough_dir_info_to_build_circuits, but uses * the default guard selection. */ -int -entry_guards_have_enough_dir_info_to_build_circuits(void) -{ - return guard_selection_have_enough_dir_info_to_build_circuits( - get_guard_selection_info()); +char * +entry_guards_get_err_str_if_dir_info_missing(int using_mds, + int num_present, int num_usable) +{ + return guard_selection_get_err_str_if_dir_info_missing( + get_guard_selection_info(), + using_mds, + num_present, num_usable); } /** Free one guard selection context */ diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 39bff14381..d909115b1f 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -316,7 +316,7 @@ struct circuit_guard_state_t { */ entry_guard_restriction_t *restrictions; }; -#endif +#endif /* defined(ENTRYNODES_PRIVATE) */ /* Common entry points for old and new guard code */ int guards_update_all(void); @@ -342,7 +342,7 @@ int num_live_entry_guards_for_guard_selection( guard_selection_t *gs, int for_directory); int num_live_entry_guards(int for_directory); -#endif +#endif /* 1 */ const node_t *entry_guard_find_node(const entry_guard_t *guard); const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard); @@ -383,8 +383,7 @@ void entry_guards_note_internet_connectivity(guard_selection_t *gs); int update_guard_selection_choice(const or_options_t *options); -/* Used by bridges.c only. */ -int num_bridges_usable(void); +MOCK_DECL(int,num_bridges_usable,(int use_maybe_reachable)); #ifdef ENTRYNODES_PRIVATE /** @@ -558,14 +557,12 @@ STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs, STATIC int entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b); STATIC char *getinfo_helper_format_single_entry_guard(const entry_guard_t *e); -STATIC entry_guard_restriction_t * -guard_create_exit_restriction(const uint8_t *exit_id); +STATIC entry_guard_restriction_t *guard_create_exit_restriction( + const uint8_t *exit_id); -STATIC entry_guard_restriction_t * -guard_create_dirserver_md_restriction(void); +STATIC entry_guard_restriction_t *guard_create_dirserver_md_restriction(void); -STATIC void -entry_guard_restriction_free(entry_guard_restriction_t *rst); +STATIC void entry_guard_restriction_free(entry_guard_restriction_t *rst); #endif /* defined(ENTRYNODES_PRIVATE) */ @@ -589,9 +586,11 @@ int getinfo_helper_entry_guards(control_connection_t *conn, int entries_known_but_down(const or_options_t *options); void entries_retry_all(const or_options_t *options); -int guard_selection_have_enough_dir_info_to_build_circuits( - guard_selection_t *gs); -int entry_guards_have_enough_dir_info_to_build_circuits(void); +char *entry_guards_get_err_str_if_dir_info_missing(int using_mds, + int num_present, int num_usable); +char *guard_selection_get_err_str_if_dir_info_missing(guard_selection_t *gs, + int using_mds, + int num_present, int num_usable); void entry_guards_free_all(void); @@ -614,5 +613,5 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, int orig_bandwidth, uint32_t guardfraction_percentage); -#endif +#endif /* !defined(TOR_ENTRYNODES_H) */ diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c index b60d2e55c8..28377a3f36 100644 --- a/src/or/ext_orport.c +++ b/src/or/ext_orport.c @@ -23,15 +23,16 @@ #include "ext_orport.h" #include "control.h" #include "config.h" -#include "util.h" #include "main.h" +#include "proto_ext_or.h" +#include "util.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; + size_t size = offsetof(ext_or_cmd_t, body) + len; ext_or_cmd_t *cmd = tor_malloc(size); cmd->len = len; return cmd; @@ -69,10 +70,10 @@ connection_write_ext_or_command(connection_t *conn, return -1; set_uint16(header, htons(command)); set_uint16(header+2, htons(bodylen)); - connection_write_to_buf(header, 4, conn); + connection_buf_add(header, 4, conn); if (bodylen) { tor_assert(body); - connection_write_to_buf(body, bodylen, conn); + connection_buf_add(body, bodylen, conn); } return 0; } @@ -170,7 +171,7 @@ connection_ext_or_auth_neg_auth_type(connection_t *conn) if (connection_get_inbuf_len(conn) < 1) return 0; - if (connection_fetch_from_buf(authtype, 1, conn) < 0) + if (connection_buf_get_bytes(authtype, 1, conn) < 0) return -1; log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); @@ -310,7 +311,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) return 0; - if (connection_fetch_from_buf(client_nonce, + if (connection_buf_get_bytes(client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) return -1; @@ -325,7 +326,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) &reply, &reply_len) < 0) return -1; - connection_write_to_buf(reply, reply_len, conn); + connection_buf_add(reply, reply_len, conn); memwipe(reply, 0, reply_len); tor_free(reply); @@ -347,9 +348,9 @@ static void connection_ext_or_auth_send_result(connection_t *conn, int success) { if (success) - connection_write_to_buf("\x01", 1, conn); + connection_buf_add("\x01", 1, conn); else - connection_write_to_buf("\x00", 1, conn); + connection_buf_add("\x00", 1, conn); } /** Receive the client's hash from <b>conn</b>, validate that it's @@ -367,7 +368,7 @@ connection_ext_or_auth_handle_client_hash(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) return 0; - if (connection_fetch_from_buf(provided_client_hash, + if (connection_buf_get_bytes(provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) return -1; @@ -459,6 +460,11 @@ connection_ext_or_handle_cmd_useraddr(connection_t *conn, tor_free(addr_str); if (res<0) return -1; + if (port == 0) { + log_warn(LD_GENERAL, "Server transport proxy gave us an empty port " + "in ExtORPort UserAddr command."); + // return -1; // enable this if nothing breaks after a while. + } res = tor_addr_parse(&addr, address_part); tor_free(address_part); @@ -637,7 +643,7 @@ connection_ext_or_start_auth(or_connection_t *or_conn) log_debug(LD_GENERAL, "ExtORPort authentication: Sending supported authentication types"); - connection_write_to_buf((const char *)authtypes, sizeof(authtypes), conn); + connection_buf_add((const char *)authtypes, sizeof(authtypes), conn); conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; return 0; diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h index b2cd05db8f..af2b97e77c 100644 --- a/src/or/ext_orport.h +++ b/src/or/ext_orport.h @@ -36,7 +36,7 @@ STATIC int handle_client_auth_nonce(const char *client_nonce, extern uint8_t *ext_or_auth_cookie; extern int ext_or_auth_cookie_is_set; #endif -#endif +#endif /* defined(EXT_ORPORT_PRIVATE) */ -#endif +#endif /* !defined(EXT_ORPORT_H) */ diff --git a/src/or/fp_pair.h b/src/or/fp_pair.h index 4cea3eda6d..f7c060b459 100644 --- a/src/or/fp_pair.h +++ b/src/or/fp_pair.h @@ -41,5 +41,5 @@ void fp_pair_map_assert_ok(const fp_pair_map_t *map); #undef DECLARE_MAP_FNS -#endif +#endif /* !defined(_TOR_FP_PAIR_H) */ diff --git a/src/or/geoip.c b/src/or/geoip.c index 65d00b8659..3944b2cf69 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -1665,6 +1665,15 @@ getinfo_helper_geoip(control_connection_t *control_conn, sa_family_t family; tor_addr_t addr; question += strlen("ip-to-country/"); + + if (!strcmp(question, "ipv4-available") || + !strcmp(question, "ipv6-available")) { + family = !strcmp(question, "ipv4-available") ? AF_INET : AF_INET6; + const int available = geoip_is_loaded(family); + tor_asprintf(answer, "%d", !! available); + return 0; + } + family = tor_addr_parse(&addr, question); if (family != AF_INET && family != AF_INET6) { *errmsg = "Invalid address family"; diff --git a/src/or/geoip.h b/src/or/geoip.h index 55ca8ca28c..acf61b97ae 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -19,7 +19,7 @@ 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); STATIC void clear_geoip_db(void); -#endif +#endif /* defined(GEOIP_PRIVATE) */ int should_record_bridge_info(const or_options_t *options); int geoip_load_file(sa_family_t family, const char *filename); MOCK_DECL(int, geoip_get_country_by_addr, (const tor_addr_t *addr)); @@ -67,5 +67,5 @@ 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 +#endif /* !defined(TOR_GEOIP_H) */ diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 8c48a6f47d..74ab766468 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -1124,5 +1124,5 @@ hibernate_set_state_for_testing_(hibernate_state_t newstate) { hibernate_state = newstate; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/hibernate.h b/src/or/hibernate.h index 8bdb65a927..85fb42864b 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -53,7 +53,7 @@ typedef enum { #ifdef TOR_UNIT_TESTS void hibernate_set_state_for_testing_(hibernate_state_t newstate); #endif -#endif +#endif /* defined(HIBERNATE_PRIVATE) */ -#endif +#endif /* !defined(TOR_HIBERNATE_H) */ diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 29681b42b5..3ebe13fb4d 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -9,15 +9,22 @@ /* For unit tests.*/ #define HS_CACHE_PRIVATE -#include "hs_cache.h" - #include "or.h" #include "config.h" +#include "hs_ident.h" #include "hs_common.h" +#include "hs_client.h" #include "hs_descriptor.h" #include "networkstatus.h" #include "rendcache.h" +#include "hs_cache.h" + +static int cached_client_descriptor_has_expired(time_t now, + const hs_cache_client_descriptor_t *cached_desc); + +/********************** Directory HS cache ******************/ + /* Directory descriptor cache. Map indexed by blinded key. */ static digest256map_t *hs_cache_v3_dir; @@ -98,7 +105,7 @@ cache_dir_desc_new(const char *desc) /* Return the size of a cache entry in bytes. */ static size_t -cache_get_entry_size(const hs_cache_dir_descriptor_t *entry) +cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry) { return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data) + strlen(entry->encoded_desc)); @@ -124,15 +131,17 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) if (cache_entry->plaintext_data->revision_counter >= desc->plaintext_data->revision_counter) { log_info(LD_REND, "Descriptor revision counter in our cache is " - "greater or equal than the one we received. " - "Rejecting!"); + "greater or equal than the one we received (%d/%d). " + "Rejecting!", + (int)cache_entry->plaintext_data->revision_counter, + (int)desc->plaintext_data->revision_counter); goto err; } /* We now know that the descriptor we just received is a new one so * remove the entry we currently have from our cache so we can then * store the new one. */ remove_v3_desc_as_dir(cache_entry); - rend_cache_decrement_allocation(cache_get_entry_size(cache_entry)); + rend_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry)); cache_dir_desc_free(cache_entry); } /* Store the descriptor we just got. We are sure here that either we @@ -142,7 +151,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) /* Update our total cache size with this entry for the OOM. This uses the * old HS protocol cache subsystem for which we are tied with. */ - rend_cache_increment_allocation(cache_get_entry_size(desc)); + rend_cache_increment_allocation(cache_get_dir_entry_size(desc)); /* XXX: Update HS statistics. We should have specific stats for v3. */ @@ -219,7 +228,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff) } /* Here, our entry has expired, remove and free. */ MAP_DEL_CURRENT(key); - entry_size = cache_get_entry_size(entry); + entry_size = cache_get_dir_entry_size(entry); bytes_removed += entry_size; /* Entry is not in the cache anymore, destroy it. */ cache_dir_desc_free(entry); @@ -228,8 +237,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff) /* Logging. */ { char key_b64[BASE64_DIGEST256_LEN + 1]; - base64_encode(key_b64, sizeof(key_b64), (const char *) key, - DIGEST256_LEN, 0); + digest256_to_base64(key_b64, (const char *) key); log_info(LD_REND, "Removing v3 descriptor '%s' from HSDir cache", safe_str_client(key_b64)); } @@ -313,6 +321,536 @@ hs_cache_clean_as_dir(time_t now) cache_clean_v3_as_dir(now, 0); } +/********************** Client-side HS cache ******************/ + +/* Client-side HS descriptor cache. Map indexed by service identity key. */ +static digest256map_t *hs_cache_v3_client; + +/* Client-side introduction point state cache. Map indexed by service public + * identity key (onion address). It contains hs_cache_client_intro_state_t + * objects all related to a specific service. */ +static digest256map_t *hs_cache_client_intro_state; + +/* Return the size of a client cache entry in bytes. */ +static size_t +cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) +{ + return sizeof(*entry) + + strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); +} + +/* Remove a given descriptor from our cache. */ +static void +remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_remove(hs_cache_v3_client, desc->key.pubkey); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_decrement_allocation(cache_get_client_entry_size(desc)); +} + +/* Store a given descriptor in our cache. */ +static void +store_v3_desc_as_client(hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_increment_allocation(cache_get_client_entry_size(desc)); +} + +/* Query our cache and return the entry or NULL if not found or if expired. */ +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key) +{ + time_t now = approx_time(); + hs_cache_client_descriptor_t *cached_desc; + + tor_assert(key); + + /* Do the lookup */ + cached_desc = digest256map_get(hs_cache_v3_client, key); + if (!cached_desc) { + return NULL; + } + + /* Don't return expired entries */ + if (cached_client_descriptor_has_expired(now, cached_desc)) { + return NULL; + } + + return cached_desc; +} + +/* Parse the encoded descriptor in <b>desc_str</b> using + * <b>service_identity_pk<b> to decrypt it first. + * + * If everything goes well, allocate and return a new + * hs_cache_client_descriptor_t object. In case of error, return NULL. */ +static hs_cache_client_descriptor_t * +cache_client_desc_new(const char *desc_str, + const ed25519_public_key_t *service_identity_pk) +{ + hs_descriptor_t *desc = NULL; + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + + /* Decode the descriptor we just fetched. */ + if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) { + goto end; + } + tor_assert(desc); + + /* All is good: make a cache object for this descriptor */ + client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t)); + ed25519_pubkey_copy(&client_desc->key, service_identity_pk); + /* Set expiration time for this cached descriptor to be the start of the next + * time period since that's when clients need to start using the next blinded + * pk of the service (and hence will need its next descriptor). */ + client_desc->expiration_ts = hs_get_start_time_of_next_time_period(0); + client_desc->desc = desc; + client_desc->encoded_desc = tor_strdup(desc_str); + + end: + return client_desc; +} + +/** Free memory allocated by <b>desc</b>. */ +static void +cache_client_desc_free(hs_cache_client_descriptor_t *desc) +{ + if (desc == NULL) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->key, 0, sizeof(desc->key)); + memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc)); + tor_free(desc->encoded_desc); + tor_free(desc); +} + +/** Helper function: Use by the free all function to clear the client cache */ +static void +cache_client_desc_free_(void *ptr) +{ + hs_cache_client_descriptor_t *desc = ptr; + cache_client_desc_free(desc); +} + +/* Return a newly allocated and initialized hs_cache_intro_state_t object. */ +static hs_cache_intro_state_t * +cache_intro_state_new(void) +{ + hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state)); + state->created_ts = approx_time(); + return state; +} + +/* Free an hs_cache_intro_state_t object. */ +static void +cache_intro_state_free(hs_cache_intro_state_t *state) +{ + tor_free(state); +} + +/* Helper function: use by the free all function. */ +static void +cache_intro_state_free_(void *state) +{ + cache_intro_state_free(state); +} + +/* Return a newly allocated and initialized hs_cache_client_intro_state_t + * object. */ +static hs_cache_client_intro_state_t * +cache_client_intro_state_new(void) +{ + hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache)); + cache->intro_points = digest256map_new(); + return cache; +} + +/* Free a cache client intro state object. */ +static void +cache_client_intro_state_free(hs_cache_client_intro_state_t *cache) +{ + if (cache == NULL) { + return; + } + digest256map_free(cache->intro_points, cache_intro_state_free_); + tor_free(cache); +} + +/* Helper function: use by the free all function. */ +static void +cache_client_intro_state_free_(void *entry) +{ + cache_client_intro_state_free(entry); +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, lookup the intro state object. Return 1 if + * found and put it in entry if not NULL. Return 0 if not found and entry is + * untouched. */ +static int +cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **entry) +{ + hs_cache_intro_state_t *state; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the intro state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + goto not_found; + } + + /* From the cache we just found for the service, lookup in the introduction + * points map for the given authentication key. */ + state = digest256map_get(cache->intro_points, auth_key->pubkey); + if (state == NULL) { + goto not_found; + } + if (entry) { + *entry = state; + } + return 1; + not_found: + return 0; +} + +/* Note the given failure in state. */ +static void +cache_client_intro_state_note(hs_cache_intro_state_t *state, + rend_intro_point_failure_t failure) +{ + tor_assert(state); + switch (failure) { + case INTRO_POINT_FAILURE_GENERIC: + state->error = 1; + break; + case INTRO_POINT_FAILURE_TIMEOUT: + state->timed_out = 1; + break; + case INTRO_POINT_FAILURE_UNREACHABLE: + state->unreachable_count++; + break; + default: + tor_assert_nonfatal_unreached(); + return; + } +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, add an entry in the client intro state cache + * If no entry exists for the service, it will create one. If state is non + * NULL, it will point to the new intro state entry. */ +static void +cache_client_intro_state_add(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **state) +{ + hs_cache_intro_state_t *entry, *old_entry; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + cache = cache_client_intro_state_new(); + digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache); + } + + entry = cache_intro_state_new(); + old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry); + /* This should never happened because the code flow is to lookup the entry + * before adding it. But, just in case, non fatal assert and free it. */ + tor_assert_nonfatal(old_entry == NULL); + tor_free(old_entry); + + if (state) { + *state = entry; + } +} + +/* Remove every intro point state entry from cache that has been created + * before or at the cutoff. */ +static void +cache_client_intro_state_clean(time_t cutoff, + hs_cache_client_intro_state_t *cache) +{ + tor_assert(cache); + + DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key, + hs_cache_intro_state_t *, entry) { + if (entry->created_ts <= cutoff) { + cache_intro_state_free(entry); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Return true iff no intro points are in this cache. */ +static int +cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache) +{ + return digest256map_isempty(cache->intro_points); +} + +/** Check whether <b>client_desc</b> is useful for us, and store it in the + * client-side HS cache if so. The client_desc is freed if we already have a + * fresher (higher revision counter count) in the cache. */ +static int +cache_store_as_client(hs_cache_client_descriptor_t *client_desc) +{ + hs_cache_client_descriptor_t *cache_entry; + + /* TODO: Heavy code duplication with cache_store_as_dir(). Consider + * refactoring and uniting! */ + + tor_assert(client_desc); + + /* Check if we already have a descriptor from this HS in cache. If we do, + * check if this descriptor is newer than the cached one */ + cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey); + if (cache_entry != NULL) { + /* If we have an entry in our cache that has a revision counter greater + * than the one we just fetched, discard the one we fetched. */ + if (BUG(cache_entry->desc->plaintext_data.revision_counter > + client_desc->desc->plaintext_data.revision_counter)) { + cache_client_desc_free(client_desc); + goto done; + } + /* Remove old entry. Make space for the new one! */ + remove_v3_desc_as_client(cache_entry); + cache_client_desc_free(cache_entry); + } + + /* Store descriptor in cache */ + store_v3_desc_as_client(client_desc); + + done: + return 0; +} + +/* Return true iff the cached client descriptor at <b>cached_desc</b has + * expired. */ +static int +cached_client_descriptor_has_expired(time_t now, + const hs_cache_client_descriptor_t *cached_desc) +{ + /* We use the current consensus time to see if we should expire this + * descriptor since we use consensus time for all other parts of the protocol + * as well (e.g. to build the blinded key and compute time periods). */ + const networkstatus_t *ns = networkstatus_get_live_consensus(now); + /* If we don't have a recent consensus, consider this entry expired since we + * will want to fetch a new HS desc when we get a live consensus. */ + if (!ns) { + return 1; + } + + if (cached_desc->expiration_ts <= ns->valid_after) { + return 1; + } + + return 0; +} + +/* clean the client cache using now as the current time. Return the total size + * of removed bytes from the cache. */ +static size_t +cache_clean_v3_as_client(time_t now) +{ + size_t bytes_removed = 0; + + if (!hs_cache_v3_client) { /* No cache to clean. Just return. */ + return 0; + } + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size; + + /* If the entry has not expired, continue to the next cached entry */ + if (!cached_client_descriptor_has_expired(now, entry)) { + continue; + } + /* Here, our entry has expired, remove and free. */ + MAP_DEL_CURRENT(key); + entry_size = cache_get_client_entry_size(entry); + bytes_removed += entry_size; + /* Entry is not in the cache anymore, destroy it. */ + cache_client_desc_free(entry); + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ + rend_cache_decrement_allocation(entry_size); + /* Logging. */ + { + char key_b64[BASE64_DIGEST256_LEN + 1]; + digest256_to_base64(key_b64, (const char *) key); + log_info(LD_REND, "Removing hidden service v3 descriptor '%s' " + "from client cache", + safe_str_client(key_b64)); + } + } DIGEST256MAP_FOREACH_END; + + return bytes_removed; +} + +/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return + * its HS descriptor if it's stored in our cache, or NULL if not. */ +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key) +{ + hs_cache_client_descriptor_t *cached_desc = NULL; + + tor_assert(key); + + cached_desc = lookup_v3_desc_as_client(key->pubkey); + if (cached_desc) { + tor_assert(cached_desc->desc); + return cached_desc->desc; + } + + return NULL; +} + +/** Public API: Given an encoded descriptor, store it in the client HS + * cache. Return -1 on error, 0 on success .*/ +int +hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk) +{ + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(identity_pk); + + /* Create client cache descriptor object */ + client_desc = cache_client_desc_new(desc_str, identity_pk); + if (!client_desc) { + log_warn(LD_GENERAL, "Failed to parse received descriptor %s.", + escaped(desc_str)); + goto err; + } + + /* Push it to the cache */ + if (cache_store_as_client(client_desc) < 0) { + goto err; + } + + return 0; + + err: + cache_client_desc_free(client_desc); + return -1; +} + +/* Clean all client caches using the current time now. */ +void +hs_cache_clean_as_client(time_t now) +{ + /* Start with v2 cache cleaning. */ + rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); + /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function + * to compute the cutoff by itself using the lifetime value. */ + cache_clean_v3_as_client(now); +} + +/* Purge the client descriptor cache. */ +void +hs_cache_purge_as_client(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size = cache_get_client_entry_size(entry); + MAP_DEL_CURRENT(key); + cache_client_desc_free(entry); + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ + rend_cache_decrement_allocation(entry_size); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client descriptor cache purged."); +} + +/* For a given service identity public key and an introduction authentication + * key, note the given failure in the client intro state cache. */ +void +hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure) +{ + int found; + hs_cache_intro_state_t *entry; + + tor_assert(service_pk); + tor_assert(auth_key); + + found = cache_client_intro_state_lookup(service_pk, auth_key, &entry); + if (!found) { + /* Create a new entry and add it to the cache. */ + cache_client_intro_state_add(service_pk, auth_key, &entry); + } + /* Note down the entry. */ + cache_client_intro_state_note(entry, failure); +} + +/* For a given service identity public key and an introduction authentication + * key, return true iff it is present in the failure cache. */ +const hs_cache_intro_state_t * +hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key) +{ + hs_cache_intro_state_t *state = NULL; + cache_client_intro_state_lookup(service_pk, auth_key, &state); + return state; +} + +/* Cleanup the client introduction state cache. */ +void +hs_cache_client_intro_state_clean(time_t now) +{ + time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE; + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + /* Cleanup intro points failure. */ + cache_client_intro_state_clean(cutoff, cache); + + /* Is this cache empty for this service key? If yes, remove it from the + * cache. Else keep it. */ + if (cache_client_intro_state_is_empty(cache)) { + cache_client_intro_state_free(cache); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Purge the client introduction state cache. */ +void +hs_cache_client_intro_state_purge(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + MAP_DEL_CURRENT(key); + cache_client_intro_state_free(cache); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client introduction point state " + "cache purged."); +} + +/**************** Generics *********************************/ + /* Do a round of OOM cleanup on all directory caches. Return the amount of * removed bytes. It is possible that the returned value is lower than * min_remove_bytes if the caches get emptied out so the caller should be @@ -367,10 +905,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes) return bytes_removed; } -/** - * Return the maximum size of an HS descriptor we are willing to accept as an - * HSDir. - */ +/* Return the maximum size of a v3 HS descriptor. */ unsigned int hs_cache_get_max_descriptor_size(void) { @@ -386,6 +921,12 @@ hs_cache_init(void) /* Calling this twice is very wrong code flow. */ tor_assert(!hs_cache_v3_dir); hs_cache_v3_dir = digest256map_new(); + + tor_assert(!hs_cache_v3_client); + hs_cache_v3_client = digest256map_new(); + + tor_assert(!hs_cache_client_intro_state); + hs_cache_client_intro_state = digest256map_new(); } /* Cleanup the hidden service cache subsystem. */ @@ -394,5 +935,12 @@ hs_cache_free_all(void) { digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_); hs_cache_v3_dir = NULL; + + digest256map_free(hs_cache_v3_client, cache_client_desc_free_); + hs_cache_v3_client = NULL; + + digest256map_free(hs_cache_client_intro_state, + cache_client_intro_state_free_); + hs_cache_client_intro_state = NULL; } diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index ed00424234..2dcc518a71 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -15,8 +15,34 @@ #include "crypto_ed25519.h" #include "hs_common.h" #include "hs_descriptor.h" +#include "rendcommon.h" #include "torcert.h" +/* This is the maximum time an introduction point state object can stay in the + * client cache in seconds (2 mins or 120 seconds). */ +#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60) + +/* Introduction point state. */ +typedef struct hs_cache_intro_state_t { + /* When this entry was created and put in the cache. */ + time_t created_ts; + + /* Did it suffered a generic error? */ + unsigned int error : 1; + + /* Did it timed out? */ + unsigned int timed_out : 1; + + /* How many times we tried to reached it and it was unreachable. */ + uint32_t unreachable_count; +} hs_cache_intro_state_t; + +typedef struct hs_cache_client_intro_state_t { + /* Contains hs_cache_intro_state_t object indexed by introduction point + * authentication key. */ + digest256map_t *intro_points; +} hs_cache_client_intro_state_t; + /* Descriptor representation on the directory side which is a subset of * information that the HSDir can decode and serve it. */ typedef struct hs_cache_dir_descriptor_t { @@ -53,11 +79,49 @@ int hs_cache_store_as_dir(const char *desc); int hs_cache_lookup_as_dir(uint32_t version, const char *query, const char **desc_out); +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key); +int hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk); +void hs_cache_clean_as_client(time_t now); +void hs_cache_purge_as_client(void); + +/* Client failure cache. */ +void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure); +const hs_cache_intro_state_t *hs_cache_client_intro_state_find( + const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key); +void hs_cache_client_intro_state_clean(time_t now); +void hs_cache_client_intro_state_purge(void); + #ifdef HS_CACHE_PRIVATE +/** Represents a locally cached HS descriptor on a hidden service client. */ +typedef struct hs_cache_client_descriptor_t { + /* This object is indexed using the service identity public key */ + ed25519_public_key_t key; + + /* When will this entry expire? We expire cached client descriptors in the + * start of the next time period, since that's when clients need to start + * using the next blinded key of the service. */ + time_t expiration_ts; + + /* The cached descriptor, this object is the owner. It can't be NULL. A + * cache object without a valid descriptor is not possible. */ + hs_descriptor_t *desc; + + /* Encoded descriptor in string form. Can't be NULL. */ + char *encoded_desc; +} hs_cache_client_descriptor_t; + STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff); -#endif /* HS_CACHE_PRIVATE */ +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key); + +#endif /* defined(HS_CACHE_PRIVATE) */ -#endif /* TOR_HS_CACHE_H */ +#endif /* !defined(TOR_HS_CACHE_H) */ diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c new file mode 100644 index 0000000000..5244cfa3dd --- /dev/null +++ b/src/or/hs_cell.c @@ -0,0 +1,948 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.c + * \brief Hidden service API for cell creation and handling. + **/ + +#include "or.h" +#include "config.h" +#include "rendservice.h" +#include "replaycache.h" +#include "util.h" + +#include "hs_cell.h" +#include "hs_ntor.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" +#include "hs/cell_introduce1.h" +#include "hs/cell_rendezvous.h" + +/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is + * the cell content up to the ENCRYPTED section of length encoded_cell_len. + * The encrypted param is the start of the ENCRYPTED section of length + * encrypted_len. The mac_key is the key needed for the computation of the MAC + * derived from the ntor handshake of length mac_key_len. + * + * The length mac_out_len must be at least DIGEST256_LEN. */ +static void +compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len, + const uint8_t *encrypted, size_t encrypted_len, + const uint8_t *mac_key, size_t mac_key_len, + uint8_t *mac_out, size_t mac_out_len) +{ + size_t offset = 0; + size_t mac_msg_len; + uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(encoded_cell); + tor_assert(encrypted); + tor_assert(mac_key); + tor_assert(mac_out); + tor_assert(mac_out_len >= DIGEST256_LEN); + + /* Compute the size of the message which is basically the entire cell until + * the MAC field of course. */ + mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN); + tor_assert(mac_msg_len <= sizeof(mac_msg)); + + /* First, put the encoded cell in the msg. */ + memcpy(mac_msg, encoded_cell, encoded_cell_len); + offset += encoded_cell_len; + /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which + * is junk at this point). */ + memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN)); + offset += (encrypted_len - DIGEST256_LEN); + tor_assert(offset == mac_msg_len); + + crypto_mac_sha3_256(mac_out, mac_out_len, + mac_key, mac_key_len, + mac_msg, mac_msg_len); + memwipe(mac_msg, 0, sizeof(mac_msg)); +} + +/* From a set of keys, subcredential and the ENCRYPTED section of an + * INTRODUCE2 cell, return a newly allocated intro cell keys structure. + * Finally, the client public key is copied in client_pk. On error, return + * NULL. */ +static hs_ntor_intro_cell_keys_t * +get_introduce2_key_material(const ed25519_public_key_t *auth_key, + const curve25519_keypair_t *enc_key, + const uint8_t *subcredential, + const uint8_t *encrypted_section, + curve25519_public_key_t *client_pk) +{ + hs_ntor_intro_cell_keys_t *keys; + + tor_assert(auth_key); + tor_assert(enc_key); + tor_assert(subcredential); + tor_assert(encrypted_section); + tor_assert(client_pk); + + keys = tor_malloc_zero(sizeof(*keys)); + + /* First bytes of the ENCRYPTED section are the client public key. */ + memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN); + + if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk, + subcredential, keys) < 0) { + /* Don't rely on the caller to wipe this on error. */ + memwipe(client_pk, 0, sizeof(curve25519_public_key_t)); + tor_free(keys); + keys = NULL; + } + return keys; +} + +/* Using the given encryption key, decrypt the encrypted_section of length + * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated + * buffer containing the decrypted data. On decryption failure, NULL is + * returned. */ +static uint8_t * +decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section, + size_t encrypted_section_len) +{ + uint8_t *decrypted = NULL; + crypto_cipher_t *cipher = NULL; + + tor_assert(enc_key); + tor_assert(encrypted_section); + + /* Decrypt ENCRYPTED section. */ + cipher = crypto_cipher_new_with_bits((char *) enc_key, + CURVE25519_PUBKEY_LEN * 8); + tor_assert(cipher); + + /* This is symmetric encryption so can't be bigger than the encrypted + * section length. */ + decrypted = tor_malloc_zero(encrypted_section_len); + if (crypto_cipher_decrypt(cipher, (char *) decrypted, + (const char *) encrypted_section, + encrypted_section_len) < 0) { + tor_free(decrypted); + decrypted = NULL; + goto done; + } + + done: + crypto_cipher_free(cipher); + return decrypted; +} + +/* Given a pointer to the decrypted data of the ENCRYPTED section of an + * INTRODUCE2 cell of length decrypted_len, parse and validate the cell + * content. Return a newly allocated cell structure or NULL on error. The + * circuit and service object are only used for logging purposes. */ +static trn_cell_introduce_encrypted_t * +parse_introduce2_encrypted(const uint8_t *decrypted_data, + size_t decrypted_len, const origin_circuit_t *circ, + const hs_service_t *service) +{ + trn_cell_introduce_encrypted_t *enc_cell = NULL; + + tor_assert(decrypted_data); + tor_assert(circ); + tor_assert(service); + + if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data, + decrypted_len) < 0) { + log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of " + "the INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) != + HS_CELL_ONION_KEY_TYPE_NTOR) { + log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but " + "expected %u on circuit %u for service %s", + trn_cell_introduce_encrypted_get_onion_key_type(enc_cell), + HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) != + CURVE25519_PUBKEY_LEN) { + log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but " + "expected %d on circuit %u for service %s", + (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* XXX: Validate NSPEC field as well. */ + + return enc_cell; + err: + trn_cell_introduce_encrypted_free(enc_cell); + return NULL; +} + +/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA + * encryption key. The encoded cell is put in cell_out that MUST at least be + * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on + * success else a negative value and cell_out is untouched. */ +static ssize_t +build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, + uint8_t *cell_out) +{ + ssize_t cell_len; + + tor_assert(circ_nonce); + tor_assert(enc_key); + tor_assert(cell_out); + + memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE); + + cell_len = rend_service_encode_establish_intro_cell((char*)cell_out, + RELAY_PAYLOAD_SIZE, + enc_key, circ_nonce); + return cell_len; +} + +/* Parse an INTRODUCE2 cell from payload of size payload_len for the given + * service and circuit which are used only for logging purposes. The resulting + * parsed cell is put in cell_ptr_out. + * + * This function only parses prop224 INTRODUCE2 cells even when the intro point + * is a legacy intro point. That's because intro points don't actually care + * about the contents of the introduce cell. Legacy INTRODUCE cells are only + * used by the legacy system now. + * + * Return 0 on success else a negative value and cell_ptr_out is untouched. */ +static int +parse_introduce2_cell(const hs_service_t *service, + const origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len, + trn_cell_introduce1_t **cell_ptr_out) +{ + trn_cell_introduce1_t *cell = NULL; + + tor_assert(service); + tor_assert(circ); + tor_assert(payload); + tor_assert(cell_ptr_out); + + /* Parse the cell so we can start cell validation. */ + if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) { + log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Success. */ + *cell_ptr_out = cell; + return 0; + err: + return -1; +} + +/* Set the onion public key onion_pk in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell, + const uint8_t *onion_pk) +{ + tor_assert(cell); + tor_assert(onion_pk); + /* There is only one possible key type for a non legacy cell. */ + trn_cell_introduce_encrypted_set_onion_key_type(cell, + HS_CELL_ONION_KEY_TYPE_NTOR); + trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN); + trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN); + memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk, + trn_cell_introduce_encrypted_getlen_onion_key(cell)); +} + +/* Set the link specifiers in lspecs in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell, + const smartlist_t *lspecs) +{ + tor_assert(cell); + tor_assert(lspecs); + tor_assert(smartlist_len(lspecs) > 0); + tor_assert(smartlist_len(lspecs) <= UINT8_MAX); + + uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs); + trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num); + /* We aren't duplicating the link specifiers object here which means that + * the ownership goes to the trn_cell_introduce_encrypted_t cell and those + * object will be freed when the cell is. */ + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, + trn_cell_introduce_encrypted_add_nspecs(cell, ls)); +} + +/* Set padding in the enc_cell only if needed that is the total length of both + * sections are below the mininum required for an INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell, + trn_cell_introduce_encrypted_t *enc_cell) +{ + tor_assert(cell); + tor_assert(enc_cell); + /* This is the length we expect to have once encoded of the whole cell. */ + ssize_t full_len = trn_cell_introduce1_encoded_len(cell) + + trn_cell_introduce_encrypted_encoded_len(enc_cell); + tor_assert(full_len > 0); + if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) { + size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len; + trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding); + memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0, + trn_cell_introduce_encrypted_getlen_pad(enc_cell)); + } +} + +/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell + * and the INTRODUCE1 data. + * + * This can't fail but it is very important that the caller sets every field + * in data so the computation of the INTRODUCE1 keys doesn't fail. */ +static void +introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell, + const trn_cell_introduce_encrypted_t *enc_cell, + const hs_cell_introduce1_data_t *data) +{ + size_t offset = 0; + ssize_t encrypted_len; + ssize_t encoded_cell_len, encoded_enc_cell_len; + uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t *encrypted = NULL; + uint8_t mac[DIGEST256_LEN]; + crypto_cipher_t *cipher = NULL; + hs_ntor_intro_cell_keys_t keys; + + tor_assert(cell); + tor_assert(enc_cell); + tor_assert(data); + + /* Encode the cells up to now of what we have to we can perform the MAC + * computation on it. */ + encoded_cell_len = trn_cell_introduce1_encode(encoded_cell, + sizeof(encoded_cell), cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_cell_len > 0); + + encoded_enc_cell_len = + trn_cell_introduce_encrypted_encode(encoded_enc_cell, + sizeof(encoded_enc_cell), enc_cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_enc_cell_len > 0); + + /* Get the key material for the encryption. */ + if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk, + data->client_kp, + data->subcredential, &keys) < 0) { + tor_assert_unreached(); + } + + /* Prepare cipher with the encryption key just computed. */ + cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key, + sizeof(keys.enc_key) * 8); + tor_assert(cipher); + + /* Compute the length of the ENCRYPTED section which is the CLIENT_PK, + * ENCRYPTED_DATA and MAC length. */ + encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len + + sizeof(mac); + tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE); + encrypted = tor_malloc_zero(encrypted_len); + + /* Put the CLIENT_PK first. */ + memcpy(encrypted, data->client_kp->pubkey.public_key, + sizeof(data->client_kp->pubkey.public_key)); + offset += sizeof(data->client_kp->pubkey.public_key); + /* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */ + crypto_cipher_encrypt(cipher, (char *) encrypted + offset, + (const char *) encoded_enc_cell, encoded_enc_cell_len); + crypto_cipher_free(cipher); + offset += encoded_enc_cell_len; + /* Compute MAC from the above and put it in the buffer. This function will + * make the adjustment to the encryptled_len to ommit the MAC length. */ + compute_introduce_mac(encoded_cell, encoded_cell_len, + encrypted, encrypted_len, + keys.mac_key, sizeof(keys.mac_key), + mac, sizeof(mac)); + memcpy(encrypted + offset, mac, sizeof(mac)); + offset += sizeof(mac); + tor_assert(offset == (size_t) encrypted_len); + + /* Set the ENCRYPTED section in the cell. */ + trn_cell_introduce1_setlen_encrypted(cell, encrypted_len); + memcpy(trn_cell_introduce1_getarray_encrypted(cell), + encrypted, encrypted_len); + + /* Cleanup. */ + memwipe(&keys, 0, sizeof(keys)); + memwipe(mac, 0, sizeof(mac)); + memwipe(encrypted, 0, sizeof(encrypted_len)); + memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell)); + tor_free(encrypted); +} + +/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means + * set it, encrypt it and encode it. */ +static void +introduce1_set_encrypted(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + trn_cell_introduce_encrypted_t *enc_cell; + trn_cell_extension_t *ext; + + tor_assert(cell); + tor_assert(data); + + enc_cell = trn_cell_introduce_encrypted_new(); + tor_assert(enc_cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce_encrypted_set_extensions(enc_cell, ext); + + /* Set the rendezvous cookie. */ + memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell), + data->rendezvous_cookie, REND_COOKIE_LEN); + + /* Set the onion public key. */ + introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key); + + /* Set the link specifiers. */ + introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers); + + /* Set padding. */ + introduce1_set_encrypted_padding(cell, enc_cell); + + /* Encrypt and encode it in the cell. */ + introduce1_encrypt_and_encode(cell, enc_cell, data); + + /* Cleanup. */ + trn_cell_introduce_encrypted_free(enc_cell); +} + +/* Set the authentication key in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_auth_key(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + /* There is only one possible type for a non legacy cell. */ + trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519); + trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN); + trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN); + memcpy(trn_cell_introduce1_getarray_auth_key(cell), + data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell)); +} + +/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_legacy_id(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + + if (data->is_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) { + return; + } + memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell), + digest, trn_cell_introduce1_getlen_legacy_key_id(cell)); + } else { + /* We have to zeroed the LEGACY_KEY_ID field. */ + memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0, + trn_cell_introduce1_getlen_legacy_key_id(cell)); + } +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point + * object. The encoded cell is put in cell_out that MUST at least be of the + * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else + * a negative value and cell_out is untouched. This function also supports + * legacy cell creation. */ +ssize_t +hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out) +{ + ssize_t cell_len = -1; + uint16_t sig_len = ED25519_SIG_LEN; + trn_cell_extension_t *ext; + trn_cell_establish_intro_t *cell = NULL; + + tor_assert(circ_nonce); + tor_assert(ip); + + /* Quickly handle the legacy IP. */ + if (ip->base.is_only_legacy) { + tor_assert(ip->legacy_key); + cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key, + cell_out); + tor_assert(cell_len <= RELAY_PAYLOAD_SIZE); + /* Success or not we are done here. */ + goto done; + } + + /* Set extension data. None used here. */ + ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + cell = trn_cell_establish_intro_new(); + trn_cell_establish_intro_set_extensions(cell, ext); + /* Set signature size. Array is then allocated in the cell. We need to do + * this early so we can use trunnel API to get the signature length. */ + trn_cell_establish_intro_set_sig_len(cell, sig_len); + trn_cell_establish_intro_setlen_sig(cell, sig_len); + + /* Set AUTH_KEY_TYPE: 2 means ed25519 */ + trn_cell_establish_intro_set_auth_key_type(cell, + HS_INTRO_AUTH_KEY_TYPE_ED25519); + + /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of + * AUTH_KEY to match */ + { + uint16_t auth_key_len = ED25519_PUBKEY_LEN; + trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); + /* We do this call _after_ setting the length because it's reallocated at + * that point only. */ + uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); + memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len); + } + + /* Calculate HANDSHAKE_AUTH field (MAC). */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_mac_offset = + sig_len + sizeof(cell->sig_len) + + trn_cell_establish_intro_getlen_handshake_mac(cell); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr; + + /* We first encode the current fields we have in the cell so we can + * compute the MAC using the raw bytes. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + /* Sanity check. */ + tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset); + + /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */ + crypto_mac_sha3_256(mac, sizeof(mac), + (uint8_t *) circ_nonce, DIGEST_LEN, + tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset); + handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell); + memcpy(handshake_ptr, mac, sizeof(mac)); + + memwipe(mac, 0, sizeof(mac)); + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Calculate the cell signature SIG. */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len)); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr; + ed25519_signature_t sig; + + /* We first encode the current fields we have in the cell so we can + * compute the signature from the raw bytes of the cell. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + + if (ed25519_sign_prefixed(&sig, tmp_cell_enc, + tmp_cell_enc_len - tmp_cell_sig_offset, + ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) { + log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell."); + goto done; + } + /* Copy the signature into the cell. */ + sig_ptr = trn_cell_establish_intro_getarray_sig(cell); + memcpy(sig_ptr, sig.sig, sig_len); + + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Encode the cell. Can't be bigger than a standard cell. */ + cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE, + cell); + + done: + trn_cell_establish_intro_free(cell); + return cell_len; +} + +/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we + * are successful at parsing it, return the length of the parsed cell else a + * negative value on error. */ +ssize_t +hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) +{ + ssize_t ret; + trn_cell_intro_established_t *cell = NULL; + + tor_assert(payload); + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. */ + ret = trn_cell_intro_established_parse(&cell, payload, payload_len); + if (ret >= 0) { + /* On success, we do not keep the cell, we just notify the caller that it + * was successfully parsed. */ + trn_cell_intro_established_free(cell); + } + return ret; +} + +/* Parsse the INTRODUCE2 cell using data which contains everything we need to + * do so and contains the destination buffers of information we extract and + * compute from the cell. Return 0 on success else a negative value. The + * service and circ are only used for logging purposes. */ +ssize_t +hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service) +{ + int ret = -1; + time_t elapsed; + uint8_t *decrypted = NULL; + size_t encrypted_section_len; + const uint8_t *encrypted_section; + trn_cell_introduce1_t *cell = NULL; + trn_cell_introduce_encrypted_t *enc_cell = NULL; + hs_ntor_intro_cell_keys_t *intro_keys = NULL; + + tor_assert(data); + tor_assert(circ); + tor_assert(service); + + /* Parse the cell into a decoded data structure pointed by cell_ptr. */ + if (parse_introduce2_cell(service, circ, data->payload, data->payload_len, + &cell) < 0) { + goto done; + } + + log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u " + "for service %s. Decoding encrypted section...", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + + encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell); + encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell); + + /* Encrypted section must at least contain the CLIENT_PK and MAC which is + * defined in section 3.3.2 of the specification. */ + if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length " + "for service %s. Dropping cell.", + safe_str_client(service->onion_address)); + goto done; + } + + /* Check our replay cache for this introduction point. */ + if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section, + encrypted_section_len, &elapsed)) { + log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the" + "same ENCRYPTED section was seen %ld seconds ago. " + "Dropping cell.", (long int) elapsed); + goto done; + } + + /* Build the key material out of the key material found in the cell. */ + intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp, + data->subcredential, + encrypted_section, + &data->client_pk); + if (intro_keys == NULL) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to " + "compute key material on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Validate MAC from the cell and our computed key material. The MAC field + * in the cell is at the end of the encrypted section. */ + { + uint8_t mac[DIGEST256_LEN]; + /* The MAC field is at the very end of the ENCRYPTED section. */ + size_t mac_offset = encrypted_section_len - sizeof(mac); + /* Compute the MAC. Use the entire encoded payload with a length up to the + * ENCRYPTED section. */ + compute_introduce_mac(data->payload, + data->payload_len - encrypted_section_len, + encrypted_section, encrypted_section_len, + intro_keys->mac_key, sizeof(intro_keys->mac_key), + mac, sizeof(mac)); + if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) { + log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + } + + { + /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */ + const uint8_t *encrypted_data = + encrypted_section + sizeof(data->client_pk); + /* It's symmetric encryption so it's correct to use the ENCRYPTED length + * for decryption. Computes the length of ENCRYPTED_DATA meaning removing + * the CLIENT_PK and MAC length. */ + size_t encrypted_data_len = + encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN); + + /* This decrypts the ENCRYPTED_DATA section of the cell. */ + decrypted = decrypt_introduce2(intro_keys->enc_key, + encrypted_data, encrypted_data_len); + if (decrypted == NULL) { + log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an " + "INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Parse this blob into an encrypted cell structure so we can then extract + * the data we need out of it. */ + enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len, + circ, service); + memwipe(decrypted, 0, encrypted_data_len); + if (enc_cell == NULL) { + goto done; + } + } + + /* XXX: Implement client authorization checks. */ + + /* Extract onion key and rendezvous cookie from the cell used for the + * rendezvous point circuit e2e encryption. */ + memcpy(data->onion_pk.public_key, + trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN); + memcpy(data->rendezvous_cookie, + trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell), + sizeof(data->rendezvous_cookie)); + + /* Extract rendezvous link specifiers. */ + for (size_t idx = 0; + idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) { + link_specifier_t *lspec = + trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx); + smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec)); + } + + /* Success. */ + ret = 0; + log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit."); + + done: + if (intro_keys) { + memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t)); + tor_free(intro_keys); + } + tor_free(decrypted); + trn_cell_introduce_encrypted_free(enc_cell); + trn_cell_introduce1_free(cell); + return ret; +} + +/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake + * info. The encoded cell is put in cell_out and the length of the data is + * returned. This can't fail. */ +ssize_t +hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_rendezvous1_t *cell; + + tor_assert(rendezvous_cookie); + tor_assert(rendezvous_handshake_info); + tor_assert(cell_out); + + cell = trn_cell_rendezvous1_new(); + /* Set the RENDEZVOUS_COOKIE. */ + memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell), + rendezvous_cookie, rendezvous_cookie_len); + /* Set the HANDSHAKE_INFO. */ + trn_cell_rendezvous1_setlen_handshake_info(cell, + rendezvous_handshake_info_len); + memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell), + rendezvous_handshake_info, rendezvous_handshake_info_len); + /* Encoding. */ + cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + tor_assert(cell_len > 0); + + trn_cell_rendezvous1_free(cell); + return cell_len; +} + +/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in + * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the + * encoded length is returned else a negative value and the content of + * cell_out should be ignored. */ +ssize_t +hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_introduce1_t *cell; + trn_cell_extension_t *ext; + + tor_assert(data); + tor_assert(cell_out); + + cell = trn_cell_introduce1_new(); + tor_assert(cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce1_set_extensions(cell, ext); + + /* Set the legacy ID field. */ + introduce1_set_legacy_id(cell, data); + + /* Set the authentication key. */ + introduce1_set_auth_key(cell, data); + + /* Set the encrypted section. This will set, encrypt and encode the + * ENCRYPTED section in the cell. After this, we'll be ready to encode. */ + introduce1_set_encrypted(cell, data); + + /* Final encoding. */ + cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + + trn_cell_introduce1_free(cell); + return cell_len; +} + +/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The + * encoded cell is put in cell_out which must be of at least + * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the + * caller should clear up the content of the cell. + * + * This function can't fail. */ +ssize_t +hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out) +{ + tor_assert(rendezvous_cookie); + tor_assert(cell_out); + + memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN); + return HS_REND_COOKIE_LEN; +} + +/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len. + * Return the status code on success else a negative value if the cell as not + * decodable. */ +int +hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + trn_cell_introduce_ack_t *cell = NULL; + + tor_assert(payload); + + /* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a + * NACK is 1 byte. We can't use the legacy function for this so we have to + * do a special case. */ + if (payload_len <= 1) { + if (payload_len == 0) { + ret = HS_CELL_INTRO_ACK_SUCCESS; + } else { + ret = HS_CELL_INTRO_ACK_FAILURE; + } + goto end; + } + + if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it."); + goto end; + } + + ret = trn_cell_introduce_ack_get_status(cell); + + end: + trn_cell_introduce_ack_free(cell); + return ret; +} + +/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On + * success, handshake_info contains the data in the HANDSHAKE_INFO field, and + * 0 is returned. On error, a negative value is returned. */ +int +hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, size_t handshake_info_len) +{ + int ret = -1; + trn_cell_rendezvous2_t *cell = NULL; + + tor_assert(payload); + tor_assert(handshake_info); + + if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it."); + goto end; + } + + /* Static size, we should never have an issue with this else we messed up + * our code flow. */ + tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) == + handshake_info_len); + memcpy(handshake_info, + trn_cell_rendezvous2_getconstarray_handshake_info(cell), + handshake_info_len); + ret = 0; + + end: + trn_cell_rendezvous2_free(cell); + return ret; +} + +/* Clear the given INTRODUCE1 data structure data. */ +void +hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data) +{ + if (data == NULL) { + return; + } + /* Object in this list have been moved to the cell object when building it + * so they've been freed earlier. We do that in order to avoid duplicating + * them leading to more memory and CPU time being used for nothing. */ + smartlist_free(data->link_specifiers); + /* The data object has no ownership of any members. */ + memwipe(data, 0, sizeof(hs_cell_introduce1_data_t)); +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h new file mode 100644 index 0000000000..958dde4ffc --- /dev/null +++ b/src/or/hs_cell.h @@ -0,0 +1,122 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.h + * \brief Header file containing cell data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CELL_H +#define TOR_HS_CELL_H + +#include "or.h" +#include "hs_service.h" + +/* An INTRODUCE1 cell requires at least this amount of bytes (see section + * 3.2.2 of the specification). Below this value, the cell must be padded. */ +#define HS_CELL_INTRODUCE1_MIN_SIZE 246 + +/* Status code of an INTRODUCE_ACK cell. */ +typedef enum { + HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */ + HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */ + HS_CELL_INTRO_ACK_BADFMT = 0x0002, /* Bad message format */ + HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */ +} hs_cell_introd_ack_status_t; + +/* Onion key type found in the INTRODUCE1 cell. */ +typedef enum { + HS_CELL_ONION_KEY_TYPE_NTOR = 1, +} hs_cell_onion_key_type_t; + +/* This data structure contains data that we need to build an INTRODUCE1 cell + * used by the INTRODUCE1 build function. */ +typedef struct hs_cell_introduce1_data_t { + /* Is this a legacy introduction point? */ + unsigned int is_legacy : 1; + /* (Legacy only) The encryption key for a legacy intro point. Only set if + * is_legacy is true. */ + const crypto_pk_t *legacy_key; + /* Introduction point authentication public key. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption public key. */ + const curve25519_public_key_t *enc_pk; + /* Subcredentials of the service. */ + const uint8_t *subcredential; + /* Onion public key for the ntor handshake. */ + const curve25519_public_key_t *onion_pk; + /* Rendezvous cookie. */ + const uint8_t *rendezvous_cookie; + /* Public key put before the encrypted data (CLIENT_PK). */ + const curve25519_keypair_t *client_kp; + /* Rendezvous point link specifiers. */ + smartlist_t *link_specifiers; +} hs_cell_introduce1_data_t; + +/* This data structure contains data that we need to parse an INTRODUCE2 cell + * which is used by the INTRODUCE2 cell parsing function. On a successful + * parsing, the onion_pk and rendezvous_cookie will be populated with the + * computed key material from the cell data. This structure is only used during + * INTRO2 parsing and discarded after that. */ +typedef struct hs_cell_introduce2_data_t { + /*** Immutable Section: Set on structure init. ***/ + + /* Introduction point authentication public key. Pointer owned by the + introduction point object through which we received the INTRO2 cell. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption keypair for the ntor handshake. Pointer + owned by the introduction point object through which we received the + INTRO2 cell*/ + const curve25519_keypair_t *enc_kp; + /* Subcredentials of the service. Pointer owned by the descriptor that owns + the introduction point through which we received the INTRO2 cell. */ + const uint8_t *subcredential; + /* Payload of the received encoded cell. */ + const uint8_t *payload; + /* Size of the payload of the received encoded cell. */ + size_t payload_len; + + /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/ + + /* Onion public key computed using the INTRODUCE2 encrypted section. */ + curve25519_public_key_t onion_pk; + /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */ + uint8_t rendezvous_cookie[REND_COOKIE_LEN]; + /* Client public key from the INTRODUCE2 encrypted section. */ + curve25519_public_key_t client_pk; + /* Link specifiers of the rendezvous point. Contains link_specifier_t. */ + smartlist_t *link_specifiers; + /* Replay cache of the introduction point. */ + replaycache_t *replay_cache; +} hs_cell_introduce2_data_t; + +/* Build cell API. */ +ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out); +ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out); +ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out); +ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out); + +/* Parse cell API. */ +ssize_t hs_cell_parse_intro_established(const uint8_t *payload, + size_t payload_len); +ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service); +int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len); +int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, + size_t handshake_info_len); + +/* Util API. */ +void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data); + +#endif /* !defined(TOR_HS_CELL_H) */ + diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c new file mode 100644 index 0000000000..a58166ccde --- /dev/null +++ b/src/or/hs_circuit.c @@ -0,0 +1,1208 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.c + **/ + +#define HS_CIRCUIT_PRIVATE + +#include "or.h" +#include "circpathbias.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" +#include "policies.h" +#include "relay.h" +#include "rendservice.h" +#include "rephist.h" +#include "router.h" + +#include "hs_cell.h" +#include "hs_ident.h" +#include "hs_ntor.h" +#include "hs_service.h" +#include "hs_circuit.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* A circuit is about to become an e2e rendezvous circuit. Check + * <b>circ_purpose</b> and ensure that it's properly set. Return true iff + * circuit purpose is properly set, otherwise return false. */ +static int +circuit_purpose_is_correct_for_rend(unsigned int circ_purpose, + int is_service_side) +{ + if (is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) { + log_warn(LD_BUG, + "HS e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + if (!is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY && + circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_BUG, + "Client e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + return 1; +} + +/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous + * circuit. Initialize the crypt path crypto using the output material from the + * ntor key exchange at <b>ntor_key_seed</b>. + * + * If <b>is_service_side</b> is set, we are the hidden service and the final + * hop of the rendezvous circuit is the client on the other side. */ +static crypt_path_t * +create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN]; + crypt_path_t *cpath = NULL; + + /* Do the key expansion */ + if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len, + keys, sizeof(keys)) < 0) { + goto err; + } + + /* Setup the cpath */ + cpath = tor_malloc_zero(sizeof(crypt_path_t)); + cpath->magic = CRYPT_PATH_MAGIC; + + if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys), + is_service_side, 1) < 0) { + tor_free(cpath); + goto err; + } + + err: + memwipe(keys, 0, sizeof(keys)); + return cpath; +} + +/* We are a v2 legacy HS client: Create and return a crypt path for the hidden + * service on the other side of the rendezvous circuit <b>circ</b>. Initialize + * the crypt path crypto using the body of the RENDEZVOUS1 cell at + * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes). + */ +static crypt_path_t * +create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body) +{ + crypt_path_t *hop = NULL; + char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; + + /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh + * handshake...*/ + tor_assert(circ->build_state); + tor_assert(circ->build_state->pending_final_cpath); + hop = circ->build_state->pending_final_cpath; + + tor_assert(hop->rend_dh_handshake_state); + if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state, + (char*)rend_cell_body, DH_KEY_LEN, + keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { + log_warn(LD_GENERAL, "Couldn't complete DH handshake."); + goto err; + } + /* ... and set up cpath. */ + if (circuit_init_cpath_crypto(hop, + keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, + 0, 0) < 0) + goto err; + + /* Check whether the digest is right... */ + if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) { + log_warn(LD_PROTOCOL, "Incorrect digest of key material."); + goto err; + } + + /* clean up the crypto stuff we just made */ + crypto_dh_free(hop->rend_dh_handshake_state); + hop->rend_dh_handshake_state = NULL; + + goto done; + + err: + hop = NULL; + + done: + memwipe(keys, 0, sizeof(keys)); + return hop; +} + +/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark + * <b>circ</b> ready for use to transfer HS relay cells. */ +static void +finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, + int is_service_side) +{ + tor_assert(circ); + tor_assert(hop); + + /* Notify the circuit state machine that we are splicing this circuit */ + int new_circ_purpose = is_service_side ? + CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED; + circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose); + + /* All is well. Extend the circuit. */ + hop->state = CPATH_STATE_OPEN; + /* Set the windows to default. */ + hop->package_window = circuit_initial_package_window(); + hop->deliver_window = CIRCWINDOW_START; + + /* Now that this circuit has finished connecting to its destination, + * make sure circuit_get_open_circ_or_launch is willing to return it + * so we can actually use it. */ + circ->hs_circ_has_timed_out = 0; + + /* Append the hop to the cpath of this circuit */ + onion_append_to_cpath(&circ->cpath, hop); + + /* In legacy code, 'pending_final_cpath' points to the final hop we just + * appended to the cpath. We set the original pointer to NULL so that we + * don't double free it. */ + if (circ->build_state) { + circ->build_state->pending_final_cpath = NULL; + } + + /* Finally, mark circuit as ready to be used for client streams */ + if (!is_service_side) { + circuit_try_attaching_streams(circ); + } +} + +/* For a given circuit and a service introduction point object, register the + * intro circuit to the circuitmap. This supports legacy intro point. */ +static void +register_intro_circ(const hs_service_intro_point_t *ip, + origin_circuit_t *circ) +{ + tor_assert(ip); + tor_assert(circ); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + return; + } + hs_circuitmap_register_intro_circ_v2_service_side(circ, digest); + } else { + hs_circuitmap_register_intro_circ_v3_service_side(circ, + &ip->auth_key_kp.pubkey); + } +} + +/* Return the number of opened introduction circuit for the given circuit that + * is matching its identity key. */ +static unsigned int +count_opened_desc_intro_point_circuits(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(service); + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + const circuit_t *circ; + const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc == NULL) { + continue; + } + circ = TO_CIRCUIT(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO); + /* Having a circuit not for the requested service is really bad. */ + tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk, + ô->hs_ident->identity_pk)); + /* Only count opened circuit and skip circuit that will be closed. */ + if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) { + count++; + } + } DIGEST256MAP_FOREACH_END; + return count; +} + +/* From a given service, rendezvous cookie and handshake info, create a + * rendezvous point circuit identifier. This can't fail. */ +STATIC hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys) +{ + hs_ident_circuit_t *ident; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + tor_assert(service); + tor_assert(rendezvous_cookie); + tor_assert(server_pk); + tor_assert(keys); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_RENDEZVOUS); + /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */ + memcpy(ident->rendezvous_cookie, rendezvous_cookie, + sizeof(ident->rendezvous_cookie)); + /* Build the HANDSHAKE_INFO which looks like this: + * SERVER_PK [32 bytes] + * AUTH_INPUT_MAC [32 bytes] + */ + memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN); + memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac, + DIGEST256_LEN); + tor_assert(sizeof(ident->rendezvous_handshake_info) == + sizeof(handshake_info)); + memcpy(ident->rendezvous_handshake_info, handshake_info, + sizeof(ident->rendezvous_handshake_info)); + /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */ + tor_assert(sizeof(ident->rendezvous_ntor_key_seed) == + sizeof(keys->ntor_key_seed)); + memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed, + sizeof(ident->rendezvous_ntor_key_seed)); + return ident; +} + +/* From a given service and service intro point, create an introduction point + * circuit identifier. This can't fail. */ +static hs_ident_circuit_t * +create_intro_circuit_identifier(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_ident_circuit_t *ident; + + tor_assert(service); + tor_assert(ip); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_INTRO); + ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey); + + return ident; +} + +/* For a given introduction point and an introduction circuit, send the + * ESTABLISH_INTRO cell. The service object is used for logging. This can fail + * and if so, the circuit is closed and the intro point object is flagged + * that the circuit is not established anymore which is important for the + * retry mechanism. */ +static void +send_establish_intro(const hs_service_t *service, + hs_service_intro_point_t *ip, origin_circuit_t *circ) +{ + ssize_t cell_len; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + + /* Encode establish intro cell. */ + cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, + ip, payload); + if (cell_len < 0) { + log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " + "on circuit %u. Closing circuit.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + + /* Send the cell on the circuit. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_INTRO, + (char *) payload, cell_len, + circ->cpath->prev) < 0) { + log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s " + "on circuit %u.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + /* On error, the circuit has been closed. */ + goto done; + } + + /* Record the attempt to use this circuit. */ + pathbias_count_use_attempt(circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* Return a string constant describing the anonymity of service. */ +static const char * +get_service_anonymity_string(const hs_service_t *service) +{ + if (service->config.is_single_onion) { + return "single onion"; + } else { + return "hidden"; + } +} + +/* For a given service, the ntor onion key and a rendezvous cookie, launch a + * circuit to the rendezvous point specified by the link specifiers. On + * success, a circuit identifier is attached to the circuit with the needed + * data. This function will try to open a circuit for a maximum value of + * MAX_REND_FAILURES then it will give up. */ +static void +launch_rendezvous_point_circuit(const hs_service_t *service, + const hs_service_intro_point_t *ip, + const hs_cell_introduce2_data_t *data) +{ + int circ_needs_uptime; + time_t now = time(NULL); + extend_info_t *info = NULL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(data); + + circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); + + /* Get the extend info data structure for the chosen rendezvous point + * specified by the given link specifiers. */ + info = hs_get_extend_info_from_lspecs(data->link_specifiers, + &data->onion_pk, + service->config.is_single_onion); + if (info == NULL) { + /* We are done here, we can't extend to the rendezvous point. + * If you're running an IPv6-only v3 single onion service on 0.3.2 or with + * 0.3.2 clients, and somehow disable the option check, it will fail here. + */ + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Not enough info to open a circuit to a rendezvous point for " + "%s service %s.", + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + goto end; + } + + for (int i = 0; i < MAX_REND_FAILURES; i++) { + int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; + if (circ_needs_uptime) { + circ_flags |= CIRCLAUNCH_NEED_UPTIME; + } + /* Firewall and policies are checked when getting the extend info. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info, + circ_flags); + if (circ != NULL) { + /* Stop retrying, we have a circuit! */ + break; + } + } + if (circ == NULL) { + log_warn(LD_REND, "Giving up on launching a rendezvous circuit to %s " + "for %s service %s", + safe_str_client(extend_info_describe(info)), + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + goto end; + } + log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s " + "for %s service %s", + safe_str_client(extend_info_describe(info)), + safe_str_client(hex_str((const char *) data->rendezvous_cookie, + REND_COOKIE_LEN)), + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + tor_assert(circ->build_state); + /* Rendezvous circuit have a specific timeout for the time spent on trying + * to connect to the rendezvous point. */ + circ->build_state->expiry_time = now + MAX_REND_TIMEOUT; + + /* Create circuit identifier and key material. */ + { + hs_ntor_rend_cell_keys_t keys; + curve25519_keypair_t ephemeral_kp; + /* No need for extra strong, this is only for this circuit life time. This + * key will be used for the RENDEZVOUS1 cell that will be sent on the + * circuit once opened. */ + curve25519_keypair_generate(&ephemeral_kp, 0); + if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey, + &ip->enc_key_kp, + &ephemeral_kp, &data->client_pk, + &keys) < 0) { + /* This should not really happened but just in case, don't make tor + * freak out, close the circuit and move on. */ + log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for " + "service %s", + safe_str_client(service->onion_address)); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + circ->hs_ident = create_rp_circuit_identifier(service, + data->rendezvous_cookie, + &ephemeral_kp.pubkey, &keys); + memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp)); + memwipe(&keys, 0, sizeof(keys)); + tor_assert(circ->hs_ident); + } + + end: + extend_info_free(info); +} + +/* Return true iff the given service rendezvous circuit circ is allowed for a + * relaunch to the rendezvous point. */ +static int +can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) +{ + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* XXX: Retrying under certain condition. This is related to #22455. */ + + /* Avoid to relaunch twice a circuit to the same rendezvous point at the + * same time. */ + if (circ->hs_service_side_rend_circ_has_been_relaunched) { + log_info(LD_REND, "Rendezvous circuit to %s has already been retried. " + "Skipping retry.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit))); + goto disallow; + } + + /* A failure count that has reached maximum allowed or circuit that expired, + * we skip relaunching. */ + if (circ->build_state->failure_count > MAX_REND_FAILURES || + circ->build_state->expiry_time <= time(NULL)) { + log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has " + "failed with %d attempts and expiry time %ld. " + "Giving up building.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit)), + circ->build_state->failure_count, + (long int) circ->build_state->expiry_time); + goto disallow; + } + + /* Allowed to relaunch. */ + return 1; + disallow: + return 0; +} + +/* Retry the rendezvous point of circ by launching a new circuit to it. */ +static void +retry_service_rendezvous_point(const origin_circuit_t *circ) +{ + int flags = 0; + origin_circuit_t *new_circ; + cpath_build_state_t *bstate; + + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Ease our life. */ + bstate = circ->build_state; + + log_info(LD_REND, "Retrying rendezvous point circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + + /* Get the current build state flags for the next circuit. */ + flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0; + flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0; + flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0; + + /* We do NOT add the onehop tunnel flag even though it might be a single + * onion service. The reason is that if we failed once to connect to the RP + * with a direct connection, we consider that chances are that we will fail + * again so try a 3-hop circuit and hope for the best. Because the service + * has no anonymity (single onion), this change of behavior won't affect + * security directly. */ + + new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, + bstate->chosen_exit, flags); + if (new_circ == NULL) { + log_warn(LD_REND, "Failed to launch rendezvous circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + goto done; + } + + /* Transfer build state information to the new circuit state in part to + * catch any other failures. */ + new_circ->build_state->failure_count = bstate->failure_count++; + new_circ->build_state->expiry_time = bstate->expiry_time; + new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident); + + done: + return; +} + +/* Using an extend info object ei, set all possible link specifiers in lspecs. + * legacy ID is mandatory thus MUST be present in ei. If IPv4 is not present, + * logs a BUG() warning, and returns an empty smartlist. Clients never make + * direct connections to rendezvous points, so they should always have an + * IPv4 address in ei. */ +static void +get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs) +{ + link_specifier_t *ls; + + tor_assert(ei); + tor_assert(lspecs); + + /* We require IPv4, we will add IPv6 support in a later tor version */ + if (BUG(!tor_addr_is_v4(&ei->addr))) { + return; + } + + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_IPV4); + link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr)); + link_specifier_set_un_ipv4_port(ls, ei->port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(ei->addr.addr.in_addr) + + sizeof(ei->port)); + smartlist_add(lspecs, ls); + + /* Legacy ID is mandatory. */ + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_LEGACY_ID); + memcpy(link_specifier_getarray_un_legacy_id(ls), ei->identity_digest, + link_specifier_getlen_un_legacy_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls)); + smartlist_add(lspecs, ls); + + /* ed25519 ID is only included if the extend_info has it. */ + if (!ed25519_public_key_is_zero(&ei->ed_identity)) { + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_ED25519_ID); + memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity, + link_specifier_getlen_un_ed25519_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); + smartlist_add(lspecs, ls); + } +} + +/* Using the given descriptor intro point ip, the extend information of the + * rendezvous point rp_ei and the service's subcredential, populate the + * already allocated intro1_data object with the needed key material and link + * specifiers. + * + * This can't fail but the ip MUST be a valid object containing the needed + * keys and authentication method. */ +static void +setup_introduce1_data(const hs_desc_intro_point_t *ip, + const extend_info_t *rp_ei, + const uint8_t *subcredential, + hs_cell_introduce1_data_t *intro1_data) +{ + smartlist_t *rp_lspecs; + + tor_assert(ip); + tor_assert(rp_ei); + tor_assert(subcredential); + tor_assert(intro1_data); + + /* Build the link specifiers from the extend information of the rendezvous + * circuit that we've picked previously. */ + rp_lspecs = smartlist_new(); + get_lspecs_from_extend_info(rp_ei, rp_lspecs); + + /* Populate the introduce1 data object. */ + memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t)); + if (ip->legacy.key != NULL) { + intro1_data->is_legacy = 1; + intro1_data->legacy_key = ip->legacy.key; + } + intro1_data->auth_pk = &ip->auth_key_cert->signed_key; + intro1_data->enc_pk = &ip->enc_key; + intro1_data->subcredential = subcredential; + intro1_data->onion_pk = &rp_ei->curve25519_onion_key; + intro1_data->link_specifiers = rp_lspecs; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return an introduction point circuit matching the given intro point object. + * NULL is returned is no such circuit can be found. */ +origin_circuit_t * +hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip) +{ + origin_circuit_t *circ = NULL; + + tor_assert(ip); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + goto end; + } + circ = hs_circuitmap_get_intro_circ_v2_service_side(digest); + } else { + circ = hs_circuitmap_get_intro_circ_v3_service_side( + &ip->auth_key_kp.pubkey); + } + end: + return circ; +} + +/* Called when we fail building a rendezvous circuit at some point other than + * the last hop: launches a new circuit to the same rendezvous point. This + * supports legacy service. + * + * We currently relaunch connections to rendezvous points if: + * - A rendezvous circuit timed out before connecting to RP. + * - The redenzvous circuit failed to connect to the RP. + * + * We avoid relaunching a connection to this rendezvous point if: + * - We have already tried MAX_REND_FAILURES times to connect to this RP. + * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT + * seconds + * - We've already retried this specific rendezvous circuit. + */ +void +hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Check if we are allowed to relaunch to the rendezvous point of circ. */ + if (!can_relaunch_service_rendezvous_point(circ)) { + goto done; + } + + /* Flag the circuit that we are relaunching so to avoid to relaunch twice a + * circuit to the same rendezvous point at the same time. */ + circ->hs_service_side_rend_circ_has_been_relaunched = 1; + + /* Legacy service don't have an hidden service ident. */ + if (circ->hs_ident) { + retry_service_rendezvous_point(circ); + } else { + rend_service_relaunch_rendezvous(circ); + } + + done: + return; +} + +/* For a given service and a service intro point, launch a circuit to the + * extend info ei. If the service is a single onion, a one-hop circuit will be + * requested. Return 0 if the circuit was successfully launched and tagged + * with the correct identifier. On error, a negative value is returned. */ +int +hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei) +{ + /* Standard flags for introduction circuit. */ + int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(ei); + + /* Update circuit flags in case of a single onion service that requires a + * direct connection. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + log_info(LD_REND, "Launching a circuit to intro point %s for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + + /* Note down the launch for the retry period. Even if the circuit fails to + * be launched, we still want to respect the retry period to avoid stress on + * the circuit subsystem. */ + service->state.num_intro_circ_launched++; + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + ei, circ_flags); + if (circ == NULL) { + goto end; + } + + /* Setup the circuit identifier and attach it to it. */ + circ->hs_ident = create_intro_circuit_identifier(service, ip); + tor_assert(circ->hs_ident); + /* Register circuit in the global circuitmap. */ + register_intro_circ(ip, circ); + + /* Success. */ + ret = 0; + end: + return ret; +} + +/* Called when a service introduction point circuit is done building. Given + * the service and intro point object, this function will send the + * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the + * circuit has been repurposed to General because we already have too many + * opened. */ +int +hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ) +{ + int ret = 0; + unsigned int num_intro_circ, num_needed_circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(desc); + tor_assert(circ); + + /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already + * established introduction circuits */ + num_intro_circ = count_opened_desc_intro_point_circuits(service, desc); + num_needed_circ = service->config.num_intro_points; + if (num_intro_circ > num_needed_circ) { + /* There are too many opened valid intro circuit for what the service + * needs so repurpose this one. */ + + /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just + * closes the circuit. I have NO idea why it does that so it hasn't been + * added here. I can only assume in case our ExcludeNodes list changes but + * in that case, all circuit are flagged unusable (config.c). --dgoulet */ + + log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we " + "have enough for service %s. Repurposing " + "it to general and leaving internal.", + safe_str_client(service->onion_address)); + tor_assert(circ->build_state->is_internal); + /* Remove it from the circuitmap. */ + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); + /* Cleaning up the hidden service identifier and repurpose. */ + hs_ident_circuit_free(circ->hs_ident); + circ->hs_ident = NULL; + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL); + /* Inform that this circuit just opened for this new purpose. */ + circuit_has_opened(circ); + /* This return value indicate to the caller that the IP object should be + * removed from the service because it's corresponding circuit has just + * been repurposed. */ + ret = 1; + goto done; + } + + log_info(LD_REND, "Introduction circuit %u established for service %s.", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call + * makes sure the circuit gets closed. */ + send_establish_intro(service, ip, circ); + + done: + return ret; +} + +/* Called when a service rendezvous point circuit is done building. Given the + * service and the circuit, this function will send a RENDEZVOUS1 cell on the + * circuit using the information in the circuit identifier. If the cell can't + * be sent, the circuit is closed. */ +void +hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ) +{ + size_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(service); + tor_assert(circ); + tor_assert(circ->hs_ident); + + /* Some useful logging. */ + log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN), + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* This can't fail. */ + payload_len = hs_cell_build_rendezvous1( + circ->hs_ident->rendezvous_cookie, + sizeof(circ->hs_ident->rendezvous_cookie), + circ->hs_ident->rendezvous_handshake_info, + sizeof(circ->hs_ident->rendezvous_handshake_info), + payload); + + /* Pad the payload with random bytes so it matches the size of a legacy cell + * which is normally always bigger. Also, the size of a legacy cell is + * always smaller than the RELAY_PAYLOAD_SIZE so this is safe. */ + if (payload_len < HS_LEGACY_RENDEZVOUS_CELL_SIZE) { + crypto_rand((char *) payload + payload_len, + HS_LEGACY_RENDEZVOUS_CELL_SIZE - payload_len); + payload_len = HS_LEGACY_RENDEZVOUS_CELL_SIZE; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_RENDEZVOUS1, + (const char *) payload, payload_len, + circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Setup end-to-end rendezvous circuit between the client and us. */ + if (hs_circuit_setup_e2e_rend_circ(circ, + circ->hs_ident->rendezvous_ntor_key_seed, + sizeof(circ->hs_ident->rendezvous_ntor_key_seed), + 1) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); + goto done; + } + + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle + * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the + * given introduction circuit circ. The service is only used for logging + * purposes. Return 0 on success else a negative value. */ +int +hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + tor_assert(payload); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + goto done; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. For a legacy node, it's an empty payload so as long as we + * have the cell, we are good. */ + if (!ip->base.is_only_legacy && + hs_cell_parse_intro_established(payload, payload_len) < 0) { + log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Switch the purpose to a fully working intro point. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO); + /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the + * circuit so update our pathbias subsystem. */ + pathbias_mark_use_success(circ); + /* Success. */ + ret = 0; + + done: + return ret; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the INTRODUCE2 payload of size payload_len for the given + * circuit and service. This cell is associated with the intro point object ip + * and the subcredential. Return 0 on success else a negative value. */ +int +hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + time_t elapsed; + hs_cell_introduce2_data_t data; + + tor_assert(service); + tor_assert(circ); + tor_assert(ip); + tor_assert(subcredential); + tor_assert(payload); + + /* Populate the data structure with everything we need for the cell to be + * parsed, decrypted and key material computed correctly. */ + data.auth_pk = &ip->auth_key_kp.pubkey; + data.enc_kp = &ip->enc_key_kp; + data.subcredential = subcredential; + data.payload = payload; + data.payload_len = payload_len; + data.link_specifiers = smartlist_new(); + data.replay_cache = ip->replay_cache; + + if (hs_cell_parse_introduce2(&data, circ, service) < 0) { + goto done; + } + + /* Check whether we've seen this REND_COOKIE before to detect repeats. */ + if (replaycache_add_test_and_elapsed( + service->state.replay_cache_rend_cookie, + data.rendezvous_cookie, sizeof(data.rendezvous_cookie), + &elapsed)) { + /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE + * as its previous one if its intro circ times out while in state + * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first + * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2 + * cell), we are already trying to connect to that rend point (and may + * have already succeeded); drop this cell. */ + log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE " + "field %ld seconds ago. Dropping cell.", + (long int) elapsed); + goto done; + } + + /* At this point, we just confirmed that the full INTRODUCE2 cell is valid + * so increment our counter that we've seen one on this intro point. */ + ip->introduce2_count++; + + /* Launch rendezvous circuit with the onion key and rend cookie. */ + launch_rendezvous_point_circuit(service, ip, &data); + /* Success. */ + ret = 0; + + done: + SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec, + link_specifier_free(lspec)); + smartlist_free(data.link_specifiers); + memwipe(&data, 0, sizeof(data)); + return ret; +} + +/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key + * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to + * serve as a rendezvous end-to-end circuit between the client and the + * service. If <b>is_service_side</b> is set, then we are the hidden service + * and the other side is the client. + * + * Return 0 if the operation went well; in case of error return -1. */ +int +hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose, + is_service_side))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len, + is_service_side); + if (!hop) { + log_warn(LD_REND, "Couldn't get v3 %s cpath!", + is_service_side ? "service-side" : "client-side"); + return -1; + } + + finalize_rend_circuit(circ, hop, is_service_side); + + return 0; +} + +/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell + * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then + * extend the crypt path of <b>circ</b> so that the hidden service is on the + * other side. */ +int +hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body) +{ + + if (BUG(!circuit_purpose_is_correct_for_rend( + TO_CIRCUIT(circ)->purpose, 0))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body); + if (!hop) { + log_warn(LD_GENERAL, "Couldn't get v2 cpath."); + return -1; + } + + finalize_rend_circuit(circ, hop, 0); + + return 0; +} + +/* Given the introduction circuit intro_circ, the rendezvous circuit + * rend_circ, a descriptor intro point object ip and the service's + * subcredential, send an INTRODUCE1 cell on intro_circ. + * + * This will also setup the circuit identifier on rend_circ containing the key + * material for the handshake and e2e encryption. Return 0 on success else + * negative value. Because relay_send_command_from_edge() closes the circuit + * on error, it is possible that intro_circ is closed on error. */ +int +hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential) +{ + int ret = -1; + ssize_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + hs_cell_introduce1_data_t intro1_data; + + tor_assert(intro_circ); + tor_assert(rend_circ); + tor_assert(ip); + tor_assert(subcredential); + + /* This takes various objects in order to populate the introduce1 data + * object which is used to build the content of the cell. */ + setup_introduce1_data(ip, rend_circ->build_state->chosen_exit, + subcredential, &intro1_data); + /* If we didn't get any link specifiers, it's because our extend info was + * bad. */ + if (BUG(!intro1_data.link_specifiers) || + !smartlist_len(intro1_data.link_specifiers)) { + log_warn(LD_REND, "Unable to get link specifiers for INTRODUCE1 cell on " + "circuit %u.", TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } + + /* Final step before we encode a cell, we setup the circuit identifier which + * will generate both the rendezvous cookie and client keypair for this + * connection. Those are put in the ident. */ + intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie; + intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp; + + memcpy(intro_circ->hs_ident->rendezvous_cookie, + rend_circ->hs_ident->rendezvous_cookie, + sizeof(intro_circ->hs_ident->rendezvous_cookie)); + + /* From the introduce1 data object, this will encode the INTRODUCE1 cell + * into payload which is then ready to be sent as is. */ + payload_len = hs_cell_build_introduce1(&intro1_data, payload); + if (BUG(payload_len < 0)) { + goto done; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ), + RELAY_COMMAND_INTRODUCE1, + (const char *) payload, payload_len, + intro_circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.", + TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } + + /* Success. */ + ret = 0; + goto done; + + done: + hs_cell_introduce1_data_clear(&intro1_data); + memwipe(payload, 0, sizeof(payload)); + return ret; +} + +/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On + * success, 0 is returned else -1 and the circuit is marked for close. */ +int +hs_circ_send_establish_rendezvous(origin_circuit_t *circ) +{ + ssize_t cell_len = 0; + uint8_t cell[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u", + TO_CIRCUIT(circ)->n_circ_id); + + /* Set timestamp_dirty, because circuit_expire_building expects it, + * and the rend cookie also means we've used the circ. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* We've attempted to use this circuit. Probe it if we fail */ + pathbias_count_use_attempt(circ); + + /* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can + * complete the handshake when receiving the acknowledgement. */ + crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN); + /* Generate the client keypair. No need to be extra strong, not long term */ + curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0); + + cell_len = + hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie, + cell); + if (BUG(cell_len < 0)) { + goto err; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_RENDEZVOUS, + (const char *) cell, cell_len, + circ->cpath->prev) < 0) { + /* Circuit has been marked for close */ + log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on " + "circuit %u", TO_CIRCUIT(circ)->n_circ_id); + memwipe(cell, 0, cell_len); + goto err; + } + + memwipe(cell, 0, cell_len); + return 0; + err: + return -1; +} + +/* We are about to close or free this <b>circ</b>. Clean it up from any + * related HS data structures. This function can be called multiple times + * safely for the same circuit. */ +void +hs_circ_cleanup(circuit_t *circ) +{ + tor_assert(circ); + + /* If it's a service-side intro circ, notify the HS subsystem for the intro + * point circuit closing so it can be dealt with cleanly. */ + if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO) { + hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ)); + } + + /* Clear HS circuitmap token for this circ (if any). Very important to be + * done after the HS subsystem has been notified of the close else the + * circuit will not be found. + * + * We do this at the close if possible because from that point on, the + * circuit is good as dead. We can't rely on removing it in the circuit + * free() function because we open a race window between the close and free + * where we can't register a new circuit for the same intro point. */ + if (circ->hs_token) { + hs_circuitmap_remove_circuit(circ); + } +} + diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h new file mode 100644 index 0000000000..63ff5e463c --- /dev/null +++ b/src/or/hs_circuit.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.h + * \brief Header file containing circuit data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CIRCUIT_H +#define TOR_HS_CIRCUIT_H + +#include "or.h" +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_service.h" + +/* Cleanup function when the circuit is closed or/and freed. */ +void hs_circ_cleanup(circuit_t *circ); + +/* Circuit API. */ +int hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ); +void hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ); +int hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei); +int hs_circ_launch_rendezvous_point(const hs_service_t *service, + const curve25519_public_key_t *onion_key, + const uint8_t *rendezvous_cookie); +void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ); + +origin_circuit_t *hs_circ_service_get_intro_circ( + const hs_service_intro_point_t *ip); + +/* Cell API. */ +int hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len); +int hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential); +int hs_circ_send_establish_rendezvous(origin_circuit_t *circ); + +/* e2e circuit API. */ + +int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, + size_t seed_len, + int is_service_side); +int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body); + +#ifdef HS_CIRCUIT_PRIVATE + +STATIC hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys); + +#endif + +#endif /* !defined(TOR_HS_CIRCUIT_H) */ + diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c index ea66fb5194..97d6053e9b 100644 --- a/src/or/hs_circuitmap.c +++ b/src/or/hs_circuitmap.c @@ -5,8 +5,10 @@ * \file hs_circuitmap.c * * \brief Hidden service circuitmap: A hash table that maps binary tokens to - * introduction and rendezvous circuits; it's used both by relays acting as - * intro points and rendezvous points, and also by hidden services themselves. + * introduction and rendezvous circuits; it's used: + * (a) by relays acting as intro points and rendezvous points + * (b) by hidden services to find intro and rend circuits and + * (c) by HS clients to find rendezvous circuits. **/ #define HS_CIRCUITMAP_PRIVATE @@ -85,7 +87,7 @@ get_hs_circuitmap(void) return the_hs_circuitmap; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /****************** HS circuitmap utility functions **************************/ @@ -404,6 +406,55 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie) return circ; } +/* Public function: Return client-side rendezvous circuit with rendezvous + * <b>cookie</b>. It will look for circuits with the following purposes: + + * a) CIRCUIT_PURPOSE_C_REND_READY: Established rend circuit (received + * RENDEZVOUS_ESTABLISHED). Waiting for RENDEZVOUS2 from service, and for + * INTRODUCE_ACK from intro point. + * + * b) CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: Established rend circuit and + * introduce circuit acked. Waiting for RENDEZVOUS2 from service. + * + * c) CIRCUIT_PURPOSE_C_REND_JOINED: Established rend circuit and received + * RENDEZVOUS2 from service. + * + * d) CIRCUIT_PURPOSE_C_ESTABLISH_REND: Rend circuit open but not yet + * established. + * + * Return NULL if no such circuit is found in the circuitmap. */ +origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie) +{ + origin_circuit_t *circ = NULL; + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_JOINED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_ESTABLISH_REND); + return circ; +} + /**** Public servide-side setters: */ /* Public function: Register v2 intro circuit with key <b>digest</b> to the @@ -439,6 +490,21 @@ hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ, REND_TOKEN_LEN, cookie); } +/* Public function: Register rendezvous circuit with key <b>cookie</b> to the + * client-side circuitmap. */ +void +hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ, + const uint8_t *cookie) +{ + circuit_t *circ = TO_CIRCUIT(or_circ); + { /* Basic circ purpose sanity checking */ + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + } + + hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie); +} + /**** Misc public functions: */ /** Public function: Remove this circuit from the HS circuitmap. Clear its HS diff --git a/src/or/hs_circuitmap.h b/src/or/hs_circuitmap.h index 33d5b64117..43b2947c17 100644 --- a/src/or/hs_circuitmap.h +++ b/src/or/hs_circuitmap.h @@ -43,6 +43,8 @@ struct origin_circuit_t * hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest); struct origin_circuit_t * hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie); +struct origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie); void hs_circuitmap_register_intro_circ_v2_service_side( struct origin_circuit_t *circ, @@ -53,6 +55,9 @@ void hs_circuitmap_register_intro_circ_v3_service_side( void hs_circuitmap_register_rend_circ_service_side( struct origin_circuit_t *circ, const uint8_t *cookie); +void hs_circuitmap_register_rend_circ_client_side( + struct origin_circuit_t *circ, + const uint8_t *cookie); void hs_circuitmap_remove_circuit(struct circuit_t *circ); @@ -76,6 +81,9 @@ typedef enum { HS_TOKEN_INTRO_V2_SERVICE_SIDE, /** A v3 introduction point pubkey on a hidden service (256bit) */ HS_TOKEN_INTRO_V3_SERVICE_SIDE, + + /** A rendezvous cookie on the client side (128bit) */ + HS_TOKEN_REND_CLIENT_SIDE, } hs_token_type_t; /** Represents a token used in the HS protocol. Each such token maps to a @@ -91,7 +99,7 @@ struct hs_token_s { uint8_t *token; }; -#endif /* HS_CIRCUITMAP_PRIVATE */ +#endif /* defined(HS_CIRCUITMAP_PRIVATE) */ #ifdef TOR_UNIT_TESTS @@ -99,5 +107,5 @@ hs_circuitmap_ht *get_hs_circuitmap(void); #endif /* TOR_UNIT_TESTS */ -#endif /* TOR_HS_CIRCUITMAP_H */ +#endif /* !defined(TOR_HS_CIRCUITMAP_H) */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c new file mode 100644 index 0000000000..9ac653c721 --- /dev/null +++ b/src/or/hs_client.c @@ -0,0 +1,1609 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_client.c + * \brief Implement next generation hidden service client functionality + **/ + +#define HS_CLIENT_PRIVATE + +#include "or.h" +#include "hs_circuit.h" +#include "hs_ident.h" +#include "connection_edge.h" +#include "container.h" +#include "rendclient.h" +#include "hs_descriptor.h" +#include "hs_cache.h" +#include "hs_cell.h" +#include "hs_ident.h" +#include "config.h" +#include "directory.h" +#include "hs_client.h" +#include "router.h" +#include "routerset.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "connection.h" +#include "nodelist.h" +#include "circpathbias.h" +#include "connection.h" +#include "hs_ntor.h" +#include "circuitbuild.h" +#include "networkstatus.h" +#include "reasons.h" + +/* Return a human-readable string for the client fetch status code. */ +static const char * +fetch_status_to_string(hs_client_fetch_status_t status) +{ + switch (status) { + case HS_CLIENT_FETCH_ERROR: + return "Internal error"; + case HS_CLIENT_FETCH_LAUNCHED: + return "Descriptor fetch launched"; + case HS_CLIENT_FETCH_HAVE_DESC: + return "Already have descriptor"; + case HS_CLIENT_FETCH_NO_HSDIRS: + return "No more HSDir available to query"; + case HS_CLIENT_FETCH_NOT_ALLOWED: + return "Fetching descriptors is not allowed"; + case HS_CLIENT_FETCH_MISSING_INFO: + return "Missing directory information"; + case HS_CLIENT_FETCH_PENDING: + return "Pending descriptor fetch"; + default: + return "(Unknown client fetch status code)"; + } +} + +/* Return true iff tor should close the SOCKS request(s) for the descriptor + * fetch that ended up with this given status code. */ +static int +fetch_status_should_close_socks(hs_client_fetch_status_t status) +{ + switch (status) { + case HS_CLIENT_FETCH_NO_HSDIRS: + /* No more HSDir to query, we can't complete the SOCKS request(s). */ + case HS_CLIENT_FETCH_ERROR: + /* The fetch triggered an internal error. */ + case HS_CLIENT_FETCH_NOT_ALLOWED: + /* Client is not allowed to fetch (FetchHidServDescriptors 0). */ + goto close; + case HS_CLIENT_FETCH_MISSING_INFO: + case HS_CLIENT_FETCH_HAVE_DESC: + case HS_CLIENT_FETCH_PENDING: + case HS_CLIENT_FETCH_LAUNCHED: + /* The rest doesn't require tor to close the SOCKS request(s). */ + goto no_close; + } + + no_close: + return 0; + close: + return 1; +} + +/* Cancel all descriptor fetches currently in progress. */ +static void +cancel_descriptor_fetches(void) +{ + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; + if (BUG(ident == NULL)) { + /* A directory connection fetching a service descriptor can't have an + * empty hidden service identifier. */ + continue; + } + log_debug(LD_REND, "Marking for close a directory connection fetching " + "a hidden service descriptor for service %s.", + safe_str_client(ed25519_fmt(&ident->identity_pk))); + connection_mark_for_close(conn); + } SMARTLIST_FOREACH_END(conn); + + /* No ownership of the objects in this list. */ + smartlist_free(conns); + log_info(LD_REND, "Hidden service client descriptor fetches cancelled."); +} + +/* Get all connections that are waiting on a circuit and flag them back to + * waiting for a hidden service descriptor for the given service key + * service_identity_pk. */ +static void +flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk) +{ + tor_assert(service_identity_pk); + + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + edge_connection_t *edge_conn; + if (BUG(!CONN_IS_EDGE(conn))) { + continue; + } + edge_conn = TO_EDGE_CONN(conn); + if (edge_conn->hs_ident && + ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk, + service_identity_pk)) { + connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); + conn->state = AP_CONN_STATE_RENDDESC_WAIT; + } + } SMARTLIST_FOREACH_END(conn); + + smartlist_free(conns); +} + +/* Remove tracked HSDir requests from our history for this hidden service + * identity public key. */ +static void +purge_hid_serv_request(const ed25519_public_key_t *identity_pk) +{ + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + ed25519_public_key_t blinded_pk; + + tor_assert(identity_pk); + + /* Get blinded pubkey of hidden service. It is possible that we just moved + * to a new time period meaning that we won't be able to purge the request + * from the previous time period. That is fine because they will expire at + * some point and we don't care about those anymore. */ + hs_build_blinded_pubkey(identity_pk, NULL, 0, + hs_get_time_period_num(0), &blinded_pk); + if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) { + return; + } + /* Purge last hidden service request from cache for this blinded key. */ + hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk); +} + +/* Return true iff there is at least one pending directory descriptor request + * for the service identity_pk. */ +static int +directory_request_is_pending(const ed25519_public_key_t *identity_pk) +{ + int ret = 0; + smartlist_t *conns = + connection_list_by_type_purpose(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; + if (BUG(ident == NULL)) { + /* A directory connection fetching a service descriptor can't have an + * empty hidden service identifier. */ + continue; + } + if (!ed25519_pubkey_eq(identity_pk, &ident->identity_pk)) { + continue; + } + ret = 1; + break; + } SMARTLIST_FOREACH_END(conn); + + /* No ownership of the objects in this list. */ + smartlist_free(conns); + return ret; +} + +/* We failed to fetch a descriptor for the service with <b>identity_pk</b> + * because of <b>status</b>. Find all pending SOCKS connections for this + * service that are waiting on the descriptor and close them with + * <b>reason</b>. */ +static void +close_all_socks_conns_waiting_for_desc(const ed25519_public_key_t *identity_pk, + hs_client_fetch_status_t status, + int reason) +{ + unsigned int count = 0; + time_t now = approx_time(); + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we tried to get the descriptor */ + if (!edge_conn->hs_ident || + !ed25519_pubkey_eq(identity_pk, + &edge_conn->hs_ident->identity_pk)) { + continue; + } + assert_connection_ok(base_conn, now); + /* Unattach the entry connection which will close for the reason. */ + connection_mark_unattached_ap(entry_conn, reason); + count++; + } SMARTLIST_FOREACH_END(base_conn); + + if (count > 0) { + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + hs_build_address(identity_pk, HS_VERSION_THREE, onion_address); + log_notice(LD_REND, "Closed %u streams for service %s.onion " + "for reason %s. Fetch status: %s.", + count, safe_str_client(onion_address), + stream_end_reason_to_string(reason), + fetch_status_to_string(status)); + } + + /* No ownership of the object(s) in this list. */ + smartlist_free(conns); +} + +/* Find all pending SOCKS connection waiting for a descriptor and retry them + * all. This is called when the directory information changed. */ +static void +retry_all_socks_conn_waiting_for_desc(void) +{ + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + hs_client_fetch_status_t status; + const edge_connection_t *edge_conn = + ENTRY_TO_EDGE_CONN(TO_ENTRY_CONN(base_conn)); + + /* Ignore non HS or non v3 connection. */ + if (edge_conn->hs_ident == NULL) { + continue; + } + /* In this loop, we will possibly try to fetch a descriptor for the + * pending connections because we just got more directory information. + * However, the refetch process can cleanup all SOCKS request so the same + * service if an internal error happens. Thus, we can end up with closed + * connections in our list. */ + if (base_conn->marked_for_close) { + continue; + } + + /* XXX: There is an optimization we could do which is that for a service + * key, we could check if we can fetch and remember that decision. */ + + /* Order a refetch in case it works this time. */ + status = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + if (BUG(status == HS_CLIENT_FETCH_HAVE_DESC)) { + /* This case is unique because it can NOT happen in theory. Once we get + * a new descriptor, the HS client subsystem is notified immediately and + * the connections waiting for it are handled which means the state will + * change from renddesc wait state. Log this and continue to next + * connection. */ + continue; + } + /* In the case of an error, either all SOCKS connections have been + * closed or we are still missing directory information. Leave the + * connection in renddesc wait state so when we get more info, we'll be + * able to try it again. */ + } SMARTLIST_FOREACH_END(base_conn); + + /* We don't have ownership of those objects. */ + smartlist_free(conns); +} + +/* A v3 HS circuit successfully connected to the hidden service. Update the + * stream state at <b>hs_conn_ident</b> appropriately. */ +static void +note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) +{ + tor_assert(hs_conn_ident); + + /* Remove from the hid serv cache all requests for that service so we can + * query the HSDir again later on for various reasons. */ + purge_hid_serv_request(&hs_conn_ident->identity_pk); + + /* The v2 subsystem cleans up the intro point time out flag at this stage. + * We don't try to do it here because we still need to keep intact the intro + * point state for future connections. Even though we are able to connect to + * the service, doesn't mean we should reset the timed out intro points. + * + * It is not possible to have successfully connected to an intro point + * present in our cache that was on error or timed out. Every entry in that + * cache have a 2 minutes lifetime so ultimately the intro point(s) state + * will be reset and thus possible to be retried. */ +} + +/* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its + * descriptor by launching a dir connection to <b>hsdir</b>. Return a + * hs_client_fetch_status_t status code depending on how it went. */ +static hs_client_fetch_status_t +directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, + const routerstatus_t *hsdir) +{ + uint64_t current_time_period = hs_get_time_period_num(0); + ed25519_public_key_t blinded_pubkey; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + hs_ident_dir_conn_t hs_conn_dir_ident; + int retval; + + tor_assert(hsdir); + tor_assert(onion_identity_pk); + + /* Get blinded pubkey */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return HS_CLIENT_FETCH_ERROR; + } + + /* Copy onion pk to a dir_ident so that we attach it to the dir conn */ + hs_ident_dir_conn_init(onion_identity_pk, &blinded_pubkey, + &hs_conn_dir_ident); + + /* Setup directory request */ + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_HSDESC); + directory_request_set_routerstatus(req, hsdir); + directory_request_set_indirection(req, DIRIND_ANONYMOUS); + directory_request_set_resource(req, base64_blinded_pubkey); + directory_request_fetch_set_hs_ident(req, &hs_conn_dir_ident); + directory_initiate_request(req); + directory_request_free(req); + + log_info(LD_REND, "Descriptor fetch request for service %s with blinded " + "key %s to directory %s", + safe_str_client(ed25519_fmt(onion_identity_pk)), + safe_str_client(base64_blinded_pubkey), + safe_str_client(routerstatus_describe(hsdir))); + + /* Cleanup memory. */ + memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey)); + memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey)); + memwipe(&hs_conn_dir_ident, 0, sizeof(hs_conn_dir_ident)); + + return HS_CLIENT_FETCH_LAUNCHED; +} + +/** Return the HSDir we should use to fetch the descriptor of the hidden + * service with identity key <b>onion_identity_pk</b>. */ +STATIC routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) +{ + int retval; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + uint64_t current_time_period = hs_get_time_period_num(0); + smartlist_t *responsible_hsdirs; + ed25519_public_key_t blinded_pubkey; + routerstatus_t *hsdir_rs = NULL; + + tor_assert(onion_identity_pk); + + responsible_hsdirs = smartlist_new(); + + /* Get blinded pubkey of hidden service */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return NULL; + } + + /* Get responsible hsdirs of service for this time period */ + hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, + 0, 1, responsible_hsdirs); + + log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.", + smartlist_len(responsible_hsdirs)); + + /* Pick an HSDir from the responsible ones. The ownership of + * responsible_hsdirs is given to this function so no need to free it. */ + hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey); + + return hsdir_rs; +} + +/** Fetch a v3 descriptor using the given <b>onion_identity_pk</b>. + * + * On success, HS_CLIENT_FETCH_LAUNCHED is returned. Otherwise, an error from + * hs_client_fetch_status_t is returned. */ +MOCK_IMPL(STATIC hs_client_fetch_status_t, +fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)) +{ + routerstatus_t *hsdir_rs =NULL; + + tor_assert(onion_identity_pk); + + hsdir_rs = pick_hsdir_v3(onion_identity_pk); + if (!hsdir_rs) { + log_info(LD_REND, "Couldn't pick a v3 hsdir."); + return HS_CLIENT_FETCH_NO_HSDIRS; + } + + return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); +} + +/* Make sure that the given v3 origin circuit circ is a valid correct + * introduction circuit. This will BUG() on any problems and hard assert if + * the anonymity of the circuit is not ok. Return 0 on success else -1 where + * the circuit should be mark for closed immediately. */ +static int +intro_circ_is_ok(const origin_circuit_t *circ) +{ + int ret = 0; + + tor_assert(circ); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCING && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { + ret = -1; + } + if (BUG(circ->hs_ident == NULL)) { + ret = -1; + } + if (BUG(!hs_ident_intro_circ_is_valid(circ->hs_ident))) { + ret = -1; + } + + /* This can stop the tor daemon but we want that since if we don't have + * anonymity on this circuit, something went really wrong. */ + assert_circ_anonymity_ok(circ, get_options()); + return ret; +} + +/* Find a descriptor intro point object that matches the given ident in the + * given descriptor desc. Return NULL if not found. */ +static const hs_desc_intro_point_t * +find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, + const hs_descriptor_t *desc) +{ + const hs_desc_intro_point_t *intro_point = NULL; + + tor_assert(ident); + tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (ed25519_pubkey_eq(&ident->intro_auth_pk, + &ip->auth_key_cert->signed_key)) { + intro_point = ip; + break; + } + } SMARTLIST_FOREACH_END(ip); + + return intro_point; +} + +/* Find a descriptor intro point object from the descriptor object desc that + * matches the given legacy identity digest in legacy_id. Return NULL if not + * found. */ +static hs_desc_intro_point_t * +find_desc_intro_point_by_legacy_id(const char *legacy_id, + const hs_descriptor_t *desc) +{ + hs_desc_intro_point_t *ret_ip = NULL; + + tor_assert(legacy_id); + tor_assert(desc); + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + hs_desc_intro_point_t *, ip) { + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + /* Not all tor node have an ed25519 identity key so we still rely on the + * legacy identity digest. */ + if (lspec->type != LS_LEGACY_ID) { + continue; + } + if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) { + break; + } + /* Found it. */ + ret_ip = ip; + goto end; + } SMARTLIST_FOREACH_END(lspec); + } SMARTLIST_FOREACH_END(ip); + + end: + return ret_ip; +} + +/* Send an INTRODUCE1 cell along the intro circuit and populate the rend + * circuit identifier with the needed key material for the e2e encryption. + * Return 0 on success, -1 if there is a transient error such that an action + * has been taken to recover and -2 if there is a permanent error indicating + * that both circuits were closed. */ +static int +send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + int status; + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + const ed25519_public_key_t *service_identity_pk = NULL; + const hs_desc_intro_point_t *ip; + + tor_assert(rend_circ); + if (intro_circ_is_ok(intro_circ) < 0) { + goto perm_err; + } + + service_identity_pk = &intro_circ->hs_ident->identity_pk; + /* For logging purposes. There will be a time where the hs_ident will have a + * version number but for now there is none because it's all v3. */ + hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address); + + log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u", + safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id); + + /* 1) Get descriptor from our cache. */ + const hs_descriptor_t *desc = + hs_cache_lookup_as_client(service_identity_pk); + if (desc == NULL || !hs_client_any_intro_points_usable(service_identity_pk, + desc)) { + log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.", + safe_str_client(onion_address), + (desc) ? "didn't have usable intro points" : + "didn't have a descriptor"); + hs_client_refetch_hsdesc(service_identity_pk); + /* We just triggered a refetch, make sure every connections are back + * waiting for that descriptor. */ + flag_all_conn_wait_desc(service_identity_pk); + /* We just asked for a refetch so this is a transient error. */ + goto tran_err; + } + + /* We need to find which intro point in the descriptor we are connected to + * on intro_circ. */ + ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc); + if (BUG(ip == NULL)) { + /* If we can find a descriptor from this introduction circuit ident, we + * must have a valid intro point object. Permanent error. */ + goto perm_err; + } + + /* Send the INTRODUCE1 cell. */ + if (hs_circ_send_introduce1(intro_circ, rend_circ, ip, + desc->subcredential) < 0) { + /* Unable to send the cell, the intro circuit has been marked for close so + * this is a permanent error. */ + tor_assert_nonfatal(TO_CIRCUIT(intro_circ)->marked_for_close); + goto perm_err; + } + + /* Cell has been sent successfully. Copy the introduction point + * authentication and encryption key in the rendezvous circuit identifier so + * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */ + memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key, + sizeof(rend_circ->hs_ident->intro_enc_pk)); + ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk, + &intro_circ->hs_ident->intro_auth_pk); + + /* Now, we wait for an ACK or NAK on this circuit. */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */ + TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(intro_circ); + + /* Success. */ + status = 0; + goto end; + + perm_err: + /* Permanent error: it is possible that the intro circuit was closed prior + * because we weren't able to send the cell. Make sure we don't double close + * it which would result in a warning. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL); + } + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_INTERNAL); + status = -2; + goto end; + + tran_err: + status = -1; + + end: + memwipe(onion_address, 0, sizeof(onion_address)); + return status; +} + +/* Using the introduction circuit circ, setup the authentication key of the + * intro point this circuit has extended to. */ +static void +setup_intro_circ_auth_key(origin_circuit_t *circ) +{ + const hs_descriptor_t *desc; + const hs_desc_intro_point_t *ip; + + tor_assert(circ); + + desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* Opening intro circuit without the descriptor is no good... */ + goto end; + } + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + ip = find_desc_intro_point_by_legacy_id( + circ->build_state->chosen_exit->identity_digest, desc); + if (ip) { + /* We got it, copy its authentication key to the identifier. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_cert->signed_key); + goto end; + } + + /* Reaching this point means we didn't find any intro point for this circuit + * which is not suppose to happen. */ + tor_assert_nonfatal_unreached(); + + end: + return; +} + +/* Called when an introduction circuit has opened. */ +static void +client_intro_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + + /* This is an introduction circuit so we'll attach the correct + * authentication key to the circuit identifier so it can be identified + * properly later on. */ + setup_intro_circ_auth_key(circ); + + connection_ap_attach_pending(1); +} + +/* Called when a rendezvous circuit has opened. */ +static void +client_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + const extend_info_t *rp_ei = circ->build_state->chosen_exit; + + /* Check that we didn't accidentally choose a node that does not understand + * the v3 rendezvous protocol */ + if (rp_ei) { + const node_t *rp_node = node_get_by_id(rp_ei->identity_digest); + if (rp_node) { + if (BUG(!node_supports_v3_rendezvous_point(rp_node))) { + return; + } + } + } + + log_info(LD_REND, "Rendezvous circuit has opened to %s.", + safe_str_client(extend_info_describe(rp_ei))); + + /* Ignore returned value, nothing we can really do. On failure, the circuit + * will be marked for close. */ + hs_circ_send_establish_rendezvous(circ); + + /* Register rend circuit in circuitmap if it's still alive. */ + if (!TO_CIRCUIT(circ)->marked_for_close) { + hs_circuitmap_register_rend_circ_client_side(circ, + circ->hs_ident->rendezvous_cookie); + } +} + +/* This is an helper function that convert a descriptor intro point object ip + * to a newly allocated extend_info_t object fully initialized. Return NULL if + * we can't convert it for which chances are that we are missing or malformed + * link specifiers. */ +STATIC extend_info_t * +desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip) +{ + extend_info_t *ei; + smartlist_t *lspecs = smartlist_new(); + + tor_assert(ip); + + /* We first encode the descriptor link specifiers into the binary + * representation which is a trunnel object. */ + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, desc_lspec) { + link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec); + smartlist_add(lspecs, lspec); + } SMARTLIST_FOREACH_END(desc_lspec); + + /* Explicitely put the direct connection option to 0 because this is client + * side and there is no such thing as a non anonymous client. */ + ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0); + + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls)); + smartlist_free(lspecs); + return ei; +} + +/* Return true iff the intro point ip for the service service_pk is usable. + * This function checks if the intro point is in the client intro state cache + * and checks at the failures. It is considered usable if: + * - No error happened (INTRO_POINT_FAILURE_GENERIC) + * - It is not flagged as timed out (INTRO_POINT_FAILURE_TIMEOUT) + * - The unreachable count is lower than + * MAX_INTRO_POINT_REACHABILITY_FAILURES (INTRO_POINT_FAILURE_UNREACHABLE) + */ +static int +intro_point_is_usable(const ed25519_public_key_t *service_pk, + const hs_desc_intro_point_t *ip) +{ + const hs_cache_intro_state_t *state; + + tor_assert(service_pk); + tor_assert(ip); + + state = hs_cache_client_intro_state_find(service_pk, + &ip->auth_key_cert->signed_key); + if (state == NULL) { + /* This means we've never encountered any problem thus usable. */ + goto usable; + } + if (state->error) { + log_info(LD_REND, "Intro point with auth key %s had an error. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->timed_out) { + log_info(LD_REND, "Intro point with auth key %s timed out. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES) { + log_info(LD_REND, "Intro point with auth key %s unreachable. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + + usable: + return 1; + not_usable: + return 0; +} + +/* Using a descriptor desc, return a newly allocated extend_info_t object of a + * randomly picked introduction point from its list. Return NULL if none are + * usable. */ +STATIC extend_info_t * +client_get_random_intro(const ed25519_public_key_t *service_pk) +{ + extend_info_t *ei = NULL, *ei_excluded = NULL; + smartlist_t *usable_ips = NULL; + const hs_descriptor_t *desc; + const hs_desc_encrypted_data_t *enc_data; + const or_options_t *options = get_options(); + /* Calculate the onion address for logging purposes */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + tor_assert(service_pk); + + desc = hs_cache_lookup_as_client(service_pk); + /* Assume the service is v3 if the descriptor is missing. This is ok, + * because we only use the address in log messages */ + hs_build_address(service_pk, + desc ? desc->plaintext_data.version : HS_VERSION_THREE, + onion_address); + if (desc == NULL || !hs_client_any_intro_points_usable(service_pk, + desc)) { + log_info(LD_REND, "Unable to randomly select an introduction point " + "for service %s because descriptor %s. We can't connect.", + safe_str_client(onion_address), + (desc) ? "doesn't have any usable intro points" + : "is missing (assuming v3 onion address)"); + goto end; + } + + enc_data = &desc->encrypted_data; + usable_ips = smartlist_new(); + smartlist_add_all(usable_ips, enc_data->intro_points); + while (smartlist_len(usable_ips) != 0) { + int idx; + const hs_desc_intro_point_t *ip; + + /* Pick a random intro point and immediately remove it from the usable + * list so we don't pick it again if we have to iterate more. */ + idx = crypto_rand_int(smartlist_len(usable_ips)); + ip = smartlist_get(usable_ips, idx); + smartlist_del(usable_ips, idx); + + /* We need to make sure we have a usable intro points which is in a good + * state in our cache. */ + if (!intro_point_is_usable(service_pk, ip)) { + continue; + } + + /* Generate an extend info object from the intro point object. */ + ei = desc_intro_point_to_extend_info(ip); + if (ei == NULL) { + /* We can get here for instance if the intro point is a private address + * and we aren't allowed to extend to those. */ + log_info(LD_REND, "Unable to select introduction point with auth key %s " + "for service %s, because we could not extend to it.", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)), + safe_str_client(onion_address)); + continue; + } + + /* Test the pick against ExcludeNodes. */ + if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) { + /* If this pick is in the ExcludeNodes list, we keep its reference so if + * we ever end up not being able to pick anything else and StrictNodes is + * unset, we'll use it. */ + if (ei_excluded) { + /* If something was already here free it. After the loop is gone we + * will examine the last excluded intro point, and that's fine since + * that's random anyway */ + extend_info_free(ei_excluded); + } + ei_excluded = ei; + continue; + } + + /* Good pick! Let's go with this. */ + goto end; + } + + /* Reaching this point means a couple of things. Either we can't use any of + * the intro point listed because the IP address can't be extended to or it + * is listed in the ExcludeNodes list. In the later case, if StrictNodes is + * set, we are forced to not use anything. */ + ei = ei_excluded; + if (options->StrictNodes) { + log_warn(LD_REND, "Every introduction point for service %s is in the " + "ExcludeNodes set and StrictNodes is set. We can't connect.", + safe_str_client(onion_address)); + extend_info_free(ei); + ei = NULL; + } else { + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Every introduction point for service " + "%s is unusable or we can't extend to it. We can't connect.", + safe_str_client(onion_address)); + } + + end: + smartlist_free(usable_ips); + memwipe(onion_address, 0, sizeof(onion_address)); + return ei; +} + +/* For this introduction circuit, we'll look at if we have any usable + * introduction point left for this service. If so, we'll use the circuit to + * re-extend to a new intro point. Else, we'll close the circuit and its + * corresponding rendezvous circuit. Return 0 if we are re-extending else -1 + * if we are closing the circuits. + * + * This is called when getting an INTRODUCE_ACK cell with a NACK. */ +static int +close_or_reextend_intro_circ(origin_circuit_t *intro_circ) +{ + int ret = -1; + const hs_descriptor_t *desc; + origin_circuit_t *rend_circ; + + tor_assert(intro_circ); + + desc = hs_cache_lookup_as_client(&intro_circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* We can't continue without a descriptor. */ + goto close; + } + /* We still have the descriptor, great! Let's try to see if we can + * re-extend by looking up if there are any usable intro points. */ + if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk, + desc)) { + goto close; + } + /* Try to re-extend now. */ + if (hs_client_reextend_intro_circuit(intro_circ) < 0) { + goto close; + } + /* Success on re-extending. Don't return an error. */ + ret = 0; + goto end; + + close: + /* Change the intro circuit purpose before so we don't report an intro point + * failure again triggering an extra descriptor fetch. The circuit can + * already be closed on failure to re-extend. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + } + /* Close the related rendezvous circuit. */ + rend_circ = hs_circuitmap_get_rend_circ_client_side( + intro_circ->hs_ident->rendezvous_cookie); + /* The rendezvous circuit might have collapsed while the INTRODUCE_ACK was + * inflight so we can't expect one every time. */ + if (rend_circ) { + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED); + } + + end: + return ret; +} + +/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate + * actions for the rendezvous point and finally close intro_circ. */ +static void +handle_introduce_ack_success(origin_circuit_t *intro_circ) +{ + origin_circuit_t *rend_circ = NULL; + + tor_assert(intro_circ); + + log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous"); + + /* Get the rendezvous circuit for this rendezvous cookie. */ + uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie; + rend_circ = hs_circuitmap_get_rend_circ_client_side(rendezvous_cookie); + if (rend_circ == NULL) { + log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping"); + goto end; + } + + assert_circ_anonymity_ok(rend_circ, get_options()); + + /* It is possible to get a RENDEZVOUS2 cell before the INTRODUCE_ACK which + * means that the circuit will be joined and already transmitting data. In + * that case, simply skip the purpose change and close the intro circuit + * like it should be. */ + if (TO_CIRCUIT(rend_circ)->purpose == CIRCUIT_PURPOSE_C_REND_JOINED) { + goto end; + } + circuit_change_purpose(TO_CIRCUIT(rend_circ), + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the + * CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED state. */ + TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL); + + end: + /* We don't need the intro circuit anymore. It did what it had to do! */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + + /* XXX: Close pending intro circuits we might have in parallel. */ + return; +} + +/* Called when we get an INTRODUCE_ACK failure status code. Depending on our + * failure cache status, either close the circuit or re-extend to a new + * introduction point. */ +static void +handle_introduce_ack_bad(origin_circuit_t *circ, int status) +{ + tor_assert(circ); + + log_info(LD_REND, "Received INTRODUCE_ACK nack by %s. Reason: %u", + safe_str_client(extend_info_describe(circ->build_state->chosen_exit)), + status); + + /* It's a NAK. The introduction point didn't relay our request. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); + + /* Note down this failure in the intro point failure cache. Depending on how + * many times we've tried this intro point, close it or reextend. */ + hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk, + &circ->hs_ident->intro_auth_pk, + INTRO_POINT_FAILURE_GENERIC); +} + +/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded + * cell is in payload of length payload_len. Return 0 on success else a + * negative value. The circuit is either close or reuse to re-extend to a new + * introduction point. */ +static int +handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int status, ret = -1; + + tor_assert(circ); + tor_assert(circ->build_state); + tor_assert(circ->build_state->chosen_exit); + assert_circ_anonymity_ok(circ, get_options()); + tor_assert(payload); + + status = hs_cell_parse_introduce_ack(payload, payload_len); + switch (status) { + case HS_CELL_INTRO_ACK_SUCCESS: + ret = 0; + handle_introduce_ack_success(circ); + goto end; + case HS_CELL_INTRO_ACK_FAILURE: + case HS_CELL_INTRO_ACK_BADFMT: + case HS_CELL_INTRO_ACK_NORELAY: + handle_introduce_ack_bad(circ, status); + /* We are going to see if we have to close the circuits (IP and RP) or we + * can re-extend to a new intro point. */ + ret = close_or_reextend_intro_circ(circ); + break; + default: + log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s", + status, + safe_str_client(extend_info_describe(circ->build_state->chosen_exit))); + break; + } + + end: + return ret; +} + +/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The + * encoded cell is in payload of length payload_len. Return 0 on success or a + * negative value on error. On error, the circuit is marked for close. */ +STATIC int +handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + curve25519_public_key_t server_pk; + uint8_t auth_mac[DIGEST256_LEN] = {0}; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + sizeof(auth_mac)] = {0}; + hs_ntor_rend_cell_keys_t keys; + const hs_ident_circuit_t *ident; + + tor_assert(circ); + tor_assert(payload); + + /* Make things easier. */ + ident = circ->hs_ident; + tor_assert(ident); + + if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info, + sizeof(handshake_info)) < 0) { + goto err; + } + /* Get from the handshake info the SERVER_PK and AUTH_MAC. */ + memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN); + memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac)); + + /* Generate the handshake info. */ + if (hs_ntor_client_get_rendezvous1_keys(&ident->intro_auth_pk, + &ident->rendezvous_client_kp, + &ident->intro_enc_pk, &server_pk, + &keys) < 0) { + log_info(LD_REND, "Unable to compute the rendezvous keys."); + goto err; + } + + /* Critical check, make sure that the MAC matches what we got with what we + * computed just above. */ + if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) { + log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell."); + goto err; + } + + /* Setup the e2e encryption on the circuit and finalize its state. */ + if (hs_circuit_setup_e2e_rend_circ(circ, keys.ntor_key_seed, + sizeof(keys.ntor_key_seed), 0) < 0) { + log_info(LD_REND, "Unable to setup the e2e encryption."); + goto err; + } + /* Success. Hidden service connection finalized! */ + ret = 0; + goto end; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + end: + memwipe(&keys, 0, sizeof(keys)); + return ret; +} + +/* Return true iff the client can fetch a descriptor for this service public + * identity key and status_out if not NULL is untouched. If the client can + * _not_ fetch the descriptor and if status_out is not NULL, it is set with + * the fetch status code. */ +static unsigned int +can_client_refetch_desc(const ed25519_public_key_t *identity_pk, + hs_client_fetch_status_t *status_out) +{ + hs_client_fetch_status_t status; + + tor_assert(identity_pk); + + /* Are we configured to fetch descriptors? */ + if (!get_options()->FetchHidServDescriptors) { + log_warn(LD_REND, "We received an onion address for a hidden service " + "descriptor but we are configured to not fetch."); + status = HS_CLIENT_FETCH_NOT_ALLOWED; + goto cannot; + } + + /* Without a live consensus we can't do any client actions. It is needed to + * compute the hashring for a service. */ + if (!networkstatus_get_live_consensus(approx_time())) { + log_info(LD_REND, "Can't fetch descriptor for service %s because we " + "are missing a live consensus. Stalling connection.", + safe_str_client(ed25519_fmt(identity_pk))); + status = HS_CLIENT_FETCH_MISSING_INFO; + goto cannot; + } + + if (!router_have_minimum_dir_info()) { + log_info(LD_REND, "Can't fetch descriptor for service %s because we " + "dont have enough descriptors. Stalling connection.", + safe_str_client(ed25519_fmt(identity_pk))); + status = HS_CLIENT_FETCH_MISSING_INFO; + goto cannot; + } + + /* Check if fetching a desc for this HS is useful to us right now */ + { + const hs_descriptor_t *cached_desc = NULL; + cached_desc = hs_cache_lookup_as_client(identity_pk); + if (cached_desc && hs_client_any_intro_points_usable(identity_pk, + cached_desc)) { + log_info(LD_GENERAL, "We would fetch a v3 hidden service descriptor " + "but we already have a usable descriptor."); + status = HS_CLIENT_FETCH_HAVE_DESC; + goto cannot; + } + } + + /* Don't try to refetch while we have a pending request for it. */ + if (directory_request_is_pending(identity_pk)) { + log_info(LD_REND, "Already a pending directory request. Waiting on it."); + status = HS_CLIENT_FETCH_PENDING; + goto cannot; + } + + /* Yes, client can fetch! */ + return 1; + cannot: + if (status_out) { + *status_out = status; + } + return 0; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/** A circuit just finished connecting to a hidden service that the stream + * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */ +void +hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn) +{ + tor_assert(connection_edge_is_rendezvous_stream(conn)); + + if (BUG(conn->rend_data && conn->hs_ident)) { + log_warn(LD_BUG, "Stream had both rend_data and hs_ident..." + "Prioritizing hs_ident"); + } + + if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */ + note_connection_attempt_succeeded(conn->hs_ident); + return; + } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */ + rend_client_note_connection_attempt_ended(conn->rend_data); + return; + } +} + +/* With the given encoded descriptor in desc_str and the service key in + * service_identity_pk, decode the descriptor and set the desc pointer with a + * newly allocated descriptor object. + * + * Return 0 on success else a negative value and desc is set to NULL. */ +int +hs_client_decode_descriptor(const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc) +{ + int ret; + uint8_t subcredential[DIGEST256_LEN]; + ed25519_public_key_t blinded_pubkey; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + tor_assert(desc); + + /* Create subcredential for this HS so that we can decrypt */ + { + uint64_t current_time_period = hs_get_time_period_num(0); + hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, + &blinded_pubkey); + hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential); + } + + /* Parse descriptor */ + ret = hs_desc_decode_descriptor(desc_str, subcredential, desc); + memwipe(subcredential, 0, sizeof(subcredential)); + if (ret < 0) { + log_warn(LD_GENERAL, "Could not parse received descriptor as client."); + if (get_options()->SafeLogging_ == SAFELOG_SCRUB_NONE) { + log_warn(LD_GENERAL, "%s", escaped(desc_str)); + } + goto err; + } + + /* Make sure the descriptor signing key cross certifies with the computed + * blinded key. Without this validation, anyone knowing the subcredential + * and onion address can forge a descriptor. */ + if (tor_cert_checksig((*desc)->plaintext_data.signing_key_cert, + &blinded_pubkey, approx_time()) < 0) { + log_warn(LD_GENERAL, "Descriptor signing key certificate signature " + "doesn't validate with computed blinded key."); + goto err; + } + + return 0; + err: + return -1; +} + +/* Return true iff there are at least one usable intro point in the service + * descriptor desc. */ +int +hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc) +{ + tor_assert(service_pk); + tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (intro_point_is_usable(service_pk, ip)) { + goto usable; + } + } SMARTLIST_FOREACH_END(ip); + + return 0; + usable: + return 1; +} + +/** Launch a connection to a hidden service directory to fetch a hidden + * service descriptor using <b>identity_pk</b> to get the necessary keys. + * + * A hs_client_fetch_status_t code is returned. */ +int +hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) +{ + hs_client_fetch_status_t status; + + tor_assert(identity_pk); + + if (!can_client_refetch_desc(identity_pk, &status)) { + return status; + } + + /* Try to fetch the desc and if we encounter an unrecoverable error, mark + * the desc as unavailable for now. */ + status = fetch_v3_desc(identity_pk); + if (fetch_status_should_close_socks(status)) { + close_all_socks_conns_waiting_for_desc(identity_pk, status, + END_STREAM_REASON_RESOLVEFAILED); + /* Remove HSDir fetch attempts so that we can retry later if the user + * wants us to regardless of if we closed any connections. */ + purge_hid_serv_request(identity_pk); + } + return status; +} + +/* This is called when we are trying to attach an AP connection to these + * hidden service circuits from connection_ap_handshake_attach_circuit(). + * Return 0 on success, -1 for a transient error that is actions were + * triggered to recover or -2 for a permenent error where both circuits will + * marked for close. + * + * The following supports every hidden service version. */ +int +hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + return (intro_circ->hs_ident) ? send_introduce1(intro_circ, rend_circ) : + rend_client_send_introduction(intro_circ, + rend_circ); +} + +/* Called when the client circuit circ has been established. It can be either + * an introduction or rendezvous circuit. This function handles all hidden + * service versions. */ +void +hs_client_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_C_INTRODUCING: + if (circ->hs_ident) { + client_intro_circ_has_opened(circ); + } else { + rend_client_introcirc_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_C_ESTABLISH_REND: + if (circ->hs_ident) { + client_rendezvous_circ_has_opened(circ); + } else { + rend_client_rendcirc_has_opened(circ); + } + break; + default: + tor_assert_nonfatal_unreached(); + } +} + +/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of + * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a + * negative value and the circuit marked for close. */ +int +hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + tor_assert(circ); + tor_assert(payload); + + (void) payload_len; + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { + log_warn(LD_PROTOCOL, "Got a RENDEZVOUS_ESTABLISHED but we were not " + "expecting one. Closing circuit."); + goto err; + } + + log_info(LD_REND, "Received an RENDEZVOUS_ESTABLISHED. This circuit is " + "now ready for rendezvous."); + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); + + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_REND_READY state. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* From a path bias point of view, this circuit is now successfully used. + * Waiting any longer opens us up to attacks from malicious hidden services. + * They could induce the client to attempt to connect to their hidden + * service and never reply to the client's rend requests */ + pathbias_mark_use_success(circ); + + /* If we already have the introduction circuit built, make sure we send + * the INTRODUCE cell _now_ */ + connection_ap_attach_pending(1); + + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + +/* This is called when a descriptor has arrived following a fetch request and + * has been stored in the client cache. Every entry connection that matches + * the service identity key in the ident will get attached to the hidden + * service circuit. */ +void +hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) +{ + time_t now = time(NULL); + smartlist_t *conns = NULL; + + tor_assert(ident); + + conns = connection_list_by_type_state(CONN_TYPE_AP, + AP_CONN_STATE_RENDDESC_WAIT); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + const hs_descriptor_t *desc; + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we just fetched its descriptor. */ + if (!edge_conn->hs_ident || + !ed25519_pubkey_eq(&ident->identity_pk, + &edge_conn->hs_ident->identity_pk)) { + continue; + } + assert_connection_ok(base_conn, now); + + /* We were just called because we stored the descriptor for this service + * so not finding a descriptor means we have a bigger problem. */ + desc = hs_cache_lookup_as_client(&ident->identity_pk); + if (BUG(desc == NULL)) { + goto end; + } + + if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) { + log_info(LD_REND, "Hidden service descriptor is unusable. " + "Closing streams."); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_RESOLVEFAILED); + /* We are unable to use the descriptor so remove the directory request + * from the cache so the next connection can try again. */ + note_connection_attempt_succeeded(edge_conn->hs_ident); + goto end; + } + + log_info(LD_REND, "Descriptor has arrived. Launching circuits."); + + /* Because the connection can now proceed to opening circuit and + * ultimately connect to the service, reset those timestamp so the + * connection is considered "fresh" and can continue without being closed + * too early. */ + base_conn->timestamp_created = now; + base_conn->timestamp_lastread = now; + base_conn->timestamp_lastwritten = now; + /* Change connection's state into waiting for a circuit. */ + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + + connection_ap_mark_as_pending_circuit(entry_conn); + } SMARTLIST_FOREACH_END(base_conn); + + end: + /* We don't have ownership of the objects in this list. */ + smartlist_free(conns); +} + +/* Return a newly allocated extend_info_t for a randomly chosen introduction + * point for the given edge connection identifier ident. Return NULL if we + * can't pick any usable introduction points. */ +extend_info_t * +hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn) +{ + tor_assert(edge_conn); + + return (edge_conn->hs_ident) ? + client_get_random_intro(&edge_conn->hs_ident->identity_pk) : + rend_client_get_random_intro(edge_conn->rend_data); +} +/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ. + * Return 0 on success else a negative value is returned. The circuit will be + * closed or reuse to extend again to another intro point. */ +int +hs_client_receive_introduce_ack(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { + log_warn(LD_PROTOCOL, "Unexpected INTRODUCE_ACK on circuit %u.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + ret = (circ->hs_ident) ? handle_introduce_ack(circ, payload, payload_len) : + rend_client_introduction_acked(circ, payload, + payload_len); + /* For path bias: This circuit was used successfully. NACK or ACK counts. */ + pathbias_mark_use_success(circ); + + end: + return ret; +} + +/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return + * 0 on success else a negative value is returned. The circuit will be closed + * on error. */ +int +hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Circuit can possibly be in both state because we could receive a + * RENDEZVOUS2 cell before the INTRODUCE_ACK has been received. */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_PROTOCOL, "Unexpected RENDEZVOUS2 cell on circuit %u. " + "Closing circuit.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + log_info(LD_REND, "Got RENDEZVOUS2 cell from hidden service on circuit %u.", + TO_CIRCUIT(circ)->n_circ_id); + + ret = (circ->hs_ident) ? handle_rendezvous2(circ, payload, payload_len) : + rend_client_receive_rendezvous(circ, payload, + payload_len); + end: + return ret; +} + +/* Extend the introduction circuit circ to another valid introduction point + * for the hidden service it is trying to connect to, or mark it and launch a + * new circuit if we can't extend it. Return 0 on success or possible + * success. Return -1 and mark the introduction circuit for close on permanent + * failure. + * + * On failure, the caller is responsible for marking the associated rendezvous + * circuit for close. */ +int +hs_client_reextend_intro_circuit(origin_circuit_t *circ) +{ + int ret = -1; + extend_info_t *ei; + + tor_assert(circ); + + ei = (circ->hs_ident) ? + client_get_random_intro(&circ->hs_ident->identity_pk) : + rend_client_get_random_intro(circ->rend_data); + if (ei == NULL) { + log_warn(LD_REND, "No usable introduction points left. Closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + + if (circ->remaining_relay_early_cells) { + log_info(LD_REND, "Re-extending circ %u, this time to %s.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(extend_info_describe(ei))); + ret = circuit_extend_to_new_exit(circ, ei); + if (ret == 0) { + /* We were able to extend so update the timestamp so we avoid expiring + * this circuit too early. The intro circuit is short live so the + * linkability issue is minimized, we just need the circuit to hold a + * bit longer so we can introduce. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + } + } else { + log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ + ret = 0; + } + + end: + extend_info_free(ei); + return ret; +} + +/* Release all the storage held by the client subsystem. */ +void +hs_client_free_all(void) +{ + /* Purge the hidden service request cache. */ + hs_purge_last_hid_serv_requests(); +} + +/* Purge all potentially remotely-detectable state held in the hidden + * service client code. Called on SIGNAL NEWNYM. */ +void +hs_client_purge_state(void) +{ + /* v2 subsystem. */ + rend_client_purge_state(); + + /* Cancel all descriptor fetches. Do this first so once done we are sure + * that our descriptor cache won't modified. */ + cancel_descriptor_fetches(); + /* Purge the introduction point state cache. */ + hs_cache_client_intro_state_purge(); + /* Purge the descriptor cache. */ + hs_cache_purge_as_client(); + /* Purge the last hidden service request cache. */ + hs_purge_last_hid_serv_requests(); + + log_info(LD_REND, "Hidden service client state has been purged."); +} + +/* Called when our directory information has changed. */ +void +hs_client_dir_info_changed(void) +{ + /* We have possibly reached the minimum directory information or new + * consensus so retry all pending SOCKS connection in + * AP_CONN_STATE_RENDDESC_WAIT state in order to fetch the descriptor. */ + retry_all_socks_conn_waiting_for_desc(); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h new file mode 100644 index 0000000000..2523568ad1 --- /dev/null +++ b/src/or/hs_client.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_client.h + * \brief Header file containing client data for the HS subsytem. + **/ + +#ifndef TOR_HS_CLIENT_H +#define TOR_HS_CLIENT_H + +#include "crypto_ed25519.h" +#include "hs_descriptor.h" +#include "hs_ident.h" + +/* Status code of a descriptor fetch request. */ +typedef enum { + /* Something internally went wrong. */ + HS_CLIENT_FETCH_ERROR = -1, + /* The fetch request has been launched successfully. */ + HS_CLIENT_FETCH_LAUNCHED = 0, + /* We already have a usable descriptor. No fetch. */ + HS_CLIENT_FETCH_HAVE_DESC = 1, + /* No more HSDir available to query. */ + HS_CLIENT_FETCH_NO_HSDIRS = 2, + /* The fetch request is not allowed. */ + HS_CLIENT_FETCH_NOT_ALLOWED = 3, + /* We are missing information to be able to launch a request. */ + HS_CLIENT_FETCH_MISSING_INFO = 4, + /* There is a pending fetch for the requested service. */ + HS_CLIENT_FETCH_PENDING = 5, +} hs_client_fetch_status_t; + +void hs_client_note_connection_attempt_succeeded( + const edge_connection_t *conn); + +int hs_client_decode_descriptor( + const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc); +int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc); +int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk); +void hs_client_dir_info_changed(void); + +int hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ); + +void hs_client_circuit_has_opened(origin_circuit_t *circ); + +int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_client_receive_introduce_ack(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + +void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); + +extend_info_t *hs_client_get_random_intro_from_edge( + const edge_connection_t *edge_conn); + +int hs_client_reextend_intro_circuit(origin_circuit_t *circ); + +void hs_client_purge_state(void); + +void hs_client_free_all(void); + +#ifdef HS_CLIENT_PRIVATE + +STATIC routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk); + +STATIC extend_info_t * +client_get_random_intro(const ed25519_public_key_t *service_pk); + +STATIC extend_info_t * +desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip); + +STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len); + +MOCK_DECL(STATIC hs_client_fetch_status_t, + fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)); + +#endif /* defined(HS_CLIENT_PRIVATE) */ + +#endif /* !defined(TOR_HS_CLIENT_H) */ + diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 7cef5a8e2c..ad783a36fb 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -14,9 +14,167 @@ #include "or.h" #include "config.h" +#include "circuitbuild.h" #include "networkstatus.h" +#include "nodelist.h" +#include "hs_cache.h" #include "hs_common.h" +#include "hs_client.h" +#include "hs_ident.h" +#include "hs_service.h" +#include "hs_circuitmap.h" +#include "policies.h" #include "rendcommon.h" +#include "rendservice.h" +#include "routerset.h" +#include "router.h" +#include "routerset.h" +#include "shared_random.h" +#include "shared_random_state.h" + +/* Trunnel */ +#include "ed25519_cert.h" + +/* Ed25519 Basepoint value. Taken from section 5 of + * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ +static const char *str_ed25519_basepoint = + "(15112221349535400772501151409588531511" + "454012693041857206046113283949847762202, " + "463168356949264781694283940034751631413" + "07993866256225615783033603165251855960)"; + +#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 /* defined(HAVE_SYS_UN_H) */ + +/* Helper function: The key is a digest that we compare to a node_t object + * current hsdir_index. */ +static int +compare_digest_to_fetch_hsdir_index(const void *_key, const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->fetch, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_store_first_hsdir_index(const void *_key, + const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->store_first, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_store_second_hsdir_index(const void *_key, + const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->store_second, DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects current hsdir_index. */ +static int +compare_node_fetch_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->fetch, + node2->hsdir_index->fetch, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_store_first_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->store_first, + node2->hsdir_index->store_first, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_store_second_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->store_second, + node2->hsdir_index->store_second, + DIGEST256_LEN); +} + +/* Allocate and return a string containing the path to filename in directory. + * This function will never return NULL. The caller must free this path. */ +char * +hs_path_from_filename(const char *directory, const char *filename) +{ + char *file_path = NULL; + + tor_assert(directory); + tor_assert(filename); + + tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename); + return file_path; +} /* Make sure that the directory for <b>service</b> is private, using the config * <b>username</b>. @@ -56,6 +214,17 @@ hs_check_service_private_dir(const char *username, const char *path, STATIC uint64_t get_time_period_length(void) { + /* If we are on a test network, make the time period smaller than normal so + that we actually see it rotate. Specifically, make it the same length as + an SRV protocol run. */ + if (get_options()->TestingTorNetwork) { + unsigned run_duration = sr_state_get_protocol_run_duration(); + /* An SRV run should take more than a minute (it's 24 rounds) */ + tor_assert_nonfatal(run_duration > 60); + /* Turn it from seconds to minutes before returning: */ + return sr_state_get_protocol_run_duration() / 60; + } + int32_t time_period_length = networkstatus_get_param(NULL, "hsdir_interval", HS_TIME_PERIOD_LENGTH_DEFAULT, HS_TIME_PERIOD_LENGTH_MIN, @@ -66,18 +235,34 @@ get_time_period_length(void) return (uint64_t) time_period_length; } -/** Get the HS time period number at time <b>now</b> */ -STATIC uint64_t -get_time_period_num(time_t now) +/** Get the HS time period number at time <b>now</b>. If <b>now</b> is not set, + * we try to get the time ourselves from a live consensus. */ +uint64_t +hs_get_time_period_num(time_t now) { uint64_t time_period_num; + time_t current_time; + + /* If no time is specified, set current time based on consensus time, and + * only fall back to system time if that fails. */ + if (now != 0) { + current_time = now; + } else { + networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); + current_time = ns ? ns->valid_after : approx_time(); + } + + /* Start by calculating minutes since the epoch */ uint64_t time_period_length = get_time_period_length(); - uint64_t minutes_since_epoch = now / 60; + uint64_t minutes_since_epoch = current_time / 60; - /* Now subtract half a day to fit the prop224 time period schedule (see - * section [TIME-PERIODS]). */ - tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET); - minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET; + /* Apply the rotation offset as specified by prop224 (section + * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV + * publication */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + time_period_rotation_offset /= 60; /* go from seconds to minutes */ + tor_assert(minutes_since_epoch > time_period_rotation_offset); + minutes_since_epoch -= time_period_rotation_offset; /* Calculate the time period */ time_period_num = minutes_since_epoch / time_period_length; @@ -85,11 +270,38 @@ get_time_period_num(time_t now) } /** Get the number of the _upcoming_ HS time period, given that the current - * time is <b>now</b>. */ + * time is <b>now</b>. If <b>now</b> is not set, we try to get the time from a + * live consensus. */ uint64_t hs_get_next_time_period_num(time_t now) { - return get_time_period_num(now) + 1; + return hs_get_time_period_num(now) + 1; +} + +/* Get the number of the _previous_ HS time period, given that the current time + * is <b>now</b>. If <b>now</b> is not set, we try to get the time from a live + * consensus. */ +uint64_t +hs_get_previous_time_period_num(time_t now) +{ + return hs_get_time_period_num(now) - 1; +} + +/* Return the start time of the upcoming time period based on <b>now</b>. If + <b>now</b> is not set, we try to get the time ourselves from a live + consensus. */ +time_t +hs_get_start_time_of_next_time_period(time_t now) +{ + uint64_t time_period_length = get_time_period_length(); + + /* Get start time of next time period */ + uint64_t next_time_period_num = hs_get_next_time_period_num(now); + uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length; + + /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + return (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset); } /* Create a new rend_data_t for a specific given <b>version</b>. @@ -344,3 +556,1238 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } +/* Using the given time period number, compute the disaster shared random + * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */ +static void +compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + crypto_digest_t *digest; + + tor_assert(srv_out); + + digest = crypto_digest256_new(DIGEST_SHA3_256); + + /* Start setting up payload: + * H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */ + crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX, + HS_SRV_DISASTER_PREFIX_LEN); + + /* Setup INT_8(period_length) | INT_8(period_num) */ + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + + crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/** Due to the high cost of computing the disaster SRV and that potentially we + * would have to do it thousands of times in a row, we always cache the + * computer disaster SRV (and its corresponding time period num) in case we + * want to reuse it soon after. We need to cache two SRVs, one for each active + * time period. + */ +static uint8_t cached_disaster_srv[2][DIGEST256_LEN]; +static uint64_t cached_time_period_nums[2] = {0}; + +/** Compute the disaster SRV value for this <b>time_period_num</b> and put it + * in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches + * to see if we have already computed it. */ +STATIC void +get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + if (time_period_num == cached_time_period_nums[0]) { + memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN); + return; + } else if (time_period_num == cached_time_period_nums[1]) { + memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN); + return; + } else { + int replace_idx; + // Replace the lower period number. + if (cached_time_period_nums[0] <= cached_time_period_nums[1]) { + replace_idx = 0; + } else { + replace_idx = 1; + } + cached_time_period_nums[replace_idx] = time_period_num; + compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]); + memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN); + return; + } +} + +#ifdef TOR_UNIT_TESTS + +/** Get the first cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_first_cached_disaster_srv(void) +{ + return cached_disaster_srv[0]; +} + +/** Get the second cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_second_cached_disaster_srv(void) +{ + return cached_disaster_srv[1]; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + +/* When creating a blinded key, we need a parameter which construction is as + * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). + * + * The nonce has a pre-defined format which uses the time period number + * period_num and the start of the period in second start_time_period. + * + * The secret of size secret_len is optional meaning that it can be NULL and + * thus will be ignored for the param construction. + * + * The result is put in param_out. */ +static void +build_blinded_key_param(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t period_num, uint64_t period_length, + uint8_t *param_out) +{ + size_t offset = 0; + const char blind_str[] = "Derive temporary signing key"; + uint8_t nonce[HS_KEYBLIND_NONCE_LEN]; + crypto_digest_t *digest; + + tor_assert(pubkey); + tor_assert(param_out); + + /* Create the nonce N. The construction is as follow: + * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */ + memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN); + offset += HS_KEYBLIND_NONCE_PREFIX_LEN; + set_uint64(nonce + offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(nonce + offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == HS_KEYBLIND_NONCE_LEN); + + /* Generate the parameter h and the construction is as follow: + * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str)); + crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN); + /* Optional secret. */ + if (secret) { + crypto_digest_add_bytes(digest, (char *) secret, secret_len); + } + crypto_digest_add_bytes(digest, str_ed25519_basepoint, + strlen(str_ed25519_basepoint)); + crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce)); + + /* Extract digest and put it in the param. */ + crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(nonce, 0, sizeof(nonce)); +} + +/* Using an ed25519 public key and version to build the checksum of an + * address. Put in checksum_out. Format is: + * SHA3-256(".onion checksum" || PUBKEY || VERSION) + * + * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */ +static void +build_hs_checksum(const ed25519_public_key_t *key, uint8_t version, + uint8_t *checksum_out) +{ + size_t offset = 0; + char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN]; + + /* Build checksum data. */ + memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX, + HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN); + offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN; + memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + set_uint8(data + offset, version); + offset += sizeof(version); + tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN); + + /* Hash the data payload to create the checksum. */ + crypto_digest256((char *) checksum_out, data, sizeof(data), + DIGEST_SHA3_256); +} + +/* Using an ed25519 public key, checksum and version to build the binary + * representation of a service address. Put in addr_out. Format is: + * addr_out = PUBKEY || CHECKSUM || VERSION + * + * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */ +static void +build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum, + uint8_t version, char *addr_out) +{ + size_t offset = 0; + + tor_assert(key); + tor_assert(checksum); + + memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + set_uint8(addr_out + offset, version); + offset += sizeof(uint8_t); + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Helper for hs_parse_address(): Using a binary representation of a service + * address, parse its content into the key_out, checksum_out and version_out. + * Any out variable can be NULL in case the caller would want only one field. + * checksum_out MUST at least be 2 bytes long. address must be at least + * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */ +static void +hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + size_t offset = 0; + + tor_assert(address); + + if (key_out) { + /* First is the key. */ + memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN); + } + offset += ED25519_PUBKEY_LEN; + if (checksum_out) { + /* Followed by a 2 bytes checksum. */ + memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + } + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + if (version_out) { + /* Finally, version value is 1 byte. */ + *version_out = get_uint8(address + offset); + } + offset += sizeof(uint8_t); + /* Extra safety. */ + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Using the given identity public key and a blinded public key, compute the + * subcredential and put it in subcred_out (must be of size DIGEST256_LEN). + * This can't fail. */ +void +hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out) +{ + uint8_t credential[DIGEST256_LEN]; + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(subcred_out); + + /* First, build the credential. Construction is as follow: + * credential = H("credential" | public-identity-key) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX, + HS_CREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN); + crypto_digest_free(digest); + + /* Now, compute the subcredential. Construction is as follow: + * subcredential = H("subcredential" | credential | blinded-public-key). */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX, + HS_SUBCREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) credential, + sizeof(credential)); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(credential, 0, sizeof(credential)); +} + +/* From the given list of hidden service ports, find the ones that much the + * given edge connection conn, pick one at random and use it to set the + * connection address. Return 0 on success or -1 if none. */ +int +hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) +{ + rend_service_port_config_t *chosen_port; + unsigned int warn_once = 0; + smartlist_t *matching_ports; + + tor_assert(ports); + tor_assert(conn); + + matching_ports = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (TO_CONN(conn)->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 " + "which is unsupported on this platform. " + "Ignoring it.", + TO_CONN(conn)->port); + } + warn_once++; + } + } + } SMARTLIST_FOREACH_END(p); + + chosen_port = smartlist_choose(matching_ports); + smartlist_free(matching_ports); + if (chosen_port) { + if (!(chosen_port->is_unix_addr)) { + /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ + tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr); + TO_CONN(conn)->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 (chosen_port) ? 0 : -1; +} + +/* Using a base32 representation of a service address, parse its content into + * the key_out, checksum_out and version_out. Any out variable can be NULL in + * case the caller would want only one field. checksum_out MUST at least be 2 + * bytes long. + * + * Return 0 if parsing went well; return -1 in case of error. */ +int +hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + char decoded[HS_SERVICE_ADDR_LEN]; + + tor_assert(address); + + /* Obvious length check. */ + if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) { + log_warn(LD_REND, "Service address %s has an invalid length. " + "Expected %lu but got %lu.", + escaped_safe_str(address), + (unsigned long) HS_SERVICE_ADDR_LEN_BASE32, + (unsigned long) strlen(address)); + goto invalid; + } + + /* Decode address so we can extract needed fields. */ + if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) { + log_warn(LD_REND, "Service address %s can't be decoded.", + escaped_safe_str(address)); + goto invalid; + } + + /* Parse the decoded address into the fields we need. */ + hs_parse_address_impl(decoded, key_out, checksum_out, version_out); + + return 0; + invalid: + return -1; +} + +/* Validate a given onion address. The length, the base32 decoding and + * checksum are validated. Return 1 if valid else 0. */ +int +hs_address_is_valid(const char *address) +{ + uint8_t version; + uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED]; + uint8_t target_checksum[DIGEST256_LEN]; + ed25519_public_key_t service_pubkey; + + /* Parse the decoded address into the fields we need. */ + if (hs_parse_address(address, &service_pubkey, checksum, &version) < 0) { + goto invalid; + } + + /* Get the checksum it's suppose to be and compare it with what we have + * encoded in the address. */ + build_hs_checksum(&service_pubkey, version, target_checksum); + if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) { + log_warn(LD_REND, "Service address %s invalid checksum.", + escaped_safe_str(address)); + goto invalid; + } + + /* Validate that this pubkey does not have a torsion component. We need to do + * this on the prop224 client-side so that attackers can't give equivalent + * forms of an onion address to users. */ + if (ed25519_validate_pubkey(&service_pubkey) < 0) { + log_warn(LD_REND, "Service address %s has bad pubkey .", + escaped_safe_str(address)); + goto invalid; + } + + /* Valid address. */ + return 1; + invalid: + return 0; +} + +/* Build a service address using an ed25519 public key and a given version. + * The returned address is base32 encoded and put in addr_out. The caller MUST + * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long. + * + * Format is as follow: + * base32(PUBKEY || CHECKSUM || VERSION) + * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION) + * */ +void +hs_build_address(const ed25519_public_key_t *key, uint8_t version, + char *addr_out) +{ + uint8_t checksum[DIGEST256_LEN]; + char address[HS_SERVICE_ADDR_LEN]; + + tor_assert(key); + tor_assert(addr_out); + + /* Get the checksum of the address. */ + build_hs_checksum(key, version, checksum); + /* Get the binary address representation. */ + build_hs_address(key, checksum, version, address); + + /* Encode the address. addr_out will be NUL terminated after this. */ + base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address, + sizeof(address)); + /* Validate what we just built. */ + tor_assert(hs_address_is_valid(addr_out)); +} + +/* Return a newly allocated copy of lspec. */ +link_specifier_t * +hs_link_specifier_dup(const link_specifier_t *lspec) +{ + link_specifier_t *dup = link_specifier_new(); + memcpy(dup, lspec, sizeof(*dup)); + /* The unrecognized field is a dynamic array so make sure to copy its + * content and not the pointer. */ + link_specifier_setlen_un_unrecognized( + dup, link_specifier_getlen_un_unrecognized(lspec)); + if (link_specifier_getlen_un_unrecognized(dup)) { + memcpy(link_specifier_getarray_un_unrecognized(dup), + link_specifier_getconstarray_un_unrecognized(lspec), + link_specifier_getlen_un_unrecognized(dup)); + } + return dup; +} + +/* From a given ed25519 public key pk and an optional secret, compute a + * blinded public key and put it in blinded_pk_out. This is only useful to + * the client side because the client only has access to the identity public + * key of the service. */ +void +hs_build_blinded_pubkey(const ed25519_public_key_t *pk, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *blinded_pk_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(pk); + tor_assert(blinded_pk_out); + tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN)); + + build_blinded_key_param(pk, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_public_blind(blinded_pk_out, pk, param); + + memwipe(param, 0, sizeof(param)); +} + +/* From a given ed25519 keypair kp and an optional secret, compute a blinded + * keypair for the current time period and put it in blinded_kp_out. This is + * only useful by the service side because the client doesn't have access to + * the identity secret key. */ +void +hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *blinded_kp_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(kp); + tor_assert(blinded_kp_out); + /* Extra safety. A zeroed key is bad. */ + tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN)); + tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN)); + + build_blinded_key_param(&kp->pubkey, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_keypair_blind(blinded_kp_out, kp, param); + + memwipe(param, 0, sizeof(param)); +} + +/* Return true if we are currently in the time segment between a new time + * period and a new SRV (in the real network that happens between 12:00 and + * 00:00 UTC). Here is a diagram showing exactly when this returns true: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^^^^^^^^^^^^ ^^^^^^^^^^^^ | + * | | + * +------------------------------------------------------------------+ + */ +MOCK_IMPL(int, +hs_in_period_between_tp_and_srv,(const networkstatus_t *consensus, time_t now)) +{ + time_t valid_after; + time_t srv_start_time, tp_start_time; + + if (!consensus) { + consensus = networkstatus_get_live_consensus(now); + if (!consensus) { + return 0; + } + } + + /* Get start time of next TP and of current SRV protocol run, and check if we + * are between them. */ + valid_after = consensus->valid_after; + srv_start_time = + sr_state_get_start_time_of_current_protocol_run(valid_after); + tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time); + + if (valid_after >= srv_start_time && valid_after < tp_start_time) { + return 0; + } + + return 1; +} + +/* Return 1 if any virtual port in ports needs a circuit with good uptime. + * Else return 0. */ +int +hs_service_requires_uptime_circ(const smartlist_t *ports) +{ + tor_assert(ports); + + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, + p->virtual_port)) { + return 1; + } + } SMARTLIST_FOREACH_END(p); + return 0; +} + +/* Build hs_index which is used to find the responsible hsdirs. This index + * value is used to select the responsible HSDir where their hsdir_index is + * closest to this value. + * SHA3-256("store-at-idx" | blinded_public_key | + * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) ) + * + * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out) +{ + crypto_digest_t *digest; + + tor_assert(blinded_pk); + tor_assert(hs_index_out); + + /* Build hs_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + + /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */ + { + uint64_t period_length = get_time_period_length(); + char buf[sizeof(uint64_t)*3]; + size_t offset = 0; + set_uint64(buf, tor_htonll(replica)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(buf)); + + crypto_digest_add_bytes(digest, buf, sizeof(buf)); + } + + crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Build hsdir_index which is used to find the responsible hsdirs. This is the + * index value that is compare to the hs_index when selecting an HSDir. + * SHA3-256("node-idx" | node_identity | + * shared_random_value | INT_8(period_length) | INT_8(period_num) ) + * + * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv_value, uint64_t period_num, + uint8_t *hsdir_index_out) +{ + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(srv_value); + tor_assert(hsdir_index_out); + + /* Build hsdir_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN); + + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + + crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Return a newly allocated buffer containing the current shared random value + * or if not present, a disaster value is computed using the given time period + * number. If a consensus is provided in <b>ns</b>, use it to get the SRV + * value. This function can't fail. */ +uint8_t * +hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *current_srv = sr_get_current(ns); + + if (current_srv) { + memcpy(sr_value, current_srv->value, sizeof(current_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return a newly allocated buffer containing the previous shared random + * value or if not present, a disaster value is computed using the given time + * period number. This function can't fail. */ +uint8_t * +hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *previous_srv = sr_get_previous(ns); + + if (previous_srv) { + memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return the number of replicas defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_n_replicas(void) +{ + /* The [1,16] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_n_replicas", + HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16); +} + +/* Return the spread fetch value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_fetch(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_fetch", + HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128); +} + +/* Return the spread store value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_store(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_store", + HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128); +} + +/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index. + * Return 0 if everything is as expected, else return -1. */ +static int +node_has_hsdir_index(const node_t *node) +{ + tor_assert(node_supports_v3_hsdir(node)); + + /* A node can't have an HSDir index without a descriptor since we need desc + * to get its ed25519 key */ + if (!node_has_descriptor(node)) { + return 0; + } + + /* At this point, since the node has a desc, this node must also have an + * hsdir index. If not, something went wrong, so BUG out. */ + if (BUG(node->hsdir_index == NULL)) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->fetch, + DIGEST256_LEN))) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_first, + DIGEST256_LEN))) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_second, + DIGEST256_LEN))) { + return 0; + } + + return 1; +} + +/* For a given blinded key and time period number, get the responsible HSDir + * and put their routerstatus_t object in the responsible_dirs list. If + * 'use_second_hsdir_index' is true, use the second hsdir_index of the node_t + * is used. If 'for_fetching' is true, the spread fetch consensus parameter is + * used else the spread store is used which is only for upload. This function + * can't fail but it is possible that the responsible_dirs list contains fewer + * nodes than expected. + * + * This function goes over the latest consensus routerstatus list and sorts it + * by their node_t hsdir_index then does a binary search to find the closest + * node. All of this makes it a bit CPU intensive so use it wisely. */ +void +hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs) +{ + smartlist_t *sorted_nodes; + /* The compare function used for the smartlist bsearch. We have two + * different depending on is_next_period. */ + int (*cmp_fct)(const void *, const void **); + + tor_assert(blinded_pk); + tor_assert(responsible_dirs); + + sorted_nodes = smartlist_new(); + + /* Add every node_t that support HSDir v3 for which we do have a valid + * hsdir_index already computed for them for this consensus. */ + { + networkstatus_t *c = networkstatus_get_latest_consensus(); + if (!c || smartlist_len(c->routerstatus_list) == 0) { + log_warn(LD_REND, "No valid consensus so we can't get the responsible " + "hidden service directories."); + goto done; + } + SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) { + /* Even though this node_t object won't be modified and should be const, + * we can't add const object in a smartlist_t. */ + node_t *n = node_get_mutable_by_id(rs->identity_digest); + tor_assert(n); + if (node_supports_v3_hsdir(n) && rs->is_hs_dir) { + if (!node_has_hsdir_index(n)) { + log_info(LD_GENERAL, "Node %s was found without hsdir index.", + node_describe(n)); + continue; + } + smartlist_add(sorted_nodes, n); + } + } SMARTLIST_FOREACH_END(rs); + } + if (smartlist_len(sorted_nodes) == 0) { + log_warn(LD_REND, "No nodes found to be HSDir or supporting v3."); + goto done; + } + + /* First thing we have to do is sort all node_t by hsdir_index. The + * is_next_period tells us if we want the current or the next one. Set the + * bsearch compare function also while we are at it. */ + if (for_fetching) { + smartlist_sort(sorted_nodes, compare_node_fetch_hsdir_index); + cmp_fct = compare_digest_to_fetch_hsdir_index; + } else if (use_second_hsdir_index) { + smartlist_sort(sorted_nodes, compare_node_store_second_hsdir_index); + cmp_fct = compare_digest_to_store_second_hsdir_index; + } else { + smartlist_sort(sorted_nodes, compare_node_store_first_hsdir_index); + cmp_fct = compare_digest_to_store_first_hsdir_index; + } + + /* For all replicas, we'll select a set of HSDirs using the consensus + * parameters and the sorted list. The replica starting at value 1 is + * defined by the specification. */ + for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) { + int idx, start, found, n_added = 0; + uint8_t hs_index[DIGEST256_LEN] = {0}; + /* Number of node to add to the responsible dirs list depends on if we are + * trying to fetch or store. A client always fetches. */ + int n_to_add = (for_fetching) ? hs_get_hsdir_spread_fetch() : + hs_get_hsdir_spread_store(); + + /* Get the index that we should use to select the node. */ + hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index); + /* The compare function pointer has been set correctly earlier. */ + start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct, + &found); + /* Getting the length of the list if no member is greater than the key we + * are looking for so start at the first element. */ + if (idx == smartlist_len(sorted_nodes)) { + start = idx = 0; + } + while (n_added < n_to_add) { + const node_t *node = smartlist_get(sorted_nodes, idx); + /* If the node has already been selected which is possible between + * replicas, the specification says to skip over. */ + if (!smartlist_contains(responsible_dirs, node->rs)) { + smartlist_add(responsible_dirs, node->rs); + ++n_added; + } + if (++idx == smartlist_len(sorted_nodes)) { + /* Wrap if we've reached the end of the list. */ + idx = 0; + } + if (idx == start) { + /* We've gone over the whole list, stop and avoid infinite loop. */ + break; + } + } + } + + done: + smartlist_free(sorted_nodes); +} + +/*********************** HSDir request tracking ***************************/ + +/** Return the period for which a hidden service directory cannot be queried + * for the same descriptor ID again, taking TestingTorNetwork into account. */ +time_t +hs_hsdir_requery_period(const or_options_t *options) +{ + tor_assert(options); + + if (options->TestingTorNetwork) { + return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; + } else { + return REND_HID_SERV_DIR_REQUERY_PERIOD; + } +} + +/** Tracks requests for fetching hidden service descriptors. It's used by + * hidden service clients, to avoid querying HSDirs that have already failed + * giving back a descriptor. The same data structure is used to track both v2 + * and v3 HS descriptor requests. + * + * The string map is a key/value store that contains the last request times to + * hidden service directories for certain queries. Specifically: + * + * key = base32(hsdir_identity) + base32(hs_identity) + * value = time_t of last request for that hs_identity to that HSDir + * + * where 'hsdir_identity' is the identity digest of the HSDir node, and + * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519 + * blinded public key of the HS in the v3 case. */ +static strmap_t *last_hid_serv_requests_ = NULL; + +/** Returns last_hid_serv_requests_, initializing it to a new strmap if + * necessary. */ +STATIC strmap_t * +get_last_hid_serv_requests(void) +{ + if (!last_hid_serv_requests_) + last_hid_serv_requests_ = strmap_new(); + return last_hid_serv_requests_; +} + +/** Look up the last request time to hidden service directory <b>hs_dir</b> + * for descriptor request key <b>req_key_str</b> which is the descriptor ID + * for a v2 service or the blinded key for v3. If <b>set</b> is non-zero, + * assign the current time <b>now</b> and return that. Otherwise, return the + * most recent request time, or 0 if no such request has been sent before. */ +time_t +hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *req_key_str, + time_t now, int set) +{ + char hsdir_id_base32[BASE32_DIGEST_LEN + 1]; + char *hsdir_desc_comb_id = NULL; + time_t *last_request_ptr; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + + /* Create the key */ + base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), + hs_dir->identity_digest, DIGEST_LEN); + tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str); + + if (set) { + time_t *oldptr; + last_request_ptr = tor_malloc_zero(sizeof(time_t)); + *last_request_ptr = now; + oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, + last_request_ptr); + tor_free(oldptr); + } else { + last_request_ptr = strmap_get(last_hid_serv_requests, + hsdir_desc_comb_id); + } + + tor_free(hsdir_desc_comb_id); + return (last_request_ptr) ? *last_request_ptr : 0; +} + +/** Clean the history of request times to hidden service directories, so that + * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD + * seconds any more. */ +void +hs_clean_last_hid_serv_requests(time_t now) +{ + strmap_iter_t *iter; + time_t cutoff = now - hs_hsdir_requery_period(get_options()); + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + time_t *ent; + strmap_iter_get(iter, &key, &val); + ent = (time_t *) val; + if (*ent < cutoff) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(ent); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Remove all requests related to the descriptor request key string + * <b>req_key_str</b> from the history of times of requests to 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. */ +void +hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str) +{ + strmap_iter_t *iter; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + strmap_iter_get(iter, &key, &val); + + /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of + * semantic, see #23305. */ + + /* This strmap contains variable-sized elements so this is a basic length + * check on the strings we are about to compare. The key is variable sized + * since it's composed as follows: + * key = base32(hsdir_identity) + base32(req_key_str) + * where 'req_key_str' is the descriptor ID of the HS in the v2 case, or + * the ed25519 blinded public key of the HS in the v3 case. */ + if (strlen(key) < REND_DESC_ID_V2_LEN_BASE32 + strlen(req_key_str)) { + iter = strmap_iter_next(last_hid_serv_requests, iter); + continue; + } + + /* Check if the tracked request matches our request key */ + if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str, + strlen(req_key_str))) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(val); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Purge the history of request times to hidden service directories, + * so that future lookups of an HS descriptor will not fail because we + * accessed all of the HSDir relays responsible for the descriptor + * recently. */ +void +hs_purge_last_hid_serv_requests(void) +{ + /* Don't create the table if it doesn't exist yet (and it may very + * well not exist if the user hasn't accessed any HSes)... */ + strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; + /* ... and let get_last_hid_serv_requests re-create it for us if + * necessary. */ + last_hid_serv_requests_ = NULL; + + if (old_last_hid_serv_requests != NULL) { + log_info(LD_REND, "Purging client last-HS-desc-request-time table"); + strmap_free(old_last_hid_serv_requests, tor_free_); + } +} + +/***********************************************************************/ + +/** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the + * one that we should use to fetch a descriptor right now. Take into account + * previous failed attempts at fetching this descriptor from HSDirs using the + * string identifier <b>req_key_str</b>. + * + * Steals ownership of <b>responsible_dirs</b>. + * + * Return the routerstatus of the chosen HSDir if successful, otherwise return + * NULL if no HSDirs are worth trying right now. */ +routerstatus_t * +hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) +{ + smartlist_t *usable_responsible_dirs = smartlist_new(); + const or_options_t *options = get_options(); + routerstatus_t *hs_dir; + time_t now = time(NULL); + int excluded_some; + + tor_assert(req_key_str); + + /* Clean outdated request history first. */ + hs_clean_last_hid_serv_requests(now); + + /* Only select those hidden service directories to which we did not send a + * request recently and for which we have a router descriptor here. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { + time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0); + const node_t *node = node_get_by_id(dir->identity_digest); + if (last + hs_hsdir_requery_period(options) >= now || + !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); + } + } SMARTLIST_FOREACH_END(dir); + + 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."); + } + } else { + /* Remember that we are requesting a descriptor from this hidden service + * directory now. */ + hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1); + } + + return hs_dir; +} + +/* From a list of link specifier, an onion key and if we are requesting a + * direct connection (ex: single onion service), return a newly allocated + * extend_info_t object. This function always returns an extend info with + * an IPv4 address, or NULL. + * + * It performs the following checks: + * if either IPv4 or legacy ID is missing, return NULL. + * if direct_conn, and we can't reach the IPv4 address, return NULL. + */ +extend_info_t * +hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + const curve25519_public_key_t *onion_key, + int direct_conn) +{ + int have_v4 = 0, have_legacy_id = 0, have_ed25519_id = 0; + char legacy_id[DIGEST_LEN] = {0}; + uint16_t port_v4 = 0; + tor_addr_t addr_v4; + ed25519_public_key_t ed25519_pk; + extend_info_t *info = NULL; + + tor_assert(lspecs); + + SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) { + switch (link_specifier_get_ls_type(ls)) { + case LS_IPV4: + /* Skip if we already seen a v4. */ + if (have_v4) continue; + tor_addr_from_ipv4h(&addr_v4, + link_specifier_get_un_ipv4_addr(ls)); + port_v4 = link_specifier_get_un_ipv4_port(ls); + have_v4 = 1; + break; + case LS_LEGACY_ID: + /* Make sure we do have enough bytes for the legacy ID. */ + if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) { + break; + } + memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls), + sizeof(legacy_id)); + have_legacy_id = 1; + break; + case LS_ED25519_ID: + memcpy(ed25519_pk.pubkey, + link_specifier_getconstarray_un_ed25519_id(ls), + ED25519_PUBKEY_LEN); + have_ed25519_id = 1; + break; + default: + /* Ignore unknown. */ + break; + } + } SMARTLIST_FOREACH_END(ls); + + /* Legacy ID is mandatory, and we require IPv4. */ + if (!have_v4 || !have_legacy_id) { + goto done; + } + + /* We know we have IPv4, because we just checked. */ + if (!direct_conn) { + /* All clients can extend to any IPv4 via a 3-hop path. */ + goto validate; + } else if (direct_conn && + fascist_firewall_allows_address_addr(&addr_v4, port_v4, + FIREWALL_OR_CONNECTION, + 0, 0)) { + /* Direct connection and we can reach it in IPv4 so go for it. */ + goto validate; + + /* We will add support for falling back to a 3-hop path in a later + * release. */ + } else { + /* If we can't reach IPv4, return NULL. */ + goto done; + } + + /* We will add support for IPv6 in a later release. */ + + validate: + /* We'll validate now that the address we've picked isn't a private one. If + * it is, are we allowing to extend to private address? */ + if (!extend_info_addr_is_allowed(&addr_v4)) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Requested address is private and we are not allowed to extend to " + "it: %s:%u", fmt_addr(&addr_v4), port_v4); + goto done; + } + + /* We do have everything for which we think we can connect successfully. */ + info = extend_info_new(NULL, legacy_id, + (have_ed25519_id) ? &ed25519_pk : NULL, NULL, + onion_key, &addr_v4, port_v4); + done: + return info; +} + +/***********************************************************************/ + +/* Initialize the entire HS subsytem. This is called in tor_init() before any + * torrc options are loaded. Only for >= v3. */ +void +hs_init(void) +{ + hs_circuitmap_init(); + hs_service_init(); + hs_cache_init(); +} + +/* Release and cleanup all memory of the HS subsystem (all version). This is + * called by tor_free_all(). */ +void +hs_free_all(void) +{ + hs_circuitmap_free_all(); + hs_service_free_all(); + hs_cache_free_all(); + hs_client_free_all(); +} + +/* For the given origin circuit circ, decrement the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_dec_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams--; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams--; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + +/* For the given origin circuit circ, increment the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_inc_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams++; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams++; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h index a8fded652a..b81c03d508 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -11,15 +11,21 @@ #include "or.h" +/* Trunnel */ +#include "ed25519_cert.h" + /* Protocol version 2. Use this instead of hardcoding "2" in the code base, * this adds a clearer semantic to the value when used. */ #define HS_VERSION_TWO 2 /* Version 3 of the protocol (prop224). */ #define HS_VERSION_THREE 3 +/* Earliest and latest version we support. */ +#define HS_VERSION_MIN HS_VERSION_TWO +#define HS_VERSION_MAX HS_VERSION_THREE /** Try to maintain this many intro points per service by default. */ #define NUM_INTRO_POINTS_DEFAULT 3 -/** Maximum number of intro points per service. */ +/** Maximum number of intro points per generic and version 2 service. */ #define NUM_INTRO_POINTS_MAX 10 /** Number of extra intro points we launch if our set of intro nodes is empty. * See proposal 155, section 4. */ @@ -46,12 +52,136 @@ #define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ /* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ + +/* Prefix of the onion address checksum. */ +#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum" +/* Length of the checksum prefix minus the NUL terminated byte. */ +#define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \ + (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1) +/* Length of the resulting checksum of the address. The construction of this + * checksum looks like: + * CHECKSUM = ".onion checksum" || PUBKEY || VERSION + * where VERSION is 1 byte. This is pre-hashing. */ +#define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \ + (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t)) +/* The amount of bytes we use from the address checksum. */ +#define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2 +/* Length of the binary encoded service address which is of course before the + * base32 encoding. Construction is: + * PUBKEY || CHECKSUM || VERSION + * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */ +#define HS_SERVICE_ADDR_LEN \ + (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t)) +/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the + * length ends up to 56 bytes (not counting the terminated NUL byte.) */ +#define HS_SERVICE_ADDR_LEN_BASE32 \ + (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5)) + +/* The default HS time period length */ +#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ /* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ +/* Keyblinding parameter construction is as follow: + * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */ +#define HS_KEYBLIND_NONCE_PREFIX "key-blind" +#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1) +#define HS_KEYBLIND_NONCE_LEN \ + (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t)) + +/* Credential and subcredential prefix value. */ +#define HS_CREDENTIAL_PREFIX "credential" +#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1) +#define HS_SUBCREDENTIAL_PREFIX "subcredential" +#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1) + +/* Node hidden service stored at index prefix value. */ +#define HS_INDEX_PREFIX "store-at-idx" +#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1) + +/* Node hidden service directory index prefix value. */ +#define HSDIR_INDEX_PREFIX "node-idx" +#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1) + +/* Prefix of the shared random value disaster mode. */ +#define HS_SRV_DISASTER_PREFIX "shared-random-disaster" +#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1) + +/* Default value of number of hsdir replicas (hsdir_n_replicas). */ +#define HS_DEFAULT_HSDIR_N_REPLICAS 2 +/* Default value of hsdir spread store (hsdir_spread_store). */ +#define HS_DEFAULT_HSDIR_SPREAD_STORE 4 +/* Default value of hsdir spread fetch (hsdir_spread_fetch). */ +#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3 + +/* The size of a legacy RENDEZVOUS1 cell which adds up to 168 bytes. It is + * bigger than the 84 bytes needed for version 3 so we need to pad up to that + * length so it is indistinguishable between versions. */ +#define HS_LEGACY_RENDEZVOUS_CELL_SIZE \ + (REND_COOKIE_LEN + DH_KEY_LEN + DIGEST_LEN) + +/* Type of authentication key used by an introduction point. */ +typedef enum { + HS_AUTH_KEY_TYPE_LEGACY = 1, + HS_AUTH_KEY_TYPE_ED25519 = 2, +} hs_auth_key_type_t; + +/* Represents the mapping from a virtual port of a rendezvous service to a + * real port on some IP. */ +typedef struct rend_service_port_config_t { + /* 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; + +/* Hidden service directory index used in a node_t which is set once we set + * the consensus. */ +typedef struct hsdir_index_t { + /* HSDir index to use when fetching a descriptor. */ + uint8_t fetch[DIGEST256_LEN]; + + /* HSDir index used by services to store their first and second + * descriptor. The first descriptor is chronologically older than the second + * one and uses older TP and SRV values. */ + uint8_t store_first[DIGEST256_LEN]; + uint8_t store_second[DIGEST256_LEN]; +} hsdir_index_t; + +void hs_init(void); +void hs_free_all(void); + +void hs_cleanup_circ(circuit_t *circ); + int hs_check_service_private_dir(const char *username, const char *path, unsigned int dir_group_readable, unsigned int create); +char *hs_path_from_filename(const char *directory, const char *filename); +void hs_build_address(const ed25519_public_key_t *key, uint8_t version, + char *addr_out); +int hs_address_is_valid(const char *address); +int hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out); + +void hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *pubkey_out); +void hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *kp_out); +int hs_service_requires_uptime_circ(const smartlist_t *ports); void rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); @@ -69,18 +199,84 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data, const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out); +routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32); + +void hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out); + +uint64_t hs_get_previous_time_period_num(time_t now); +uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); +time_t hs_get_start_time_of_next_time_period(time_t now); + +link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); + +MOCK_DECL(int, hs_in_period_between_tp_and_srv, + (const networkstatus_t *consensus, time_t now)); + +uint8_t *hs_get_current_srv(uint64_t time_period_num, + const networkstatus_t *ns); +uint8_t *hs_get_previous_srv(uint64_t time_period_num, + const networkstatus_t *ns); + +void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv, uint64_t period_num, + uint8_t *hsdir_index_out); +void hs_build_hs_index(uint64_t replica, + const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out); + +int32_t hs_get_hsdir_n_replicas(void); +int32_t hs_get_hsdir_spread_fetch(void); +int32_t hs_get_hsdir_spread_store(void); + +void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, + int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs); +routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs, + const char *req_key_str); + +time_t hs_hsdir_requery_period(const or_options_t *options); +time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *desc_id_base32, + time_t now, int set); +void hs_clean_last_hid_serv_requests(time_t now); +void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id); +void hs_purge_last_hid_serv_requests(void); + +int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); + +void hs_inc_rdv_stream_counter(origin_circuit_t *circ); +void hs_dec_rdv_stream_counter(origin_circuit_t *circ); + +extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + const curve25519_public_key_t *onion_key, + int direct_conn); #ifdef HS_COMMON_PRIVATE +STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); + +/** The period for which a hidden service directory cannot be queried for + * the same descriptor ID again. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) +/** Test networks generate a new consensus every 5 or 10 seconds. + * So allow them to requery HSDirs much faster. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) + #ifdef TOR_UNIT_TESTS +STATIC strmap_t *get_last_hid_serv_requests(void); STATIC uint64_t get_time_period_length(void); -STATIC uint64_t get_time_period_num(time_t now); -#endif /* TOR_UNIT_TESTS */ +STATIC uint8_t *get_first_cached_disaster_srv(void); +STATIC uint8_t *get_second_cached_disaster_srv(void); + +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* HS_COMMON_PRIVATE */ +#endif /* defined(HS_COMMON_PRIVATE) */ -#endif /* TOR_HS_COMMON_H */ +#endif /* !defined(TOR_HS_COMMON_H) */ diff --git a/src/or/hs_config.c b/src/or/hs_config.c new file mode 100644 index 0000000000..fa5c1ab176 --- /dev/null +++ b/src/or/hs_config.c @@ -0,0 +1,590 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.c + * \brief Implement hidden service configuration subsystem. + * + * \details + * + * This file has basically one main entry point: hs_config_service_all(). It + * takes the torrc options and configure hidden service from it. In validate + * mode, nothing is added to the global service list or keys are not generated + * nor loaded. + * + * A service is configured in two steps. It is first created using the tor + * options and then put in a staging list. It will stay there until + * hs_service_load_all_keys() is called. That function is responsible to + * load/generate the keys for the service in the staging list and if + * successful, transfert the service to the main global service list where + * at that point it is ready to be used. + * + * Configuration functions are per-version and there is a main generic one for + * every option that is common to all version (config_generic_service). + **/ + +#define HS_CONFIG_PRIVATE + +#include "hs_common.h" +#include "hs_config.h" +#include "hs_service.h" +#include "rendservice.h" + +/* Using the given list of services, stage them into our global state. Every + * service version are handled. This function can remove entries in the given + * service_list. + * + * Staging a service means that we take all services in service_list and we + * put them in the staging list (global) which acts as a temporary list that + * is used by the service loading key process. In other words, staging a + * service puts it in a list to be considered when loading the keys and then + * moved to the main global list. */ +static void +stage_services(smartlist_t *service_list) +{ + tor_assert(service_list); + + /* This is v2 specific. Trigger service pruning which will make sure the + * just configured services end up in the main global list. It should only + * be done in non validation mode because v2 subsystem handles service + * object differently. */ + rend_service_prune_list(); + + /* Cleanup v2 service from the list, we don't need those object anymore + * because we validated them all against the others and we want to stage + * only >= v3 service. And remember, v2 has a different object type which is + * shadow copied from an hs_service_t type. */ + SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) { + if (s->config.version == HS_VERSION_TWO) { + SMARTLIST_DEL_CURRENT(service_list, s); + hs_service_free(s); + } + } SMARTLIST_FOREACH_END(s); + + /* This is >= v3 specific. Using the newly configured service list, stage + * them into our global state. Every object ownership is lost after. */ + hs_service_stage_services(service_list); +} + +/* Validate the given service against all service in the given list. If the + * service is ephemeral, this function ignores it. Services with the same + * directory path aren't allowed and will return an error. If a duplicate is + * found, 1 is returned else 0 if none found. */ +static int +service_is_duplicate_in_list(const smartlist_t *service_list, + const hs_service_t *service) +{ + int ret = 0; + + tor_assert(service_list); + tor_assert(service); + + /* Ephemeral service don't have a directory configured so no need to check + * for a service in the list having the same path. */ + if (service->config.is_ephemeral) { + goto end; + } + + /* XXX: Validate if we have any service that has the given service dir path. + * This has two problems: + * + * a) It's O(n^2), but the same comment from the bottom of + * rend_config_services() should apply. + * + * b) We only compare directory paths as strings, so we can't + * detect two distinct paths that specify the same directory + * (which can arise from symlinks, case-insensitivity, bind + * mounts, etc.). + * + * It also can't detect that two separate Tor instances are trying + * to use the same HiddenServiceDir; for that, we would need a + * lock file. But this is enough to detect a simple mistake that + * at least one person has actually made. */ + SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) { + if (!strcmp(s->config.directory_path, service->config.directory_path)) { + log_warn(LD_REND, "Another hidden service is already configured " + "for directory %s", + escaped(service->config.directory_path)); + ret = 1; + goto end; + } + } SMARTLIST_FOREACH_END(s); + + end: + return ret; +} + +/* Helper function: Given an configuration option name, its value, a minimum + * min and a maxium max, parse the value as a uint64_t. On success, ok is set + * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be + * ignored. This function logs both on error and success. */ +static uint64_t +helper_parse_uint64(const char *opt, const char *value, uint64_t min, + uint64_t max, int *ok) +{ + uint64_t ret = 0; + + tor_assert(opt); + tor_assert(value); + tor_assert(ok); + + *ok = 0; + ret = tor_parse_uint64(value, 10, min, max, ok, NULL); + if (!*ok) { + log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64 + ", not %s.", + opt, min, max, value); + goto err; + } + log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret); + err: + return ret; +} + +/* Return true iff the given options starting at line_ for a hidden service + * contains at least one invalid option. Each hidden service option don't + * apply to all versions so this function can find out. The line_ MUST start + * right after the HiddenServiceDir line of this service. + * + * This is mainly for usability so we can inform the user of any invalid + * option for the hidden service version instead of silently ignoring. */ +static int +config_has_invalid_options(const config_line_t *line_, + const hs_service_t *service) +{ + int ret = 0; + const char **optlist; + const config_line_t *line; + + tor_assert(service); + tor_assert(service->config.version <= HS_VERSION_MAX); + + /* List of options that a v3 service doesn't support thus must exclude from + * its configuration. */ + const char *opts_exclude_v3[] = { + "HiddenServiceAuthorizeClient", + NULL /* End marker. */ + }; + + /* Defining the size explicitly allows us to take advantage of the compiler + * which warns us if we ever bump the max version but forget to grow this + * array. The plus one is because we have a version 0 :). */ + struct { + const char **list; + } exclude_lists[HS_VERSION_MAX + 1] = { + { NULL }, /* v0. */ + { NULL }, /* v1. */ + { NULL }, /* v2 */ + { opts_exclude_v3 }, /* v3. */ + }; + + optlist = exclude_lists[service->config.version].list; + if (optlist == NULL) { + /* No exclude options to look at for this version. */ + goto end; + } + for (int i = 0; optlist[i]; i++) { + const char *opt = optlist[i]; + for (line = line_; line; line = line->next) { + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + goto end; + } + if (!strcasecmp(line->key, opt)) { + log_warn(LD_CONFIG, "Hidden service option %s is incompatible with " + "version %" PRIu32 " of service in %s", + opt, service->config.version, + service->config.directory_path); + ret = 1; + /* Continue the loop so we can find all possible options. */ + continue; + } + } + } + end: + return ret; +} + +/* Validate service configuration. This is used when loading the configuration + * and once we've setup a service object, it's config object is passed to this + * function for further validation. This does not validate service key + * material. Return 0 if valid else -1 if invalid. */ +static int +config_validate_service(const hs_service_config_t *config) +{ + tor_assert(config); + + /* Amount of ports validation. */ + if (!config->ports || smartlist_len(config->ports) == 0) { + log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.", + escaped(config->directory_path)); + goto invalid; + } + + /* Valid. */ + return 0; + invalid: + return -1; +} + +/* Configuration funcion for a version 3 service. The line_ must be pointing + * to the directive directly after a HiddenServiceDir. That way, when hitting + * the next HiddenServiceDir line or reaching the end of the list of lines, we + * know that we have to stop looking for more options. The given service + * object must be already allocated and passed through + * config_generic_service() prior to calling this function. + * + * Return 0 on success else a negative value. */ +static int +config_service_v3(const config_line_t *line_, + hs_service_config_t *config) +{ + int have_num_ip = 0; + const char *dup_opt_seen = NULL; + const config_line_t *line; + + tor_assert(config); + + for (line = line_; line; line = line->next) { + int ok = 0; + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + break; + } + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + config->num_intro_points = + (unsigned int) helper_parse_uint64(line->key, line->value, + NUM_INTRO_POINTS_DEFAULT, + HS_CONFIG_V3_MAX_INTRO_POINTS, + &ok); + if (!ok || have_num_ip) { + if (have_num_ip) + dup_opt_seen = line->key; + goto err; + } + have_num_ip = 1; + continue; + } + } + + /* We do not load the key material for the service at this stage. This is + * done later once tor can confirm that it is in a running state. */ + + /* We are about to return a fully configured service so do one last pass of + * validation at it. */ + if (config_validate_service(config) < 0) { + goto err; + } + + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given options in line_ and options. This is + * called for any service regardless of its version which means that all + * directives in this function are generic to any service version. This + * function will also check the validity of the service directory path. + * + * The line_ must be pointing to the directive directly after a + * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or + * reaching the end of the list of lines, we know that we have to stop looking + * for more options. + * + * Return 0 on success else -1. */ +static int +config_generic_service(const config_line_t *line_, + const or_options_t *options, + hs_service_t *service) +{ + int dir_seen = 0; + const config_line_t *line; + hs_service_config_t *config; + /* If this is set, we've seen a duplicate of this option. Keep the string + * so we can log the directive. */ + const char *dup_opt_seen = NULL; + /* These variables will tell us if we ever have duplicate. */ + int have_version = 0, have_allow_unknown_ports = 0; + int have_dir_group_read = 0, have_max_streams = 0; + int have_max_streams_close = 0; + + tor_assert(line_); + tor_assert(options); + tor_assert(service); + + /* Makes thing easier. */ + config = &service->config; + + /* The first line starts with HiddenServiceDir so we consider what's next is + * the configuration of the service. */ + for (line = line_; line ; line = line->next) { + int ok = 0; + + /* This indicate that we have a new service to configure. */ + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* This function only configures one service at a time so if we've + * already seen one, stop right now. */ + if (dir_seen) { + break; + } + /* Ok, we've seen one and we are about to configure it. */ + dir_seen = 1; + config->directory_path = tor_strdup(line->value); + log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...", + escaped(config->directory_path)); + continue; + } + if (BUG(!dir_seen)) { + goto err; + } + /* Version of the service. */ + if (!strcasecmp(line->key, "HiddenServiceVersion")) { + service->config.version = + (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN, + HS_VERSION_MAX, &ok); + if (!ok || have_version) { + if (have_version) + dup_opt_seen = line->key; + goto err; + } + have_version = 1; + continue; + } + /* Virtual port. */ + if (!strcasecmp(line->key, "HiddenServicePort")) { + char *err_msg = NULL; + /* XXX: Can we rename this? */ + rend_service_port_config_t *portcfg = + rend_service_parse_port_config(line->value, " ", &err_msg); + if (!portcfg) { + if (err_msg) { + log_warn(LD_CONFIG, "%s", err_msg); + } + tor_free(err_msg); + goto err; + } + tor_assert(!err_msg); + smartlist_add(config->ports, portcfg); + log_info(LD_CONFIG, "HiddenServicePort=%s for %s", + line->value, escaped(config->directory_path)); + continue; + } + /* Do we allow unknown ports. */ + if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { + config->allow_unknown_ports = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_allow_unknown_ports) { + if (have_allow_unknown_ports) + dup_opt_seen = line->key; + goto err; + } + have_allow_unknown_ports = 1; + continue; + } + /* Directory group readable. */ + if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) { + config->dir_group_readable = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_dir_group_read) { + if (have_dir_group_read) + dup_opt_seen = line->key; + goto err; + } + have_dir_group_read = 1; + continue; + } + /* Maximum streams per circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { + config->max_streams_per_rdv_circuit = + helper_parse_uint64(line->key, line->value, 0, + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok); + if (!ok || have_max_streams) { + if (have_max_streams) + dup_opt_seen = line->key; + goto err; + } + have_max_streams = 1; + continue; + } + /* Maximum amount of streams before we close the circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { + config->max_streams_close_circuit = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_max_streams_close) { + if (have_max_streams_close) + dup_opt_seen = line->key; + goto err; + } + have_max_streams_close = 1; + continue; + } + } + + /* Check if we are configured in non anonymous mode meaning every service + * becomes a single onion service. */ + if (rend_service_non_anonymous_mode_enabled(options)) { + config->is_single_onion = 1; + /* We will add support for IPv6-only v3 single onion services in a future + * Tor version. This won't catch "ReachableAddresses reject *4", but that + * option doesn't work anyway. */ + if (options->ClientUseIPv4 == 0 && config->version == HS_VERSION_THREE) { + log_warn(LD_CONFIG, "IPv6-only v3 single onion services are not " + "supported. Set HiddenServiceSingleHopMode 0 and " + "HiddenServiceNonAnonymousMode 0, or set ClientUseIPv4 1."); + goto err; + } + } + + /* Success */ + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given line and options. This function will + * call the corresponding configuration function for a specific service + * version and validate the service against the other ones. On success, add + * the service to the given list and return 0. On error, nothing is added to + * the list and a negative value is returned. */ +static int +config_service(const config_line_t *line, const or_options_t *options, + smartlist_t *service_list) +{ + int ret; + hs_service_t *service = NULL; + + tor_assert(line); + tor_assert(options); + tor_assert(service_list); + + /* We have a new hidden service. */ + service = hs_service_new(options); + /* We'll configure that service as a generic one and then pass it to a + * specific function according to the configured version number. */ + if (config_generic_service(line, options, service) < 0) { + goto err; + } + tor_assert(service->config.version <= HS_VERSION_MAX); + /* Before we configure the service on a per-version basis, we'll make + * sure that this set of options for a service are valid that is for + * instance an option only for v2 is not used for v3. */ + if (config_has_invalid_options(line->next, service)) { + goto err; + } + /* Check permission on service directory that was just parsed. And this must + * be done regardless of the service version. Do not ask for the directory + * to be created, this is done when the keys are loaded because we could be + * in validation mode right now. */ + if (hs_check_service_private_dir(options->User, + service->config.directory_path, + service->config.dir_group_readable, + 0) < 0) { + goto err; + } + /* Different functions are in charge of specific options for a version. We + * start just after the service directory line so once we hit another + * directory line, the function knows that it has to stop parsing. */ + switch (service->config.version) { + case HS_VERSION_TWO: + ret = rend_config_service(line->next, options, &service->config); + break; + case HS_VERSION_THREE: + ret = config_service_v3(line->next, &service->config); + break; + default: + /* We do validate before if we support the parsed version. */ + tor_assert_nonfatal_unreached(); + goto err; + } + if (ret < 0) { + goto err; + } + /* We'll check if this service can be kept depending on the others + * configured previously. */ + if (service_is_duplicate_in_list(service_list, service)) { + goto err; + } + /* Passes, add it to the given list. */ + smartlist_add(service_list, service); + return 0; + + err: + hs_service_free(service); + return -1; +} + +/* From a set of <b>options</b>, setup every hidden service found. Return 0 on + * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and + * return as normal, but don't actually change the configured services. */ +int +hs_config_service_all(const or_options_t *options, int validate_only) +{ + int dir_option_seen = 0, ret = -1; + const config_line_t *line; + smartlist_t *new_service_list = NULL; + + tor_assert(options); + + /* Newly configured service are put in that list which is then used for + * validation and staging for >= v3. */ + new_service_list = smartlist_new(); + + for (line = options->RendConfigLines; line; line = line->next) { + /* Ignore all directives that aren't the start of a service. */ + if (strcasecmp(line->key, "HiddenServiceDir")) { + if (!dir_option_seen) { + log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", + line->key); + goto err; + } + continue; + } + /* Flag that we've seen a directory directive and we'll use it to make + * sure that the torrc options ordering is actually valid. */ + dir_option_seen = 1; + + /* Try to configure this service now. On success, it will be added to the + * list and validated against the service in that same list. */ + if (config_service(line, options, new_service_list) < 0) { + goto err; + } + } + + /* In non validation mode, we'll stage those services we just successfully + * configured. Service ownership is transfered from the list to the global + * state. If any service is invalid, it will be removed from the list and + * freed. All versions are handled in that function. */ + if (!validate_only) { + stage_services(new_service_list); + } else { + /* We've just validated that we were able to build a clean working list of + * services. We don't need those objects anymore. */ + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, + hs_service_free(s)); + /* For the v2 subsystem, the configuration function adds the service + * object to the staging list and it is transferred in the main list + * through the prunning process. In validation mode, we thus have to purge + * the staging list so it's not kept in memory as valid service. */ + rend_service_free_staging_list(); + } + + /* Success. Note that the service list has no ownership of its content. */ + ret = 0; + goto end; + + err: + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s)); + + end: + smartlist_free(new_service_list); + /* Tor main should call the free all function on error. */ + return ret; +} + diff --git a/src/or/hs_config.h b/src/or/hs_config.h new file mode 100644 index 0000000000..6cd7aed460 --- /dev/null +++ b/src/or/hs_config.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.h + * \brief Header file containing configuration ABI/API for the HS subsytem. + **/ + +#ifndef TOR_HS_CONFIG_H +#define TOR_HS_CONFIG_H + +#include "or.h" + +/* Max value for HiddenServiceMaxStreams */ +#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535 +/* Maximum number of intro points per version 3 services. */ +#define HS_CONFIG_V3_MAX_INTRO_POINTS 20 + +/* API */ + +int hs_config_service_all(const or_options_t *options, int validate_only); + +#endif /* !defined(TOR_HS_CONFIG_H) */ + diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index fae527b2db..1708866944 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -55,13 +55,14 @@ /* For unit tests.*/ #define HS_DESCRIPTOR_PRIVATE -#include "hs_descriptor.h" - #include "or.h" #include "ed25519_cert.h" /* Trunnel interface. */ +#include "hs_descriptor.h" +#include "circuitbuild.h" #include "parsecommon.h" #include "rendcache.h" #include "hs_cache.h" +#include "hs_config.h" #include "torcert.h" /* tor_cert_encode_ed22519() */ /* Constant string value used for the descriptor format. */ @@ -77,6 +78,7 @@ #define str_intro_auth_required "intro-auth-required" #define str_single_onion "single-onion-service" #define str_intro_point "introduction-point" +#define str_ip_onion_key "onion-key" #define str_ip_auth_key "auth-key" #define str_ip_enc_key "enc-key" #define str_ip_enc_key_cert "enc-key-cert" @@ -135,6 +137,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = { /* Descriptor ruleset for the introduction points section. */ static token_rule_t hs_desc_intro_point_v3_token_table[] = { T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), + T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK), T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ), T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK), T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK), @@ -143,29 +146,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = { END_OF_TABLE }; -/* Free a descriptor intro point object. */ -STATIC void -desc_intro_point_free(hs_desc_intro_point_t *ip) -{ - if (!ip) { - return; - } - if (ip->link_specifiers) { - SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, - ls, tor_free(ls)); - smartlist_free(ip->link_specifiers); - } - tor_cert_free(ip->auth_key_cert); - tor_cert_free(ip->enc_key_cert); - if (ip->legacy.key) { - crypto_pk_free(ip->legacy.key); - } - if (ip->legacy.cert.encoded) { - tor_free(ip->legacy.cert.encoded); - } - tor_free(ip); -} - /* Free the content of the plaintext section of a descriptor. */ STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) @@ -196,7 +176,7 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) } if (desc->intro_points) { SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, - desc_intro_point_free(ip)); + hs_desc_intro_point_free(ip)); smartlist_free(desc->intro_points); } memwipe(desc, 0, sizeof(*desc)); @@ -255,7 +235,7 @@ build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); offset += sizeof(desc->subcredential); /* Copy revision counter value. */ - set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter)); + set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter)); offset += sizeof(uint64_t); tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); } @@ -351,42 +331,10 @@ encode_link_specifiers(const smartlist_t *specs) SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *, spec) { - link_specifier_t *ls = link_specifier_new(); - link_specifier_set_ls_type(ls, spec->type); - - switch (spec->type) { - case LS_IPV4: - link_specifier_set_un_ipv4_addr(ls, - tor_addr_to_ipv4h(&spec->u.ap.addr)); - link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); - /* Four bytes IPv4 and two bytes port. */ - link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + - sizeof(spec->u.ap.port)); - break; - case LS_IPV6: - { - size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); - const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); - uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); - memcpy(ipv6_array, in6_addr, addr_len); - link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); - /* Sixteen bytes IPv6 and two bytes port. */ - link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); - break; - } - case LS_LEGACY_ID: - { - size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); - uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); - memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); - link_specifier_set_ls_len(ls, legacy_id_len); - break; + link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec); + if (ls) { + link_specifier_list_add_spec(lslist, ls); } - default: - tor_assert(0); - } - - link_specifier_list_add_spec(lslist, ls); } SMARTLIST_FOREACH_END(spec); { @@ -478,6 +426,26 @@ encode_enc_key(const hs_desc_intro_point_t *ip) return encoded; } +/* Encode an introduction point onion key. Return a newly allocated string + * with it. On failure, return NULL. */ +static char * +encode_onion_key(const hs_desc_intro_point_t *ip) +{ + char *encoded = NULL; + char key_b64[CURVE25519_BASE64_PADDED_LEN + 1]; + + tor_assert(ip); + + /* Base64 encode the encryption key for the "onion-key" field. */ + if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) { + goto done; + } + tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64); + + done: + return encoded; +} + /* Encode an introduction point object and return a newly allocated string * with it. On failure, return NULL. */ static char * @@ -497,6 +465,16 @@ encode_intro_point(const ed25519_public_key_t *sig_key, tor_free(ls_str); } + /* Onion key encoding. */ + { + char *encoded_onion_key = encode_onion_key(ip); + if (encoded_onion_key == NULL) { + goto err; + } + smartlist_add_asprintf(lines, "%s", encoded_onion_key); + tor_free(encoded_onion_key); + } + /* Authentication key encoding. */ { char *encoded_cert; @@ -987,6 +965,10 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_assert(encoded_out); tor_assert(desc->plaintext_data.version == 3); + if (BUG(desc->subcredential == NULL)) { + goto err; + } + /* Build the non-encrypted values. */ { char *encoded_cert; @@ -1133,6 +1115,15 @@ decode_link_specifiers(const char *encoded) memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls), sizeof(hs_spec->u.legacy_id)); break; + case LS_ED25519_ID: + /* Both are known at compile time so let's make sure they are the same + * else we can copy memory out of bound. */ + tor_assert(link_specifier_getlen_un_ed25519_id(ls) == + sizeof(hs_spec->u.ed25519_id)); + memcpy(hs_spec->u.ed25519_id, + link_specifier_getconstarray_un_ed25519_id(ls), + sizeof(hs_spec->u.ed25519_id)); + break; default: goto err; } @@ -1311,13 +1302,17 @@ encrypted_data_length_is_valid(size_t len) * <b>encrypted_blob_size</b>. Use the descriptor object <b>desc</b> to * generate the right decryption keys; set <b>decrypted_out</b> to the * plaintext. If <b>is_superencrypted_layer</b> is set, this is the outter - * encrypted layer of the descriptor. */ -static size_t -decrypt_desc_layer(const hs_descriptor_t *desc, - const uint8_t *encrypted_blob, - size_t encrypted_blob_size, - int is_superencrypted_layer, - char **decrypted_out) + * encrypted layer of the descriptor. + * + * On any error case, including an empty output, return 0 and set + * *<b>decrypted_out</b> to NULL. + */ +MOCK_IMPL(STATIC size_t, +decrypt_desc_layer,(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out)) { uint8_t *decrypted = NULL; uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN]; @@ -1391,6 +1386,11 @@ decrypt_desc_layer(const hs_descriptor_t *desc, } } + if (result_len == 0) { + /* Treat this as an error, so that somebody will free the output. */ + goto err; + } + /* Make sure to NUL terminate the string. */ decrypted[encrypted_len] = '\0'; *decrypted_out = (char *) decrypted; @@ -1625,6 +1625,50 @@ decode_intro_legacy_key(const directory_token_t *tok, return -1; } +/* Dig into the descriptor <b>tokens</b> to find the onion key we should use + * for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it + * was found and well-formed, otherwise return -1 in case of errors. */ +static int +set_intro_point_onion_key(curve25519_public_key_t *onion_key_out, + const smartlist_t *tokens) +{ + int retval = -1; + smartlist_t *onion_keys = NULL; + + tor_assert(onion_key_out); + + onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY); + if (!onion_keys) { + log_warn(LD_REND, "Descriptor did not contain intro onion keys"); + goto err; + } + + SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) { + /* This field is using GE(2) so for possible forward compatibility, we + * accept more fields but must be at least 2. */ + tor_assert(tok->n_args >= 2); + + /* Try to find an ntor key, it's the only recognized type right now */ + if (!strcmp(tok->args[0], "ntor")) { + if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor onion-key is invalid"); + goto err; + } + /* Got the onion key! Set the appropriate retval */ + retval = 0; + } + } SMARTLIST_FOREACH_END(tok); + + /* Log an error if we didn't find it :( */ + if (retval < 0) { + log_warn(LD_REND, "Descriptor did not contain ntor onion keys"); + } + + err: + smartlist_free(onion_keys); + return retval; +} + /* Given the start of a section and the end of it, decode a single * introduction point from that section. Return a newly allocated introduction * point object containing the decoded data. Return NULL if the section can't @@ -1650,17 +1694,24 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Ok we seem to have a well formed section containing enough tokens to * parse. Allocate our IP object and try to populate it. */ - ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t)); + ip = hs_desc_intro_point_new(); /* "introduction-point" SP link-specifiers NL */ tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT); tor_assert(tok->n_args == 1); + /* Our constructor creates this list by default so free it. */ + smartlist_free(ip->link_specifiers); ip->link_specifiers = decode_link_specifiers(tok->args[0]); if (!ip->link_specifiers) { log_warn(LD_REND, "Introduction point has invalid link specifiers"); goto err; } + /* "onion-key" SP ntor SP key NL */ + if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) { + goto err; + } + /* "auth-key" NL certificate NL */ tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY); tor_assert(tok->object_body); @@ -1732,7 +1783,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) goto done; err: - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); ip = NULL; done: @@ -1747,18 +1798,13 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Given a descriptor string at <b>data</b>, decode all possible introduction * points that we can find. Add the introduction point object to desc_enc as we - * find them. Return 0 on success. - * - * On error, a negative value is returned. It is possible that some intro - * point object have been added to the desc_enc, they should be considered - * invalid. One single bad encoded introduction point will make this function - * return an error. */ -STATIC int + * find them. This function can't fail and it is possible that zero + * introduction points can be decoded. */ +static void decode_intro_points(const hs_descriptor_t *desc, hs_desc_encrypted_data_t *desc_enc, const char *data) { - int retval = -1; smartlist_t *chunked_desc = smartlist_new(); smartlist_t *intro_points = smartlist_new(); @@ -1799,22 +1845,19 @@ decode_intro_points(const hs_descriptor_t *desc, SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) { hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point); if (!ip) { - /* Malformed introduction point section. Stop right away, this - * descriptor shouldn't be used. */ - goto err; + /* Malformed introduction point section. We'll ignore this introduction + * point and continue parsing. New or unknown fields are possible for + * forward compatibility. */ + continue; } smartlist_add(desc_enc->intro_points, ip); } SMARTLIST_FOREACH_END(intro_point); done: - retval = 0; - - err: SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a)); smartlist_free(chunked_desc); SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a)); smartlist_free(intro_points); - return retval; } /* Return 1 iff the given base64 encoded signature in b64_sig from the encoded * descriptor in encoded_desc validates the descriptor content. */ @@ -2041,14 +2084,14 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, /* Initialize the descriptor's introduction point list before we start * decoding. Having 0 intro point is valid. Then decode them all. */ desc_encrypted_out->intro_points = smartlist_new(); - if (decode_intro_points(desc, desc_encrypted_out, message) < 0) { - goto err; - } + decode_intro_points(desc, desc_encrypted_out, message); + /* Validation of maximum introduction points allowed. */ - if (smartlist_len(desc_encrypted_out->intro_points) > MAX_INTRO_POINTS) { + if (smartlist_len(desc_encrypted_out->intro_points) > + HS_CONFIG_V3_MAX_INTRO_POINTS) { log_warn(LD_REND, "Service descriptor contains too many introduction " "points. Maximum allowed is %d but we have %d", - MAX_INTRO_POINTS, + HS_CONFIG_V3_MAX_INTRO_POINTS, smartlist_len(desc_encrypted_out->intro_points)); goto err; } @@ -2223,7 +2266,7 @@ hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, hs_descriptor_t **desc_out) { - int ret; + int ret = -1; hs_descriptor_t *desc; tor_assert(encoded); @@ -2231,10 +2274,13 @@ hs_desc_decode_descriptor(const char *encoded, desc = tor_malloc_zero(sizeof(hs_descriptor_t)); /* Subcredentials are optional. */ - if (subcredential) { - memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + if (BUG(!subcredential)) { + log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!"); + goto err; } + memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data); if (ret < 0) { goto err; @@ -2280,10 +2326,10 @@ static int * * Return 0 on success and encoded_out is a valid pointer. On error, -1 is * returned and encoded_out is set to NULL. */ -int -hs_desc_encode_descriptor(const hs_descriptor_t *desc, - const ed25519_keypair_t *signing_kp, - char **encoded_out) +MOCK_IMPL(int, +hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)) { int ret = -1; uint32_t version; @@ -2360,3 +2406,197 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) data->superencrypted_blob_size); } +/* Return the size in bytes of the given encrypted data object. Used by OOM + * subsystem. */ +static size_t +hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data) +{ + tor_assert(data); + size_t intro_size = 0; + if (data->intro_auth_types) { + intro_size += + smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types); + } + if (data->intro_points) { + /* XXX could follow pointers here and get more accurate size */ + intro_size += + smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t); + } + + return sizeof(*data) + intro_size; +} + +/* Return the size in bytes of the given descriptor object. Used by OOM + * subsystem. */ + size_t +hs_desc_obj_size(const hs_descriptor_t *data) +{ + tor_assert(data); + return (hs_desc_plaintext_obj_size(&data->plaintext_data) + + hs_desc_encrypted_obj_size(&data->encrypted_data) + + sizeof(data->subcredential)); +} + +/* Return a newly allocated descriptor intro point. */ +hs_desc_intro_point_t * +hs_desc_intro_point_new(void) +{ + hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); + ip->link_specifiers = smartlist_new(); + return ip; +} + +/* Free a descriptor intro point object. */ +void +hs_desc_intro_point_free(hs_desc_intro_point_t *ip) +{ + if (ip == NULL) { + return; + } + if (ip->link_specifiers) { + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, + ls, hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); + } + tor_cert_free(ip->auth_key_cert); + tor_cert_free(ip->enc_key_cert); + crypto_pk_free(ip->legacy.key); + tor_free(ip->legacy.cert.encoded); + tor_free(ip); +} + +/* Free the given descriptor link specifier. */ +void +hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls) +{ + if (ls == NULL) { + return; + } + tor_free(ls); +} + +/* Return a newly allocated descriptor link specifier using the given extend + * info and requested type. Return NULL on error. */ +hs_desc_link_specifier_t * +hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) +{ + hs_desc_link_specifier_t *ls = NULL; + + tor_assert(info); + + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = type; + switch (ls->type) { + case LS_IPV4: + if (info->addr.family != AF_INET) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_IPV6: + if (info->addr.family != AF_INET6) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_LEGACY_ID: + /* Bug out if the identity digest is not set */ + if (BUG(tor_mem_is_zero(info->identity_digest, + sizeof(info->identity_digest)))) { + goto err; + } + memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); + break; + case LS_ED25519_ID: + /* ed25519 keys are optional for intro points */ + if (ed25519_public_key_is_zero(&info->ed_identity)) { + goto err; + } + memcpy(ls->u.ed25519_id, info->ed_identity.pubkey, + sizeof(ls->u.ed25519_id)); + break; + default: + /* Unknown type is code flow error. */ + tor_assert(0); + } + + return ls; + err: + tor_free(ls); + return NULL; +} + +/* From the given descriptor, remove and free every introduction point. */ +void +hs_descriptor_clear_intro_points(hs_descriptor_t *desc) +{ + smartlist_t *ips; + + tor_assert(desc); + + ips = desc->encrypted_data.intro_points; + if (ips) { + SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *, + ip, hs_desc_intro_point_free(ip)); + smartlist_clear(ips); + } +} + +/* From a descriptor link specifier object spec, returned a newly allocated + * link specifier object that is the encoded representation of spec. Return + * NULL on error. */ +link_specifier_t * +hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec) +{ + tor_assert(spec); + + link_specifier_t *ls = link_specifier_new(); + link_specifier_set_ls_type(ls, spec->type); + + switch (spec->type) { + case LS_IPV4: + link_specifier_set_un_ipv4_addr(ls, + tor_addr_to_ipv4h(&spec->u.ap.addr)); + link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + + sizeof(spec->u.ap.port)); + break; + case LS_IPV6: + { + size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); + const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); + uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); + memcpy(ipv6_array, in6_addr, addr_len); + link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); + /* Sixteen bytes IPv6 and two bytes port. */ + link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); + break; + } + case LS_LEGACY_ID: + { + size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); + uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); + memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); + link_specifier_set_ls_len(ls, legacy_id_len); + break; + } + case LS_ED25519_ID: + { + size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls); + uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls); + memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len); + link_specifier_set_ls_len(ls, ed25519_id_len); + break; + } + default: + tor_assert_nonfatal_unreached(); + link_specifier_free(ls); + ls = NULL; + } + + return ls; +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 136477ae3a..52bec8e244 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -18,17 +18,26 @@ #include "crypto_ed25519.h" #include "torcert.h" +/* Trunnel */ +struct link_specifier_t; + /* The earliest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3 /* The latest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3 +/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours + * which is 180 minutes or 10800 seconds. */ +#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60) /* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours * which is 720 minutes or 43200 seconds. */ #define HS_DESC_MAX_LIFETIME (12 * 60 * 60) /* Lifetime of certificate in the descriptor. This defines the lifetime of the - * descriptor signing key and the cross certification cert of that key. */ -#define HS_DESC_CERT_LIFETIME (24 * 60 * 60) + * descriptor signing key and the cross certification cert of that key. It is + * set to 54 hours because a descriptor can be around for 48 hours and because + * consensuses are used after the hour, add an extra 6 hours to give some time + * for the service to stop using it. */ +#define HS_DESC_CERT_LIFETIME (54 * 60 * 60) /* Length of the salt needed for the encrypted section of a descriptor. */ #define HS_DESC_ENCRYPTED_SALT_LEN 16 /* Length of the secret input needed for the KDF construction which derives @@ -65,12 +74,14 @@ typedef struct hs_desc_link_specifier_t { * specification. */ uint8_t type; - /* It's either an address/port or a legacy identity fingerprint. */ + /* It must be one of these types, can't be more than one. */ union { /* IP address and port of the relay use to extend. */ tor_addr_port_t ap; /* Legacy identity. A 20-byte SHA1 identity fingerprint. */ uint8_t legacy_id[DIGEST_LEN]; + /* ed25519 identity. A 32-byte key. */ + uint8_t ed25519_id[ED25519_PUBKEY_LEN]; } u; } hs_desc_link_specifier_t; @@ -80,6 +91,10 @@ typedef struct hs_desc_intro_point_t { * contains hs_desc_link_specifier_t object. It MUST have at least one. */ smartlist_t *link_specifiers; + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + /* Authentication key used to establish the introduction point circuit and * cross-certifies the blinded public key for the replica thus signed by * the blinded key and in turn signs it. */ @@ -197,9 +212,15 @@ void hs_descriptor_free(hs_descriptor_t *desc); void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc); void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc); -int hs_desc_encode_descriptor(const hs_descriptor_t *desc, - const ed25519_keypair_t *signing_kp, - char **encoded_out); +void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls); +hs_desc_link_specifier_t *hs_desc_link_specifier_new( + const extend_info_t *info, uint8_t type); +void hs_descriptor_clear_intro_points(hs_descriptor_t *desc); + +MOCK_DECL(int, + hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)); int hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, @@ -209,8 +230,15 @@ int hs_desc_decode_plaintext(const char *encoded, int hs_desc_decode_encrypted(const hs_descriptor_t *desc, hs_desc_encrypted_data_t *desc_out); +size_t hs_desc_obj_size(const hs_descriptor_t *data); size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data); +hs_desc_intro_point_t *hs_desc_intro_point_new(void); +void hs_desc_intro_point_free(hs_desc_intro_point_t *ip); + +link_specifier_t *hs_desc_lspec_to_trunnel( + const hs_desc_link_specifier_t *spec); + #ifdef HS_DESCRIPTOR_PRIVATE /* Encoding. */ @@ -223,21 +251,23 @@ STATIC smartlist_t *decode_link_specifiers(const char *encoded); STATIC hs_desc_intro_point_t *decode_introduction_point( const hs_descriptor_t *desc, const char *text); -STATIC int decode_intro_points(const hs_descriptor_t *desc, - hs_desc_encrypted_data_t *desc_enc, - const char *data); STATIC int encrypted_data_length_is_valid(size_t len); STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type); STATIC int desc_sig_is_valid(const char *b64_sig, const ed25519_public_key_t *signing_pubkey, const char *encoded_desc, size_t encoded_len); -STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip); STATIC size_t decode_superencrypted(const char *message, size_t message_len, uint8_t **encrypted_out); STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc); -#endif /* HS_DESCRIPTOR_PRIVATE */ +MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out)); + +#endif /* defined(HS_DESCRIPTOR_PRIVATE) */ -#endif /* TOR_HS_DESCRIPTOR_H */ +#endif /* !defined(TOR_HS_DESCRIPTOR_H) */ diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c new file mode 100644 index 0000000000..b0e4e36a9b --- /dev/null +++ b/src/or/hs_ident.c @@ -0,0 +1,126 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.c + * \brief Contains circuit and connection identifier code for the whole HS + * subsytem. + **/ + +#include "hs_ident.h" + +/* Return a newly allocated circuit identifier. The given public key is copied + * identity_pk into the identifier. */ +hs_ident_circuit_t * +hs_ident_circuit_new(const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type) +{ + tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO || + circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS); + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + ident->circuit_type = circuit_type; + return ident; +} + +/* Free the given circuit identifier. */ +void +hs_ident_circuit_free(hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_circuit_t)); + tor_free(ident); +} + +/* For a given circuit identifier src, return a newly allocated copy of it. + * This can't fail. */ +hs_ident_circuit_t * +hs_ident_circuit_dup(const hs_ident_circuit_t *src) +{ + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + +/* For a given directory connection identifier src, return a newly allocated + * copy of it. This can't fail. */ +hs_ident_dir_conn_t * +hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src) +{ + hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + +/* Free the given directory connection identifier. */ +void +hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_dir_conn_t)); + tor_free(ident); +} + +/* Initialized the allocated ident object with identity_pk and blinded_pk. + * None of them can be NULL since a valid directory connection identifier must + * have all fields set. */ +void +hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + hs_ident_dir_conn_t *ident) +{ + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(ident); + + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + ed25519_pubkey_copy(&ident->blinded_pk, blinded_pk); +} + +/* Return a newly allocated edge connection identifier. The given public key + * identity_pk is copied into the identifier. */ +hs_ident_edge_conn_t * +hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk) +{ + hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + return ident; +} + +/* Free the given edge connection identifier. */ +void +hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_edge_conn_t)); + tor_free(ident); +} + +/* Return true if the given ident is valid for an introduction circuit. */ +int +hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->identity_pk)) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) { + goto invalid; + } + + /* Valid. */ + return 1; + invalid: + return 0; +} + diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h new file mode 100644 index 0000000000..03150d25ea --- /dev/null +++ b/src/or/hs_ident.h @@ -0,0 +1,141 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.h + * \brief Header file containing circuit and connection identifier data for + * the whole HS subsytem. + * + * \details + * This interface is used to uniquely identify a hidden service on a circuit + * or connection using the service identity public key. Once the circuit or + * connection subsystem calls in the hidden service one, we use those + * identifiers to lookup the corresponding objects like service, intro point + * and descriptor. + * + * Furthermore, the circuit identifier holds cryptographic material needed for + * the e2e encryption on the rendezvous circuit which is set once the + * rendezvous circuit has opened and ready to be used. + **/ + +#ifndef TOR_HS_IDENT_H +#define TOR_HS_IDENT_H + +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_common.h" + +/* Length of the rendezvous cookie that is used to connect circuits at the + * rendezvous point. */ +#define HS_REND_COOKIE_LEN DIGEST_LEN + +/* Type of circuit an hs_ident_t object is associated with. */ +typedef enum { + HS_IDENT_CIRCUIT_INTRO = 1, + HS_IDENT_CIRCUIT_RENDEZVOUS = 2, +} hs_ident_circuit_type_t; + +/* Client and service side circuit identifier that is used for hidden service + * circuit establishment. Not all fields contain data, it depends on the + * circuit purpose. This is attached to an origin_circuit_t. All fields are + * used by both client and service. */ +typedef struct hs_ident_circuit_t { + /* (All circuit) The public key used to uniquely identify the service. It is + * the one found in the onion address. */ + ed25519_public_key_t identity_pk; + + /* (All circuit) The type of circuit this identifier is attached to. + * Accessors of the fields in this object assert non fatal on this circuit + * type. In other words, if a rendezvous field is being accessed, the + * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is + * set when an object is initialized in its constructor. */ + hs_ident_circuit_type_t circuit_type; + + /* (All circuit) Introduction point authentication key. It's also needed on + * the rendezvous circuit for the ntor handshake. It's used as the unique key + * of the introduction point so it should not be shared between multiple + * intro points. */ + ed25519_public_key_t intro_auth_pk; + + /* (Only client rendezvous circuit) Introduction point encryption public + * key. We keep it in the rendezvous identifier for the ntor handshake. */ + curve25519_public_key_t intro_enc_pk; + + /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the + * service with an INTRODUCE1 cell and used by the service in an + * RENDEZVOUS1 cell. */ + uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; + + /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the + * RENDEZVOUS1 cell of the service. The construction is as follows: + * SERVER_PK [32 bytes] + * AUTH_MAC [32 bytes] + */ + uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + /* (Only client rendezvous circuit) Client ephemeral keypair needed for the + * e2e encryption with the service. */ + curve25519_keypair_t rendezvous_client_kp; + + /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for + * the e2e encryption with the client on the circuit. */ + uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN]; + + /* (Only rendezvous circuit) Number of streams associated with this + * rendezvous circuit. We track this because there is a check on a maximum + * value. */ + uint64_t num_rdv_streams; +} hs_ident_circuit_t; + +/* Client and service side directory connection identifier used for a + * directory connection to identify which service is being queried. This is + * attached to a dir_connection_t. */ +typedef struct hs_ident_dir_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* The blinded public key used to uniquely identify the descriptor that this + * directory connection identifier is for. Only used by the service-side code + * to fine control descriptor uploads. */ + ed25519_public_key_t blinded_pk; + + /* XXX: Client authorization. */ +} hs_ident_dir_conn_t; + +/* Client and service side edge connection identifier used for an edge + * connection to identify which service is being queried. This is attached to + * a edge_connection_t. */ +typedef struct hs_ident_edge_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* XXX: Client authorization. */ +} hs_ident_edge_conn_t; + +/* Circuit identifier API. */ +hs_ident_circuit_t *hs_ident_circuit_new( + const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type); +void hs_ident_circuit_free(hs_ident_circuit_t *ident); +hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src); + +/* Directory connection identifier API. */ +hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src); +void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident); +void hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + hs_ident_dir_conn_t *ident); + +/* Edge connection identifier API. */ +hs_ident_edge_conn_t *hs_ident_edge_conn_new( + const ed25519_public_key_t *identity_pk); +void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident); + +/* Validators */ +int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident); + +#endif /* !defined(TOR_HS_IDENT_H) */ + diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c index 06f8a2c3ad..4a5202f614 100644 --- a/src/or/hs_intropoint.c +++ b/src/or/hs_intropoint.c @@ -24,6 +24,7 @@ #include "hs/cell_introduce1.h" #include "hs_circuitmap.h" +#include "hs_descriptor.h" #include "hs_intropoint.h" #include "hs_common.h" @@ -595,3 +596,18 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request, return -1; } +/* Clear memory allocated by the given intropoint object ip (but don't free the + * object itself). */ +void +hs_intropoint_clear(hs_intropoint_t *ip) +{ + if (ip == NULL) { + return; + } + tor_cert_free(ip->auth_key_cert); + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls, + hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); + memset(ip, 0, sizeof(hs_intropoint_t)); +} + diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h index 163ed810e7..749d1530e1 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -9,12 +9,15 @@ #ifndef TOR_HS_INTRO_H #define TOR_HS_INTRO_H +#include "crypto_curve25519.h" +#include "torcert.h" + /* Authentication key type in an ESTABLISH_INTRO cell. */ -enum hs_intro_auth_key_type { +typedef enum { HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00, HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01, HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02, -}; +} hs_intro_auth_key_type_t; /* INTRODUCE_ACK status code. */ typedef enum { @@ -24,6 +27,18 @@ typedef enum { HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003, } hs_intro_ack_status_t; +/* Object containing introduction point common data between the service and + * the client side. */ +typedef struct hs_intropoint_t { + /* Does this intro point only supports legacy ID ?. */ + unsigned int is_only_legacy : 1; + + /* Authentication key certificate from the descriptor. */ + tor_cert_t *auth_key_cert; + /* A list of link specifier. */ + smartlist_t *link_specifiers; +} hs_intropoint_t; + int hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request, size_t request_len); @@ -35,6 +50,9 @@ MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ)); /* also used by rendservice.c */ int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ); +hs_intropoint_t *hs_intro_new(void); +void hs_intropoint_clear(hs_intropoint_t *ip); + #ifdef HS_INTROPOINT_PRIVATE #include "hs/cell_establish_intro.h" @@ -55,7 +73,7 @@ STATIC int handle_introduce1(or_circuit_t *client_circ, STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell); STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ); -#endif /* HS_INTROPOINT_PRIVATE */ +#endif /* defined(HS_INTROPOINT_PRIVATE) */ -#endif /* TOR_HS_INTRO_H */ +#endif /* !defined(TOR_HS_INTRO_H) */ diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c index 119899817e..a416bc46c3 100644 --- a/src/or/hs_ntor.c +++ b/src/or/hs_ntor.c @@ -578,49 +578,41 @@ hs_ntor_client_rendezvous2_mac_is_good( /* Input length to KDF for key expansion */ #define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN) -/* Output length of KDF for key expansion */ -#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2) - -/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the - * circuit key expansion as specified by section '4.2.1. Key expansion' and - * return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */ -hs_ntor_rend_circuit_keys_t * -hs_ntor_circuit_key_expansion( - const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys) + +/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size + * DIGEST256_LEN), do the circuit key expansion as specified by section + * '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be + * of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN). + * + * Return 0 if things went well, else return -1. */ +int +hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len, + uint8_t *keys_out, size_t keys_out_len) { uint8_t *ptr; uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN]; - uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN]; crypto_xof_t *xof; - hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL; + + /* Sanity checks on lengths to make sure we are good */ + if (BUG(seed_len != DIGEST256_LEN)) { + return -1; + } + if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) { + return -1; + } /* Let's build the input to the KDF */ ptr = kdf_input; - APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN); + APPEND(ptr, ntor_key_seed, DIGEST256_LEN); APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); tor_assert(ptr == kdf_input + sizeof(kdf_input)); /* Generate the keys */ xof = crypto_xof_new(); crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); - crypto_xof_squeeze_bytes(xof, keys, sizeof(keys)); + crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN); crypto_xof_free(xof); - /* Generate keys structure and assign keys to it */ - rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t)); - ptr = keys; - memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN;; - memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN; - memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN; - memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN); - ptr += CIPHER256_KEY_LEN; - memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN); - ptr += CIPHER256_KEY_LEN; - tor_assert(ptr == keys + sizeof(keys)); - - return rend_circuit_keys; + return 0; } diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h index cd75f46a4c..77e544a130 100644 --- a/src/or/hs_ntor.h +++ b/src/or/hs_ntor.h @@ -6,6 +6,10 @@ #include "or.h" +/* Output length of KDF for key expansion */ +#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \ + (DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2) + /* Key material needed to encode/decode INTRODUCE1 cells */ typedef struct { /* Key used for encryption of encrypted INTRODUCE1 blob */ @@ -23,21 +27,6 @@ typedef struct { uint8_t ntor_key_seed[DIGEST256_LEN]; } hs_ntor_rend_cell_keys_t; -/* Key material resulting from key expansion as detailed in section "4.2.1. Key - * expansion" of rend-spec-ng.txt. */ -typedef struct { - /* Per-circuit key material used in ESTABLISH_INTRO cell */ - uint8_t KH[DIGEST256_LEN]; - /* Authentication key for outgoing RELAY cells */ - uint8_t Df[DIGEST256_LEN]; - /* Authentication key for incoming RELAY cells */ - uint8_t Db[DIGEST256_LEN]; - /* Encryption key for outgoing RELAY cells */ - uint8_t Kf[CIPHER256_KEY_LEN]; - /* Decryption key for incoming RELAY cells */ - uint8_t Kb[CIPHER256_KEY_LEN]; -} hs_ntor_rend_circuit_keys_t; - int hs_ntor_client_get_introduce1_keys( const ed25519_public_key_t *intro_auth_pubkey, const curve25519_public_key_t *intro_enc_pubkey, @@ -66,12 +55,13 @@ int hs_ntor_service_get_rendezvous1_keys( const curve25519_public_key_t *client_ephemeral_enc_pubkey, hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out); -hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion( - const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys); +int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, + size_t seed_len, + uint8_t *keys_out, size_t keys_out_len); int hs_ntor_client_rendezvous2_mac_is_good( const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys, const uint8_t *rcvd_mac); -#endif +#endif /* !defined(TOR_HS_NTOR_H) */ diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 205ef11c92..dd836bf842 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -6,169 +6,3373 @@ * \brief Implement next generation hidden service functionality **/ +#define HS_SERVICE_PRIVATE + #include "or.h" +#include "circpathbias.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" +#include "connection.h" +#include "directory.h" +#include "main.h" +#include "networkstatus.h" +#include "nodelist.h" #include "relay.h" #include "rendservice.h" -#include "circuitlist.h" -#include "circpathbias.h" +#include "router.h" +#include "routerkeys.h" +#include "routerlist.h" +#include "shared_random_state.h" +#include "statefile.h" +#include "hs_circuit.h" +#include "hs_common.h" +#include "hs_config.h" +#include "hs_circuit.h" +#include "hs_descriptor.h" +#include "hs_ident.h" #include "hs_intropoint.h" #include "hs_service.h" -#include "hs_common.h" -#include "hs/cell_establish_intro.h" +/* Trunnel */ +#include "ed25519_cert.h" #include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* Helper macro. Iterate over every service in the global map. The var is the + * name of the service pointer. */ +#define FOR_EACH_SERVICE_BEGIN(var) \ + STMT_BEGIN \ + hs_service_t **var##_iter, *var; \ + HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \ + var = *var##_iter; +#define FOR_EACH_SERVICE_END } STMT_END ; + +/* Helper macro. Iterate over both current and previous descriptor of a + * service. The var is the name of the descriptor pointer. This macro skips + * any descriptor object of the service that is NULL. */ +#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \ + STMT_BEGIN \ + hs_service_descriptor_t *var; \ + for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \ + ++var ## _loop_idx) { \ + (var ## _loop_idx == 0) ? (var = service->desc_current) : \ + (var = service->desc_next); \ + if (var == NULL) continue; +#define FOR_EACH_DESCRIPTOR_END } STMT_END ; + +/* Onion service directory file names. */ +static const char fname_keyfile_prefix[] = "hs_ed25519"; +static const char fname_hostname[] = "hostname"; +static const char address_tld[] = "onion"; + +/* Staging list of service object. When configuring service, we add them to + * this list considered a staging area and they will get added to our global + * map once the keys have been loaded. These two steps are seperated because + * loading keys requires that we are an actual running tor process. */ +static smartlist_t *hs_service_staging_list; + +/** True if the list of available router descriptors might have changed which + * might result in an altered hash ring. Check if the hash ring changed and + * reupload if needed */ +static int consider_republishing_hs_descriptors = 0; -/* XXX We don't currently use these functions, apart from generating unittest - data. When we start implementing the service-side support for prop224 we - should revisit these functions and use them. */ +static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc); +static void move_descriptors(hs_service_t *src, hs_service_t *dst); -/** Given an ESTABLISH_INTRO <b>cell</b>, encode it and place its payload in - * <b>buf_out</b> which has size <b>buf_out_len</b>. Return the number of - * bytes written, or a negative integer if there was an error. */ -ssize_t -get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len, - const trn_cell_establish_intro_t *cell) +/* Helper: Function to compare two objects in the service map. Return 1 if the + * two service have the same master public identity key. */ +static inline int +hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second) { - ssize_t bytes_used = 0; + tor_assert(first); + tor_assert(second); + /* Simple key compare. */ + return ed25519_pubkey_eq(&first->keys.identity_pk, + &second->keys.identity_pk); +} + +/* Helper: Function for the service hash table code below. The key used is the + * master public identity key which is ultimately the onion address. */ +static inline unsigned int +hs_service_ht_hash(const hs_service_t *service) +{ + tor_assert(service); + return (unsigned int) siphash24g(service->keys.identity_pk.pubkey, + sizeof(service->keys.identity_pk.pubkey)); +} + +/* This is _the_ global hash map of hidden services which indexed the service + * contained in it by master public identity key which is roughly the onion + * address of the service. */ +static struct hs_service_ht *hs_service_map; + +/* Register the service hash table. */ +HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */ + hs_service_t, /* Object contained in the map. */ + hs_service_node, /* The name of the HT_ENTRY member. */ + hs_service_ht_hash, /* Hashing function. */ + hs_service_ht_eq) /* Compare function for objects. */ + +HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node, + hs_service_ht_hash, hs_service_ht_eq, + 0.6, tor_reallocarray, tor_free_) - if (buf_out_len < RELAY_PAYLOAD_SIZE) { +/* Query the given service map with a public key and return a service object + * if found else NULL. It is also possible to set a directory path in the + * search query. If pk is NULL, then it will be set to zero indicating the + * hash table to compare the directory path instead. */ +STATIC hs_service_t * +find_service(hs_service_ht *map, const ed25519_public_key_t *pk) +{ + hs_service_t dummy_service; + tor_assert(map); + tor_assert(pk); + memset(&dummy_service, 0, sizeof(dummy_service)); + ed25519_pubkey_copy(&dummy_service.keys.identity_pk, pk); + return HT_FIND(hs_service_ht, map, &dummy_service); +} + +/* Register the given service in the given map. If the service already exists + * in the map, -1 is returned. On success, 0 is returned and the service + * ownership has been transfered to the global map. */ +STATIC int +register_service(hs_service_ht *map, hs_service_t *service) +{ + tor_assert(map); + tor_assert(service); + tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk)); + + if (find_service(map, &service->keys.identity_pk)) { + /* Existing service with the same key. Do not register it. */ return -1; } + /* Taking ownership of the object at this point. */ + HT_INSERT(hs_service_ht, map, service); + return 0; +} - bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len, - cell); - return bytes_used; +/* Remove a given service from the given map. If service is NULL or the + * service key is unset, return gracefully. */ +STATIC void +remove_service(hs_service_ht *map, hs_service_t *service) +{ + hs_service_t *elm; + + tor_assert(map); + + /* Ignore if no service or key is zero. */ + if (BUG(service == NULL) || + BUG(ed25519_public_key_is_zero(&service->keys.identity_pk))) { + return; + } + + elm = HT_REMOVE(hs_service_ht, map, service); + if (elm) { + tor_assert(elm == service); + } else { + log_warn(LD_BUG, "Could not find service in the global map " + "while removing service %s", + escaped(service->config.directory_path)); + } } -/* Set the cell extensions of <b>cell</b>. */ +/* Set the default values for a service configuration object <b>c</b>. */ static void -set_trn_cell_extensions(trn_cell_establish_intro_t *cell) +set_service_default_config(hs_service_config_t *c, + const or_options_t *options) { - trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new(); + (void) options; + tor_assert(c); + c->ports = smartlist_new(); + c->directory_path = NULL; + c->max_streams_per_rdv_circuit = 0; + c->max_streams_close_circuit = 0; + c->num_intro_points = NUM_INTRO_POINTS_DEFAULT; + c->allow_unknown_ports = 0; + c->is_single_onion = 0; + c->dir_group_readable = 0; + c->is_ephemeral = 0; +} - /* For now, we don't use extensions at all. */ - trn_cell_extensions->num = 0; /* It's already zeroed, but be explicit. */ - trn_cell_establish_intro_set_extensions(cell, trn_cell_extensions); +/* From a service configuration object config, clear everything from it + * meaning free allocated pointers and reset the values. */ +static void +service_clear_config(hs_service_config_t *config) +{ + if (config == NULL) { + return; + } + tor_free(config->directory_path); + if (config->ports) { + SMARTLIST_FOREACH(config->ports, rend_service_port_config_t *, p, + rend_service_port_config_free(p);); + smartlist_free(config->ports); + } + memset(config, 0, sizeof(*config)); } -/** Given the circuit handshake info in <b>circuit_key_material</b>, create and - * return an ESTABLISH_INTRO cell. Return NULL if something went wrong. The - * returned cell is allocated on the heap and it's the responsibility of the - * caller to free it. */ -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len) +/* Helper function to return a human readable description of the given intro + * point object. + * + * This function is not thread-safe. Each call to this invalidates the + * previous values returned by it. */ +static const char * +describe_intro_point(const hs_service_intro_point_t *ip) { - trn_cell_establish_intro_t *cell = NULL; - ssize_t encoded_len; + /* Hex identity digest of the IP prefixed by the $ sign and ends with NUL + * byte hence the plus two. */ + static char buf[HEX_DIGEST_LEN + 2]; + const char *legacy_id = NULL; - log_warn(LD_GENERAL, - "Generating ESTABLISH_INTRO cell (key_material_len: %u)", - (unsigned) circuit_key_material_len); + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + if (lspec->type == LS_LEGACY_ID) { + legacy_id = (const char *) lspec->u.legacy_id; + break; + } + } SMARTLIST_FOREACH_END(lspec); - /* Generate short-term keypair for use in ESTABLISH_INTRO */ - ed25519_keypair_t key_struct; - if (ed25519_keypair_generate(&key_struct, 0) < 0) { - goto err; + /* For now, we only print the identity digest but we could improve this with + * much more information such as the ed25519 identity has well. */ + buf[0] = '$'; + if (legacy_id) { + base16_encode(buf + 1, HEX_DIGEST_LEN + 1, legacy_id, DIGEST_LEN); } - cell = trn_cell_establish_intro_new(); + return buf; +} - /* Set AUTH_KEY_TYPE: 2 means ed25519 */ - trn_cell_establish_intro_set_auth_key_type(cell, - HS_INTRO_AUTH_KEY_TYPE_ED25519); +/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ +static int32_t +get_intro_point_min_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_introduce2", + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} - /* Set AUTH_KEY_LEN field */ - /* Must also set byte-length of AUTH_KEY to match */ - int auth_key_len = ED25519_PUBKEY_LEN; - trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); - trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); +/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ +static int32_t +get_intro_point_max_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_introduce2", + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} - /* Set AUTH_KEY field */ - uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); - memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len); +/* Return the minimum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ +static int32_t +get_intro_point_min_lifetime(void) +{ +#define MIN_INTRO_POINT_LIFETIME_TESTING 10 + if (get_options()->TestingTorNetwork) { + return MIN_INTRO_POINT_LIFETIME_TESTING; + } - /* No cell extensions needed */ - set_trn_cell_extensions(cell); + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_lifetime", + INTRO_POINT_LIFETIME_MIN_SECONDS, + 0, INT32_MAX); +} - /* Set signature size. - We need to do this up here, because _encode() needs it and we need to call - _encode() to calculate the MAC and signature. - */ - int sig_len = ED25519_SIG_LEN; - trn_cell_establish_intro_set_sig_len(cell, sig_len); - trn_cell_establish_intro_setlen_sig(cell, sig_len); +/* Return the maximum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ +static int32_t +get_intro_point_max_lifetime(void) +{ +#define MAX_INTRO_POINT_LIFETIME_TESTING 30 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_LIFETIME_TESTING; + } - /* XXX How to make this process easier and nicer? */ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_lifetime", + INTRO_POINT_LIFETIME_MAX_SECONDS, + 0, INT32_MAX); +} - /* Calculate the cell MAC (aka HANDSHAKE_AUTH). */ - { - /* To calculate HANDSHAKE_AUTH, we dump the cell in bytes, and then derive - the MAC from it. */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - uint8_t mac[TRUNNEL_SHA3_256_LEN]; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell."); +/* Return the number of extra introduction point defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_num_extra(void) +{ + /* The [0, 128] range bounds the number of extra introduction point allowed. + * Above 128 intro points, it's getting a bit crazy. */ + return networkstatus_get_param(NULL, "hs_intro_num_extra", + NUM_INTRO_POINTS_EXTRA, 0, 128); +} + +/* Helper: Function that needs to return 1 for the HT for each loop which + * frees every service in an hash map. */ +static int +ht_free_service_(struct hs_service_t *service, void *data) +{ + (void) data; + hs_service_free(service); + /* This function MUST return 1 so the given object is then removed from the + * service map leading to this free of the object being safe. */ + return 1; +} + +/* Free every service that can be found in the global map. Once done, clear + * and free the global map. */ +static void +service_free_all(void) +{ + if (hs_service_map) { + /* The free helper function returns 1 so this is safe. */ + hs_service_ht_HT_FOREACH_FN(hs_service_map, ht_free_service_, NULL); + HT_CLEAR(hs_service_ht, hs_service_map); + tor_free(hs_service_map); + hs_service_map = NULL; + } + + if (hs_service_staging_list) { + /* Cleanup staging list. */ + SMARTLIST_FOREACH(hs_service_staging_list, hs_service_t *, s, + hs_service_free(s)); + smartlist_free(hs_service_staging_list); + hs_service_staging_list = NULL; + } +} + +/* Free a given service intro point object. */ +STATIC void +service_intro_point_free(hs_service_intro_point_t *ip) +{ + if (!ip) { + return; + } + memwipe(&ip->auth_key_kp, 0, sizeof(ip->auth_key_kp)); + memwipe(&ip->enc_key_kp, 0, sizeof(ip->enc_key_kp)); + crypto_pk_free(ip->legacy_key); + replaycache_free(ip->replay_cache); + hs_intropoint_clear(&ip->base); + tor_free(ip); +} + +/* Helper: free an hs_service_intro_point_t object. This function is used by + * digest256map_free() which requires a void * pointer. */ +static void +service_intro_point_free_(void *obj) +{ + service_intro_point_free(obj); +} + +/* Return a newly allocated service intro point and fully initialized from the + * given extend_info_t ei if non NULL. If is_legacy is true, we also generate + * the legacy key. On error, NULL is returned. + * + * If ei is NULL, returns a hs_service_intro_point_t with an empty link + * specifier list and no onion key. (This is used for testing.) + * + * ei must be an extend_info_t containing an IPv4 address. (We will add supoort + * for IPv6 in a later release.) When calling extend_info_from_node(), pass + * 0 in for_direct_connection to make sure ei always has an IPv4 address. */ +STATIC hs_service_intro_point_t * +service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) +{ + hs_desc_link_specifier_t *ls; + hs_service_intro_point_t *ip; + + ip = tor_malloc_zero(sizeof(*ip)); + /* We'll create the key material. No need for extra strong, those are short + * term keys. */ + ed25519_keypair_generate(&ip->auth_key_kp, 0); + + { /* Set introduce2 max cells limit */ + int32_t min_introduce2_cells = get_intro_point_min_introduce2(); + int32_t max_introduce2_cells = get_intro_point_max_introduce2(); + if (BUG(max_introduce2_cells < min_introduce2_cells)) { + goto err; + } + ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells, + max_introduce2_cells); + } + { /* Set intro point lifetime */ + int32_t intro_point_min_lifetime = get_intro_point_min_lifetime(); + int32_t intro_point_max_lifetime = get_intro_point_max_lifetime(); + if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) { + goto err; + } + ip->time_to_expire = time(NULL) + + crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime); + } + + ip->replay_cache = replaycache_new(0, 0); + + /* Initialize the base object. We don't need the certificate object. */ + ip->base.link_specifiers = smartlist_new(); + + /* Generate the encryption key for this intro point. */ + curve25519_keypair_generate(&ip->enc_key_kp, 0); + /* Figure out if this chosen node supports v3 or is legacy only. */ + if (is_legacy) { + ip->base.is_only_legacy = 1; + /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */ + ip->legacy_key = crypto_pk_new(); + if (crypto_pk_generate_key(ip->legacy_key) < 0) { goto err; } + } + + if (ei == NULL) { + goto done; + } + + /* We'll try to add all link specifiers. Legacy is mandatory. + * IPv4 or IPv6 is required, and we always send IPv4. */ + ls = hs_desc_link_specifier_new(ei, LS_IPV4); + /* It is impossible to have an extend info object without a v4. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID); + /* It is impossible to have an extend info object without an identity + * digest. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + /* ed25519 identity key is optional for intro points */ + ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID); + if (ls) { + smartlist_add(ip->base.link_specifiers, ls); + } + + /* IPv6 is not supported in this release. */ + + /* Finally, copy onion key from the extend_info_t object. */ + memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key)); + + done: + return ip; + err: + service_intro_point_free(ip); + return NULL; +} + +/* Add the given intro point object to the given intro point map. The intro + * point MUST have its RSA encryption key set if this is a legacy type or the + * authentication key set otherwise. */ +STATIC void +service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) +{ + hs_service_intro_point_t *old_ip_entry; + + tor_assert(map); + tor_assert(ip); + + old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip); + /* Make sure we didn't just try to double-add an intro point */ + tor_assert_nonfatal(!old_ip_entry); +} + +/* For a given service, remove the intro point from that service's descriptors + * (check both current and next descriptor) */ +STATIC void +service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + tor_assert(service); + tor_assert(ip); + + /* Trying all descriptors. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We'll try to remove the descriptor on both descriptors which is not + * very expensive to do instead of doing loopup + remove. */ + digest256map_remove(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey); + } FOR_EACH_DESCRIPTOR_END; +} + +/* For a given service and authentication key, return the intro point or NULL + * if not found. This will check both descriptors in the service. */ +STATIC hs_service_intro_point_t * +service_intro_point_find(const hs_service_t *service, + const ed25519_public_key_t *auth_key) +{ + hs_service_intro_point_t *ip = NULL; + + tor_assert(service); + tor_assert(auth_key); + + /* Trying all descriptors to find the right intro point. + * + * Even if we use the same node as intro point in both descriptors, the node + * will have a different intro auth key for each descriptor since we generate + * a new one everytime we pick an intro point. + * + * After #22893 gets implemented, intro points will be moved to be + * per-service instead of per-descriptor so this function will need to + * change. + */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if ((ip = digest256map_get(desc->intro_points.map, + auth_key->pubkey)) != NULL) { + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return ip; +} + +/* For a given service and intro point, return the descriptor for which the + * intro point is assigned to. NULL is returned if not found. */ +STATIC hs_service_descriptor_t * +service_desc_find_by_intro(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_service_descriptor_t *descp = NULL; + + tor_assert(service); + tor_assert(ip); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if (digest256map_get(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey)) { + descp = desc; + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return descp; +} + +/* From a circuit identifier, get all the possible objects associated with the + * ident. If not NULL, service, ip or desc are set if the object can be found. + * They are untouched if they can't be found. + * + * This is an helper function because we do those lookups often so it's more + * convenient to simply call this functions to get all the things at once. */ +STATIC void +get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc) +{ + hs_service_t *s; + + tor_assert(ident); + + /* Get service object from the circuit identifier. */ + s = find_service(hs_service_map, &ident->identity_pk); + if (s && service) { + *service = s; + } + + /* From the service object, get the intro point object of that circuit. The + * following will query both descriptors intro points list. */ + if (s && ip) { + *ip = service_intro_point_find(s, &ident->intro_auth_pk); + } + + /* Get the descriptor for this introduction point and service. */ + if (s && ip && *ip && desc) { + *desc = service_desc_find_by_intro(s, *ip); + } +} + +/* From a given intro point, return the first link specifier of type + * encountered in the link specifier list. Return NULL if it can't be found. + * + * The caller does NOT have ownership of the object, the intro point does. */ +static hs_desc_link_specifier_t * +get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) +{ + hs_desc_link_specifier_t *lnk_spec = NULL; + + tor_assert(ip); + + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + hs_desc_link_specifier_t *, ls) { + if (ls->type == type) { + lnk_spec = ls; + goto end; + } + } SMARTLIST_FOREACH_END(ls); + + end: + return lnk_spec; +} + +/* Given a service intro point, return the node_t associated to it. This can + * return NULL if the given intro point has no legacy ID or if the node can't + * be found in the consensus. */ +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip) +{ + const hs_desc_link_specifier_t *ls; + + tor_assert(ip); + + ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + if (BUG(!ls)) { + return NULL; + } + /* XXX In the future, we want to only use the ed25519 ID (#22173). */ + return node_get_by_id((const char *) ls->u.legacy_id); +} + +/* Given a service intro point, return the extend_info_t for it. This can + * return NULL if the node can't be found for the intro point or the extend + * info can't be created for the found node. If direct_conn is set, the extend + * info is validated on if we can connect directly. */ +static extend_info_t * +get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, + unsigned int direct_conn) +{ + extend_info_t *info = NULL; + const node_t *node; + + tor_assert(ip); + + node = get_node_from_intro_point(ip); + if (node == NULL) { + /* This can happen if the relay serving as intro point has been removed + * from the consensus. In that case, the intro point will be removed from + * the descriptor during the scheduled events. */ + goto end; + } + + /* In the case of a direct connection (single onion service), it is possible + * our firewall policy won't allow it so this can return a NULL value. */ + info = extend_info_from_node(node, direct_conn); + + end: + return info; +} + +/* Return the number of introduction points that are established for the + * given descriptor. */ +static unsigned int +count_desc_circuit_established(const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + count += ip->circuit_established; + } DIGEST256MAP_FOREACH_END; + + return count; +} + +/* For a given service and descriptor of that service, close all active + * directory connections. */ +static void +close_directory_connections(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + smartlist_t *dir_conns; + + tor_assert(service); + tor_assert(desc); + + /* Close pending HS desc upload connections for the blinded key of 'desc'. */ + dir_conns = connection_list_by_type_purpose(CONN_TYPE_DIR, + DIR_PURPOSE_UPLOAD_HSDESC); + SMARTLIST_FOREACH_BEGIN(dir_conns, connection_t *, conn) { + dir_connection_t *dir_conn = TO_DIR_CONN(conn); + if (ed25519_pubkey_eq(&dir_conn->hs_ident->identity_pk, + &service->keys.identity_pk) && + ed25519_pubkey_eq(&dir_conn->hs_ident->blinded_pk, + &desc->blinded_kp.pubkey)) { + connection_mark_for_close(conn); + count++; + continue; + } + } SMARTLIST_FOREACH_END(conn); + + log_info(LD_REND, "Closed %u active service directory connections for " + "descriptor %s of service %s", + count, safe_str_client(ed25519_fmt(&desc->blinded_kp.pubkey)), + safe_str_client(service->onion_address)); + /* We don't have ownership of the objects in this list. */ + smartlist_free(dir_conns); +} + +/* Close all rendezvous circuits for the given service. */ +static void +close_service_rp_circuits(hs_service_t *service) +{ + origin_circuit_t *ocirc = NULL; + + tor_assert(service); + + /* The reason we go over all circuit instead of using the circuitmap API is + * because most hidden service circuits are rendezvous circuits so there is + * no real improvement at getting all rendezvous circuits from the + * circuitmap and then going over them all to find the right ones. + * Furthermore, another option would have been to keep a list of RP cookies + * for a service but it creates an engineering complexity since we don't + * have a "RP circuit closed" event to clean it up properly so we avoid a + * memory DoS possibility. */ + + while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) { + /* Only close circuits that are v3 and for this service. */ + if (ocirc->hs_ident != NULL && + ed25519_pubkey_eq(ô->hs_ident->identity_pk, + &service->keys.identity_pk)) { + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. When freed, it is removed from the + * hs circuitmap. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } +} + +/* Close the circuit(s) for the given map of introduction points. */ +static void +close_intro_circuits(hs_service_intropoints_t *intro_points) +{ + tor_assert(intro_points); + + DIGEST256MAP_FOREACH(intro_points->map, key, + const hs_service_intro_point_t *, ip) { + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc) { + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. When freed, the circuit is removed + * from the HS circuitmap. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Close all introduction circuits for the given service. */ +static void +close_service_intro_circuits(hs_service_t *service) +{ + tor_assert(service); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + close_intro_circuits(&desc->intro_points); + } FOR_EACH_DESCRIPTOR_END; +} + +/* Close any circuits related to the given service. */ +static void +close_service_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* Only support for version >= 3. */ + if (BUG(service->config.version < HS_VERSION_THREE)) { + return; + } + /* Close intro points. */ + close_service_intro_circuits(service); + /* Close rendezvous points. */ + close_service_rp_circuits(service); +} + +/* Move every ephemeral services from the src service map to the dst service + * map. It is possible that a service can't be register to the dst map which + * won't stop the process of moving them all but will trigger a log warn. */ +static void +move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst) +{ + hs_service_t **iter, **next; + + tor_assert(src); + tor_assert(dst); + + /* Iterate over the map to find ephemeral service and move them to the other + * map. We loop using this method to have a safe removal process. */ + for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) { + hs_service_t *s = *iter; + if (!s->config.is_ephemeral) { + /* Yeah, we are in a very manual loop :). */ + next = HT_NEXT(hs_service_ht, src, iter); + continue; + } + /* Remove service from map and then register to it to the other map. + * Reminder that "*iter" and "s" are the same thing. */ + next = HT_NEXT_RMV(hs_service_ht, src, iter); + if (register_service(dst, s) < 0) { + log_warn(LD_BUG, "Ephemeral service key is already being used. " + "Skipping."); + } + } +} + +/* Return a const string of the directory path escaped. If this is an + * ephemeral service, it returns "[EPHEMERAL]". This can only be called from + * the main thread because escaped() uses a static variable. */ +static const char * +service_escaped_dir(const hs_service_t *s) +{ + return (s->config.is_ephemeral) ? "[EPHEMERAL]" : + escaped(s->config.directory_path); +} + +/** Move the hidden service state from <b>src</b> to <b>dst</b>. We do this + * when we receive a SIGHUP: <b>dst</b> is the post-HUP service */ +static void +move_hs_state(hs_service_t *src_service, hs_service_t *dst_service) +{ + tor_assert(src_service); + tor_assert(dst_service); + + hs_service_state_t *src = &src_service->state; + hs_service_state_t *dst = &dst_service->state; + + /* Let's do a shallow copy */ + dst->intro_circ_retry_started_time = src->intro_circ_retry_started_time; + dst->num_intro_circ_launched = src->num_intro_circ_launched; + dst->replay_cache_rend_cookie = src->replay_cache_rend_cookie; + + src->replay_cache_rend_cookie = NULL; /* steal pointer reference */ +} + +/* Register services that are in the staging list. Once this function returns, + * the global service map will be set with the right content and all non + * surviving services will be cleaned up. */ +static void +register_all_services(void) +{ + struct hs_service_ht *new_service_map; - /* sanity check */ - tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN); + tor_assert(hs_service_staging_list); - /* Calculate MAC of all fields before HANDSHAKE_AUTH */ - crypto_mac_sha3_256(mac, sizeof(mac), - circuit_key_material, circuit_key_material_len, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN)); - /* Write the MAC to the cell */ - uint8_t *handshake_ptr = - trn_cell_establish_intro_getarray_handshake_mac(cell); - memcpy(handshake_ptr, mac, sizeof(mac)); + /* We'll save us some allocation and computing time. */ + if (smartlist_len(hs_service_staging_list) == 0) { + return; } - /* Calculate the cell signature */ + /* Allocate a new map that will replace the current one. */ + new_service_map = tor_malloc_zero(sizeof(*new_service_map)); + HT_INIT(hs_service_ht, new_service_map); + + /* First step is to transfer all ephemeral services from the current global + * map to the new one we are constructing. We do not prune ephemeral + * services as the only way to kill them is by deleting it from the control + * port or stopping the tor daemon. */ + move_ephemeral_services(hs_service_map, new_service_map); + + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) { + hs_service_t *s; + + /* Check if that service is already in our global map and if so, we'll + * transfer the intro points to it. */ + s = find_service(hs_service_map, &snew->keys.identity_pk); + if (s) { + /* Pass ownership of the descriptors from s (the current service) to + * snew (the newly configured one). */ + move_descriptors(s, snew); + move_hs_state(s, snew); + /* Remove the service from the global map because after this, we need to + * go over the remaining service in that map that aren't surviving the + * reload to close their circuits. */ + remove_service(hs_service_map, s); + hs_service_free(s); + } + /* Great, this service is now ready to be added to our new map. */ + if (BUG(register_service(new_service_map, snew) < 0)) { + /* This should never happen because prior to registration, we validate + * every service against the entire set. Not being able to register a + * service means we failed to validate correctly. In that case, don't + * break tor and ignore the service but tell user. */ + log_warn(LD_BUG, "Unable to register service with directory %s", + service_escaped_dir(snew)); + SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew); + hs_service_free(snew); + } + } SMARTLIST_FOREACH_END(snew); + + /* Close any circuits associated with the non surviving services. Every + * service in the current global map are roaming. */ + FOR_EACH_SERVICE_BEGIN(service) { + close_service_circuits(service); + } FOR_EACH_SERVICE_END; + + /* Time to make the switch. We'll clear the staging list because its content + * has now changed ownership to the map. */ + smartlist_clear(hs_service_staging_list); + service_free_all(); + hs_service_map = new_service_map; +} + +/* Write the onion address of a given service to the given filename fname_ in + * the service directory. Return 0 on success else -1 on error. */ +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_) +{ + int ret = -1; + char *fname = NULL; + char *addr_buf = NULL; + + tor_assert(service); + tor_assert(fname_); + + /* Construct the full address with the onion tld and write the hostname file + * to disk. */ + tor_asprintf(&addr_buf, "%s.%s\n", service->onion_address, address_tld); + /* Notice here that we use the given "fname_". */ + fname = hs_path_from_filename(service->config.directory_path, fname_); + if (write_str_to_file(fname, addr_buf, 0) < 0) { + log_warn(LD_REND, "Could not write onion address to hostname file %s", + escaped(fname)); + goto end; + } + +#ifndef _WIN32 + if (service->config.dir_group_readable) { + /* Mode to 0640. */ + if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) { + log_warn(LD_FS, "Unable to make onion service hostname file %s " + "group-readable.", escaped(fname)); + } + } +#endif /* !defined(_WIN32) */ + + /* Success. */ + ret = 0; + end: + tor_free(fname); + tor_free(addr_buf); + return ret; +} + +/* Load and/or generate private keys for the given service. On success, the + * hostname file will be written to disk along with the master private key iff + * the service is not configured for offline keys. Return 0 on success else -1 + * on failure. */ +static int +load_service_keys(hs_service_t *service) +{ + int ret = -1; + char *fname = NULL; + ed25519_keypair_t *kp; + const hs_service_config_t *config; + + tor_assert(service); + + config = &service->config; + + /* Create and fix permission on service directory. We are about to write + * files to that directory so make sure it exists and has the right + * permissions. We do this here because at this stage we know that Tor is + * actually running and the service we have has been validated. */ + if (BUG(hs_check_service_private_dir(get_options()->User, + config->directory_path, + config->dir_group_readable, 1) < 0)) { + goto end; + } + + /* Try to load the keys from file or generate it if not found. */ + fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix); + /* Don't ask for key creation, we want to know if we were able to load it or + * we had to generate it. Better logging! */ + kp = ed_key_init_from_file(fname, INIT_ED_KEY_SPLIT, LOG_INFO, NULL, 0, 0, + 0, NULL); + if (!kp) { + log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname); + /* We'll now try to generate the keys and for it we want the strongest + * randomness for it. The keypair will be written in different files. */ + uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG | + INIT_ED_KEY_SPLIT; + kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0, + NULL); + if (!kp) { + log_warn(LD_REND, "Unable to generate keys and save in %s.", fname); + goto end; + } + } + + /* Copy loaded or generated keys to service object. */ + ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey); + memcpy(&service->keys.identity_sk, &kp->seckey, + sizeof(service->keys.identity_sk)); + /* This does a proper memory wipe. */ + ed25519_keypair_free(kp); + + /* Build onion address from the newly loaded keys. */ + tor_assert(service->config.version <= UINT8_MAX); + hs_build_address(&service->keys.identity_pk, + (uint8_t) service->config.version, + service->onion_address); + + /* Write onion address to hostname file. */ + if (write_address_to_file(service, fname_hostname) < 0) { + goto end; + } + + /* Succes. */ + ret = 0; + end: + tor_free(fname); + return ret; +} + +/* Free a given service descriptor object and all key material is wiped. */ +STATIC void +service_descriptor_free(hs_service_descriptor_t *desc) +{ + if (!desc) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp)); + memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp)); + /* Cleanup all intro points. */ + digest256map_free(desc->intro_points.map, service_intro_point_free_); + digestmap_free(desc->intro_points.failed_id, tor_free_); + if (desc->previous_hsdirs) { + SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s)); + smartlist_free(desc->previous_hsdirs); + } + tor_free(desc); +} + +/* Return a newly allocated service descriptor object. */ +STATIC hs_service_descriptor_t * +service_descriptor_new(void) +{ + hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc)); + sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t)); + /* Initialize the intro points map. */ + sdesc->intro_points.map = digest256map_new(); + sdesc->intro_points.failed_id = digestmap_new(); + sdesc->previous_hsdirs = smartlist_new(); + return sdesc; +} + +/* Move descriptor(s) from the src service to the dst service. We do this + * during SIGHUP when we re-create our hidden services. */ +static void +move_descriptors(hs_service_t *src, hs_service_t *dst) +{ + tor_assert(src); + tor_assert(dst); + + if (src->desc_current) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_current)) { + service_descriptor_free(dst->desc_current); + } + dst->desc_current = src->desc_current; + src->desc_current = NULL; + } + + if (src->desc_next) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_next)) { + service_descriptor_free(dst->desc_next); + } + dst->desc_next = src->desc_next; + src->desc_next = NULL; + } +} + +/* From the given service, remove all expired failing intro points for each + * descriptor. */ +static void +remove_expired_failing_intro(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* For both descriptors, cleanup the failing intro points list. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) { + time_t failure_time = *t; + if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) { + MAP_DEL_CURRENT(key); + tor_free(t); + } + } DIGESTMAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* For the given descriptor desc, put all node_t object found from its failing + * intro point list and put them in the given node_list. */ +static void +setup_intro_point_exclude_list(const hs_service_descriptor_t *desc, + smartlist_t *node_list) +{ + tor_assert(desc); + tor_assert(node_list); + + DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) { + (void) t; /* Make gcc happy. */ + const node_t *node = node_get_by_id(key); + if (node) { + smartlist_add(node_list, (void *) node); + } + } DIGESTMAP_FOREACH_END; +} + +/* For the given failing intro point ip, we add its time of failure to the + * failed map and index it by identity digest (legacy ID) in the descriptor + * desc failed id map. */ +static void +remember_failing_intro_point(const hs_service_intro_point_t *ip, + hs_service_descriptor_t *desc, time_t now) +{ + time_t *time_of_failure, *prev_ptr; + const hs_desc_link_specifier_t *legacy_ls; + + tor_assert(ip); + tor_assert(desc); + + time_of_failure = tor_malloc_zero(sizeof(time_t)); + *time_of_failure = now; + legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + tor_assert(legacy_ls); + prev_ptr = digestmap_set(desc->intro_points.failed_id, + (const char *) legacy_ls->u.legacy_id, + time_of_failure); + tor_free(prev_ptr); +} + +/* Copy the descriptor link specifier object from src to dst. */ +static void +link_specifier_copy(hs_desc_link_specifier_t *dst, + const hs_desc_link_specifier_t *src) +{ + tor_assert(dst); + tor_assert(src); + memcpy(dst, src, sizeof(hs_desc_link_specifier_t)); +} + +/* Using a given descriptor signing keypair signing_kp, a service intro point + * object ip and the time now, setup the content of an already allocated + * descriptor intro desc_ip. + * + * Return 0 on success else a negative value. */ +static int +setup_desc_intro_point(const ed25519_keypair_t *signing_kp, + const hs_service_intro_point_t *ip, + time_t now, hs_desc_intro_point_t *desc_ip) +{ + int ret = -1; + time_t nearest_hour = now - (now % 3600); + + tor_assert(signing_kp); + tor_assert(ip); + tor_assert(desc_ip); + + /* Copy the onion key. */ + memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key)); + + /* Key and certificate material. */ + desc_ip->auth_key_cert = tor_cert_create(signing_kp, + CERT_TYPE_AUTH_HS_IP_KEY, + &ip->auth_key_kp.pubkey, + nearest_hour, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (desc_ip->auth_key_cert == NULL) { + log_warn(LD_REND, "Unable to create intro point auth-key certificate"); + goto done; + } + + /* Copy link specifier(s). */ + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + const hs_desc_link_specifier_t *, ls) { + hs_desc_link_specifier_t *dup = tor_malloc_zero(sizeof(*dup)); + link_specifier_copy(dup, ls); + smartlist_add(desc_ip->link_specifiers, dup); + } SMARTLIST_FOREACH_END(ls); + + /* For a legacy intro point, we'll use an RSA/ed cross certificate. */ + if (ip->base.is_only_legacy) { + desc_ip->legacy.key = crypto_pk_dup_key(ip->legacy_key); + /* Create cross certification cert. */ + ssize_t cert_len = tor_make_rsa_ed25519_crosscert( + &signing_kp->pubkey, + desc_ip->legacy.key, + nearest_hour + HS_DESC_CERT_LIFETIME, + &desc_ip->legacy.cert.encoded); + if (cert_len < 0) { + log_warn(LD_REND, "Unable to create enc key legacy cross cert."); + goto done; + } + desc_ip->legacy.cert.len = cert_len; + } + + /* Encryption key and its cross certificate. */ { - /* To calculate the sig we follow the same procedure as above. We first - dump the cell up to the sig, and then calculate the sig */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - ed25519_signature_t sig; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell (2)."); - goto err; + ed25519_public_key_t ed25519_pubkey; + + /* Use the public curve25519 key. */ + memcpy(&desc_ip->enc_key, &ip->enc_key_kp.pubkey, + sizeof(desc_ip->enc_key)); + /* The following can't fail. */ + ed25519_public_key_from_curve25519_public_key(&ed25519_pubkey, + &ip->enc_key_kp.pubkey, + 0); + desc_ip->enc_key_cert = tor_cert_create(signing_kp, + CERT_TYPE_CROSS_HS_IP_KEYS, + &ed25519_pubkey, nearest_hour, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (desc_ip->enc_key_cert == NULL) { + log_warn(LD_REND, "Unable to create enc key curve25519 cross cert."); + goto done; } + } + /* Success. */ + ret = 0; - tor_assert(encoded_len > ED25519_SIG_LEN); + done: + return ret; +} - if (ed25519_sign_prefixed(&sig, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + sizeof(cell->sig_len)), - ESTABLISH_INTRO_SIG_PREFIX, - &key_struct)) { - log_warn(LD_BUG, "Unable to gen signature for ESTABLISH_INTRO cell."); - goto err; +/* Using the given descriptor from the given service, build the descriptor + * intro point list so we can then encode the descriptor for publication. This + * function does not pick intro points, they have to be in the descriptor + * current map. Cryptographic material (keys) must be initialized in the + * descriptor for this function to make sense. */ +static void +build_desc_intro_points(const hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + hs_desc_encrypted_data_t *encrypted; + + tor_assert(service); + tor_assert(desc); + + /* Ease our life. */ + encrypted = &desc->desc->encrypted_data; + /* Cleanup intro points, we are about to set them from scratch. */ + hs_descriptor_clear_intro_points(desc->desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + hs_desc_intro_point_t *desc_ip = hs_desc_intro_point_new(); + if (setup_desc_intro_point(&desc->signing_kp, ip, now, desc_ip) < 0) { + hs_desc_intro_point_free(desc_ip); + continue; } + /* We have a valid descriptor intro point. Add it to the list. */ + smartlist_add(encrypted->intro_points, desc_ip); + } DIGEST256MAP_FOREACH_END; +} + +/* Populate the descriptor encrypted section fomr the given service object. + * This will generate a valid list of introduction points that can be used + * after for circuit creation. Return 0 on success else -1 on error. */ +static int +build_service_desc_encrypted(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + hs_desc_encrypted_data_t *encrypted; - /* And write the signature to the cell */ - uint8_t *sig_ptr = trn_cell_establish_intro_getarray_sig(cell); - memcpy(sig_ptr, sig.sig, sig_len); + tor_assert(service); + tor_assert(desc); + + encrypted = &desc->desc->encrypted_data; + + encrypted->create2_ntor = 1; + encrypted->single_onion_service = service->config.is_single_onion; + + /* Setup introduction points from what we have in the service. */ + if (encrypted->intro_points == NULL) { + encrypted->intro_points = smartlist_new(); } + /* We do NOT build introduction point yet, we only do that once the circuit + * have been opened. Until we have the right number of introduction points, + * we do not encode anything in the descriptor. */ + + /* XXX: Support client authorization (#20700). */ + encrypted->intro_auth_types = NULL; + return 0; +} + +/* Populare the descriptor plaintext section from the given service object. + * The caller must make sure that the keys in the descriptors are valid that + * is are non-zero. Return 0 on success else -1 on error. */ +static int +build_service_desc_plaintext(const hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + int ret = -1; + hs_desc_plaintext_data_t *plaintext; - /* We are done! Return the cell! */ - return cell; + tor_assert(service); + tor_assert(desc); + /* XXX: Use a "assert_desc_ok()" ? */ + tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp, + sizeof(desc->blinded_kp))); + tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp, + sizeof(desc->signing_kp))); + + /* Set the subcredential. */ + hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey, + desc->desc->subcredential); + + plaintext = &desc->desc->plaintext_data; + + plaintext->version = service->config.version; + plaintext->lifetime_sec = HS_DESC_DEFAULT_LIFETIME; + plaintext->signing_key_cert = + tor_cert_create(&desc->blinded_kp, CERT_TYPE_SIGNING_HS_DESC, + &desc->signing_kp.pubkey, now, HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (plaintext->signing_key_cert == NULL) { + log_warn(LD_REND, "Unable to create descriptor signing certificate for " + "service %s", + safe_str_client(service->onion_address)); + goto end; + } + /* Copy public key material to go in the descriptor. */ + ed25519_pubkey_copy(&plaintext->signing_pubkey, &desc->signing_kp.pubkey); + ed25519_pubkey_copy(&plaintext->blinded_pubkey, &desc->blinded_kp.pubkey); + /* Success. */ + ret = 0; + + end: + return ret; +} +/* For the given service and descriptor object, create the key material which + * is the blinded keypair and the descriptor signing keypair. Return 0 on + * success else -1 on error where the generated keys MUST be ignored. */ +static int +build_service_desc_keys(const hs_service_t *service, + hs_service_descriptor_t *desc, + uint64_t time_period_num) +{ + int ret = 0; + ed25519_keypair_t kp; + + tor_assert(desc); + tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk, + ED25519_PUBKEY_LEN)); + + /* XXX: Support offline key feature (#18098). */ + + /* Copy the identity keys to the keypair so we can use it to create the + * blinded key. */ + memcpy(&kp.pubkey, &service->keys.identity_pk, sizeof(kp.pubkey)); + memcpy(&kp.seckey, &service->keys.identity_sk, sizeof(kp.seckey)); + /* Build blinded keypair for this time period. */ + hs_build_blinded_keypair(&kp, NULL, 0, time_period_num, &desc->blinded_kp); + /* Let's not keep too much traces of our keys in memory. */ + memwipe(&kp, 0, sizeof(kp)); + + /* No need for extra strong, this is a temporary key only for this + * descriptor. Nothing long term. */ + if (ed25519_keypair_generate(&desc->signing_kp, 0) < 0) { + log_warn(LD_REND, "Can't generate descriptor signing keypair for " + "service %s", + safe_str_client(service->onion_address)); + ret = -1; + } + + return ret; +} + +/* Given a service and the current time, build a descriptor for the service. + * This function does not pick introduction point, this needs to be done by + * the update function. On success, desc_out will point to the newly allocated + * descriptor object. + * + * This can error if we are unable to create keys or certificate. */ +static void +build_service_descriptor(hs_service_t *service, time_t now, + uint64_t time_period_num, + hs_service_descriptor_t **desc_out) +{ + char *encoded_desc; + hs_service_descriptor_t *desc; + + tor_assert(service); + tor_assert(desc_out); + + desc = service_descriptor_new(); + desc->time_period_num = time_period_num; + + /* Create the needed keys so we can setup the descriptor content. */ + if (build_service_desc_keys(service, desc, time_period_num) < 0) { + goto err; + } + /* Setup plaintext descriptor content. */ + if (build_service_desc_plaintext(service, desc, now) < 0) { + goto err; + } + /* Setup encrypted descriptor content. */ + if (build_service_desc_encrypted(service, desc) < 0) { + goto err; + } + + /* Set the revision counter for this descriptor */ + set_descriptor_revision_counter(desc->desc); + + /* Let's make sure that we've created a descriptor that can actually be + * encoded properly. This function also checks if the encoded output is + * decodable after. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto err; + } + tor_free(encoded_desc); + + /* Assign newly built descriptor to the next slot. */ + *desc_out = desc; + return; + + err: + service_descriptor_free(desc); +} + +/* Build both descriptors for the given service that has just booted up. + * Because it's a special case, it deserves its special function ;). */ +static void +build_descriptors_for_new_service(hs_service_t *service, time_t now) +{ + uint64_t current_desc_tp, next_desc_tp; + + tor_assert(service); + /* These are the conditions for a new service. */ + tor_assert(!service->desc_current); + tor_assert(!service->desc_next); + + /* + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | A B | + * +------------------------------------------------------------------+ + * + * Case A: The service boots up before a new time period, the current time + * period is thus TP#1 and the next is TP#2 which for both we have access to + * their SRVs. + * + * Case B: The service boots up inside TP#2, we can't use the TP#3 for the + * next descriptor because we don't have the SRV#3 so the current should be + * TP#1 and next TP#2. + */ + + if (hs_in_period_between_tp_and_srv(NULL, now)) { + /* Case B from the above, inside of the new time period. */ + current_desc_tp = hs_get_previous_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_time_period_num(0); /* TP#2 */ + } else { + /* Case A from the above, outside of the new time period. */ + current_desc_tp = hs_get_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_next_time_period_num(0); /* TP#2 */ + } + + /* Build descriptors. */ + build_service_descriptor(service, now, current_desc_tp, + &service->desc_current); + build_service_descriptor(service, now, next_desc_tp, + &service->desc_next); + log_info(LD_REND, "Hidden service %s has just started. Both descriptors " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); +} + +/* Build descriptors for each service if needed. There are conditions to build + * a descriptor which are details in the function. */ +STATIC void +build_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + + /* A service booting up will have both descriptors to NULL. No other cases + * makes both descriptor non existent. */ + if (service->desc_current == NULL && service->desc_next == NULL) { + build_descriptors_for_new_service(service, now); + continue; + } + + /* Reaching this point means we are pass bootup so at runtime. We should + * *never* have an empty current descriptor. If the next descriptor is + * empty, we'll try to build it for the next time period. This only + * happens when we rotate meaning that we are guaranteed to have a new SRV + * at that point for the next time period. */ + tor_assert(service->desc_current); + + if (service->desc_next == NULL) { + build_service_descriptor(service, now, hs_get_next_time_period_num(0), + &service->desc_next); + log_info(LD_REND, "Hidden service %s next descriptor successfully " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); + } + } FOR_EACH_DESCRIPTOR_END; +} + +/* Randomly pick a node to become an introduction point but not present in the + * given exclude_nodes list. The chosen node is put in the exclude list + * regardless of success or not because in case of failure, the node is simply + * unsusable from that point on. + * + * If direct_conn is set, try to pick a node that our local firewall/policy + * allows us to connect to directly. If we can't find any, return NULL. + * This function supports selecting dual-stack nodes for direct single onion + * service IPv6 connections. But it does not send IPv6 addresses in link + * specifiers. (Current clients don't use IPv6 addresses to extend, and + * direct client connections to intro points are not supported.) + * + * Return a newly allocated service intro point ready to be used for encoding. + * Return NULL on error. */ +static hs_service_intro_point_t * +pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) +{ + const node_t *node; + extend_info_t *info = NULL; + hs_service_intro_point_t *ip = NULL; + /* Normal 3-hop introduction point flags. */ + router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC; + /* Single onion flags. */ + router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN; + + node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes, + direct_conn ? direct_flags : flags); + /* Unable to find a node. When looking for a node for a direct connection, + * we could try a 3-hop path instead. We'll add support for this in a later + * release. */ + if (!node) { + goto err; + } + + /* We have a suitable node, add it to the exclude list. We do this *before* + * we can validate the extend information because even in case of failure, + * we don't want to use that node anymore. */ + smartlist_add(exclude_nodes, (void *) node); + + /* We do this to ease our life but also this call makes appropriate checks + * of the node object such as validating ntor support for instance. + * + * We must provide an extend_info for clients to connect over a 3-hop path, + * so we don't pass direct_conn here. */ + info = extend_info_from_node(node, 0); + if (BUG(info == NULL)) { + goto err; + } + + /* Let's do a basic sanity check here so that we don't end up advertising the + * ed25519 identity key of relays that don't actually support the link + * protocol */ + if (!node_supports_ed25519_link_authentication(node)) { + tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity)); + } else { + /* Make sure we *do* have an ed key if we support the link authentication. + * Sending an empty key would result in a failure to extend. */ + tor_assert_nonfatal(!ed25519_public_key_is_zero(&info->ed_identity)); + } + + /* Create our objects and populate them with the node information. */ + ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node)); + if (ip == NULL) { + goto err; + } + + log_info(LD_REND, "Picked intro point: %s", extend_info_describe(info)); + extend_info_free(info); + return ip; err: - trn_cell_establish_intro_free(cell); + service_intro_point_free(ip); + extend_info_free(info); return NULL; } +/* For a given descriptor from the given service, pick any needed intro points + * and update the current map with those newly picked intro points. Return the + * number node that might have been added to the descriptor current map. */ +static unsigned int +pick_needed_intro_points(hs_service_t *service, + hs_service_descriptor_t *desc) +{ + int i = 0, num_needed_ip; + smartlist_t *exclude_nodes = smartlist_new(); + + tor_assert(service); + tor_assert(desc); + + /* Compute how many intro points we actually need to open. */ + num_needed_ip = service->config.num_intro_points - + digest256map_size(desc->intro_points.map); + if (BUG(num_needed_ip < 0)) { + /* Let's not make tor freak out here and just skip this. */ + goto done; + } + + /* We want to end up with config.num_intro_points intro points, but if we + * have no intro points at all (chances are they all cycled or we are + * starting up), we launch get_intro_point_num_extra() extra circuits and + * use the first config.num_intro_points that complete. See proposal #155, + * section 4 for the rationale of this which is purely for performance. + * + * The ones after the first config.num_intro_points will be converted to + * 'General' internal circuits and then we'll drop them from the list of + * intro points. */ + if (digest256map_size(desc->intro_points.map) == 0) { + num_needed_ip += get_intro_point_num_extra(); + } + + /* Build an exclude list of nodes of our intro point(s). The expiring intro + * points are OK to pick again because this is afterall a concept of round + * robin so they are considered valid nodes to pick again. */ + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *intro_node = get_node_from_intro_point(ip); + if (intro_node) { + smartlist_add(exclude_nodes, (void*)intro_node); + } + } DIGEST256MAP_FOREACH_END; + /* Also, add the failing intro points that our descriptor encounteered in + * the exclude node list. */ + setup_intro_point_exclude_list(desc, exclude_nodes); + + for (i = 0; i < num_needed_ip; i++) { + hs_service_intro_point_t *ip; + + /* This function will add the picked intro point node to the exclude nodes + * list so we don't pick the same one at the next iteration. */ + ip = pick_intro_point(service->config.is_single_onion, exclude_nodes); + if (ip == NULL) { + /* If we end up unable to pick an introduction point it is because we + * can't find suitable node and calling this again is highly unlikely to + * give us a valid node all of the sudden. */ + log_info(LD_REND, "Unable to find a suitable node to be an " + "introduction point for service %s.", + safe_str_client(service->onion_address)); + goto done; + } + /* Valid intro point object, add it to the descriptor current map. */ + service_intro_point_add(desc->intro_points.map, ip); + } + /* We've successfully picked all our needed intro points thus none are + * missing which will tell our upload process to expect the number of + * circuits to be the number of configured intro points circuits and not the + * number of intro points object that we have. */ + desc->missing_intro_points = 0; + + /* Success. */ + done: + /* We don't have ownership of the node_t object in this list. */ + smartlist_free(exclude_nodes); + return i; +} + +/** Clear previous cached HSDirs in <b>desc</b>. */ +static void +service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc) +{ + if (BUG(!desc->previous_hsdirs)) { + return; + } + + SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s)); + smartlist_clear(desc->previous_hsdirs); +} + +/** Note that we attempted to upload <b>desc</b> to <b>hsdir</b>. */ +static void +service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir->identity); + + if (BUG(!desc->previous_hsdirs)) { + return; + } + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + smartlist_add_strdup(desc->previous_hsdirs, b64_digest); + } +} + +/** Schedule an upload of <b>desc</b>. If <b>descriptor_changed</b> is set, it + * means that this descriptor is dirty. */ +STATIC void +service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed) + +{ + desc->next_upload_time = now; + + /* If the descriptor changed, clean up the old HSDirs list. We want to + * re-upload no matter what. */ + if (descriptor_changed) { + service_desc_clear_previous_hsdirs(desc); + } +} + +/* Update the given descriptor from the given service. The possible update + * actions includes: + * - Picking missing intro points if needed. + * - Incrementing the revision counter if needed. + */ +static void +update_service_descriptor(hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + tor_assert(desc->desc); + + num_intro_points = digest256map_size(desc->intro_points.map); + + /* Pick any missing introduction point(s). */ + if (num_intro_points < service->config.num_intro_points) { + unsigned int num_new_intro_points = pick_needed_intro_points(service, + desc); + if (num_new_intro_points != 0) { + log_info(LD_REND, "Service %s just picked %u intro points and wanted " + "%u for %s descriptor. It currently has %d intro " + "points. Launching ESTABLISH_INTRO circuit shortly.", + safe_str_client(service->onion_address), + num_new_intro_points, + service->config.num_intro_points - num_intro_points, + (desc == service->desc_current) ? "current" : "next", + num_intro_points); + /* We'll build those introduction point into the descriptor once we have + * confirmation that the circuits are opened and ready. However, + * indicate that this descriptor should be uploaded from now on. */ + service_desc_schedule_upload(desc, now, 1); + } + /* Were we able to pick all the intro points we needed? If not, we'll + * flag the descriptor that it's missing intro points because it + * couldn't pick enough which will trigger a descriptor upload. */ + if ((num_new_intro_points + num_intro_points) < + service->config.num_intro_points) { + desc->missing_intro_points = 1; + } + } +} + +/* Update descriptors for each service if needed. */ +STATIC void +update_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + /* We'll try to update each descriptor that is if certain conditions apply + * in order for the descriptor to be updated. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + update_service_descriptor(service, desc, now); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + +/* Return true iff the given intro point has expired that is it has been used + * for too long or we've reached our max seen INTRODUCE2 cell. */ +STATIC int +intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now) +{ + tor_assert(ip); + + if (ip->introduce2_count >= ip->introduce2_max) { + goto expired; + } + + if (ip->time_to_expire <= now) { + goto expired; + } + + /* Not expiring. */ + return 0; + expired: + return 1; +} + +/* Go over the given set of intro points for each service and remove any + * invalid ones. The conditions for removal are: + * + * - The node doesn't exists anymore (not in consensus) + * OR + * - The intro point maximum circuit retry count has been reached and no + * circuit can be found associated with it. + * OR + * - The intro point has expired and we should pick a new one. + * + * If an intro point is removed, the circuit (if any) is immediately close. + * If a circuit can't be found, the intro point is kept if it hasn't reached + * its maximum circuit retry value and thus should be retried. */ +static void +cleanup_intro_points(hs_service_t *service, time_t now) +{ + /* List of intro points to close. We can't mark the intro circuits for close + * in the modify loop because doing so calls + * hs_service_intro_circ_has_closed() which does a digest256map_get() on the + * intro points map (that we are iterating over). This can't be done in a + * single iteration after a MAP_DEL_CURRENT, the object will still be + * returned leading to a use-after-free. So, we close the circuits and free + * the intro points after the loop if any. */ + smartlist_t *ips_to_free = smartlist_new(); + + tor_assert(service); + + /* For both descriptors, cleanup the intro points. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Go over the current intro points we have, make sure they are still + * valid and remove any of them that aren't. */ + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *node = get_node_from_intro_point(ip); + int has_expired = intro_point_should_expire(ip, now); + + /* We cleanup an intro point if it has expired or if we do not know the + * node_t anymore (removed from our latest consensus) or if we've + * reached the maximum number of retry with a non existing circuit. */ + if (has_expired || node == NULL || + ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + log_info(LD_REND, "Intro point %s%s (retried: %u times). " + "Removing it.", + describe_intro_point(ip), + has_expired ? " has expired" : + (node == NULL) ? " fell off the consensus" : "", + ip->circuit_retries); + + /* We've retried too many times, remember it as a failed intro point + * so we don't pick it up again for INTRO_CIRC_RETRY_PERIOD sec. */ + if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + remember_failing_intro_point(ip, desc, approx_time()); + } + + /* Remove intro point from descriptor map and add it to the list of + * ips to free for which we'll also try to close the intro circuit. */ + MAP_DEL_CURRENT(key); + smartlist_add(ips_to_free, ip); + } + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; + + /* Go over the intro points to free and close their circuit if any. */ + SMARTLIST_FOREACH_BEGIN(ips_to_free, hs_service_intro_point_t *, ip) { + /* See if we need to close the intro point circuit as well */ + + /* XXX: Legacy code does NOT close circuits like this: it keeps the circuit + * open until a new descriptor is uploaded and then closed all expiring + * intro point circuit. Here, we close immediately and because we just + * discarded the intro point, a new one will be selected, a new descriptor + * created and uploaded. There is no difference to an attacker between the + * timing of a new consensus and intro point rotation (possibly?). */ + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + + /* Cleanup the intro point */ + service_intro_point_free(ip); + } SMARTLIST_FOREACH_END(ip); + + smartlist_free(ips_to_free); +} + +/* Set the next rotation time of the descriptors for the given service for the + * time now. */ +static void +set_rotation_time(hs_service_t *service, time_t now) +{ + time_t valid_after; + const networkstatus_t *ns = networkstatus_get_live_consensus(now); + if (ns) { + valid_after = ns->valid_after; + } else { + valid_after = now; + } + + tor_assert(service); + service->state.next_rotation_time = + sr_state_get_start_time_of_current_protocol_run(valid_after) + + sr_state_get_protocol_run_duration(); + + { + char fmt_time[ISO_TIME_LEN + 1]; + format_local_iso_time(fmt_time, service->state.next_rotation_time); + log_info(LD_REND, "Next descriptor rotation time set to %s for %s", + fmt_time, safe_str_client(service->onion_address)); + } +} + +/* Return true iff the service should rotate its descriptor. The time now is + * only used to fetch the live consensus and if none can be found, this + * returns false. */ +static unsigned int +should_rotate_descriptors(hs_service_t *service, time_t now) +{ + const networkstatus_t *ns; + + tor_assert(service); + + ns = networkstatus_get_live_consensus(now); + if (ns == NULL) { + goto no_rotation; + } + + if (ns->valid_after >= service->state.next_rotation_time) { + goto rotation; + } + + no_rotation: + return 0; + rotation: + return 1; +} + +/* Rotate the service descriptors of the given service. The current descriptor + * will be freed, the next one put in as the current and finally the next + * descriptor pointer is NULLified. */ +static void +rotate_service_descriptors(hs_service_t *service, time_t now) +{ + if (service->desc_current) { + /* Close all IP circuits for the descriptor. */ + close_intro_circuits(&service->desc_current->intro_points); + /* We don't need this one anymore, we won't serve any clients coming with + * this service descriptor. */ + service_descriptor_free(service->desc_current); + } + /* The next one become the current one and emptying the next will trigger + * a descriptor creation for it. */ + service->desc_current = service->desc_next; + service->desc_next = NULL; + + /* We've just rotated, set the next time for the rotation. */ + set_rotation_time(service, now); +} + +/* Rotate descriptors for each service if needed. A non existing current + * descriptor will trigger a descriptor build for the next time period. */ +STATIC void +rotate_all_descriptors(time_t now) +{ + /* XXX We rotate all our service descriptors at once. In the future it might + * be wise, to rotate service descriptors independently to hide that all + * those descriptors are on the same tor instance */ + + FOR_EACH_SERVICE_BEGIN(service) { + + /* Note for a service booting up: Both descriptors are NULL in that case + * so this function might return true if we are in the timeframe for a + * rotation leading to basically swapping two NULL pointers which is + * harmless. However, the side effect is that triggering a rotation will + * update the service state and avoid doing anymore rotations after the + * two descriptors have been built. */ + if (!should_rotate_descriptors(service, now)) { + continue; + } + + tor_assert(service->desc_current); + tor_assert(service->desc_next); + + log_info(LD_REND, "Time to rotate our descriptors (%p / %p) for %s", + service->desc_current, service->desc_next, + safe_str_client(service->onion_address)); + + rotate_service_descriptors(service, now); + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all our services are up + * to date and ready for the other scheduled events. This includes looking at + * the introduction points status and descriptor rotation time. */ +STATIC void +run_housekeeping_event(time_t now) +{ + /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are + * simply moving things around or removing unneeded elements. */ + + FOR_EACH_SERVICE_BEGIN(service) { + + /* If the service is starting off, set the rotation time. We can't do that + * at configure time because the get_options() needs to be set for setting + * that time that uses the voting interval. */ + if (service->state.next_rotation_time == 0) { + /* Set the next rotation time of the descriptors. If it's Oct 25th + * 23:47:00, the next rotation time is when the next SRV is computed + * which is at Oct 26th 00:00:00 that is in 13 minutes. */ + set_rotation_time(service, now); + } + + /* Cleanup invalid intro points from the service descriptor. */ + cleanup_intro_points(service, now); + + /* Remove expired failing intro point from the descriptor failed list. We + * reset them at each INTRO_CIRC_RETRY_PERIOD. */ + remove_expired_failing_intro(service, now); + + /* At this point, the service is now ready to go through the scheduled + * events guaranteeing a valid state. Intro points might be missing from + * the descriptors after the cleanup but the update/build process will + * make sure we pick those missing ones. */ + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all descriptors are up to + * date. Once this returns, each service descriptor needs to be considered for + * new introduction circuits and then for upload. */ +static void +run_build_descriptor_event(time_t now) +{ + /* For v2 services, this step happens in the upload event. */ + + /* Run v3+ events. */ + /* We start by rotating the descriptors only if needed. */ + rotate_all_descriptors(now); + + /* Then, we'll try to build new descriptors that we might need. The + * condition is that the next descriptor is non existing because it has + * been rotated or we just started up. */ + build_all_descriptors(now); + + /* Finally, we'll check if we should update the descriptors. Missing + * introduction points will be picked in this function which is useful for + * newly built descriptors. */ + update_all_descriptors(now); +} + +/* For the given service, launch any intro point circuits that could be + * needed. This considers every descriptor of the service. */ +static void +launch_intro_point_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* For both descriptors, try to launch any missing introduction point + * circuits using the current map. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Keep a ref on if we need a direct connection. We use this often. */ + unsigned int direct_conn = service->config.is_single_onion; + + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + extend_info_t *ei; + + /* Skip the intro point that already has an existing circuit + * (established or not). */ + if (hs_circ_service_get_intro_circ(ip)) { + continue; + } + + ei = get_extend_info_from_intro_point(ip, direct_conn); + if (ei == NULL) { + /* This is possible if we can get a node_t but not the extend info out + * of it. In this case, we remove the intro point and a new one will + * be picked at the next main loop callback. */ + MAP_DEL_CURRENT(key); + service_intro_point_free(ip); + continue; + } + + /* Launch a circuit to the intro point. */ + ip->circuit_retries++; + if (hs_circ_launch_intro_point(service, ip, ei) < 0) { + log_info(LD_REND, "Unable to launch intro circuit to node %s " + "for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + /* Intro point will be retried if possible after this. */ + } + extend_info_free(ei); + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* Don't try to build more than this many circuits before giving up for a + * while. Dynamically calculated based on the configured number of intro + * points for the given service and how many descriptor exists. The default + * use case of 3 introduction points and two descriptors will allow 28 + * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */ +static unsigned int +get_max_intro_circ_per_period(const hs_service_t *service) +{ + unsigned int count = 0; + unsigned int multiplier = 0; + unsigned int num_wanted_ip; + + tor_assert(service); + tor_assert(service->config.num_intro_points <= + HS_CONFIG_V3_MAX_INTRO_POINTS); + +/* For a testing network, allow to do it for the maximum amount so circuit + * creation and rotation and so on can actually be tested without limit. */ +#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING; + } + + num_wanted_ip = service->config.num_intro_points; + + /* The calculation is as follow. We have a number of intro points that we + * want configured as a torrc option (num_intro_points). We then add an + * extra value so we can launch multiple circuits at once and pick the + * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll + * pick 5 intros and launch 5 circuits. */ + count += (num_wanted_ip + get_intro_point_num_extra()); + + /* Then we add the number of retries that is possible to do for each intro + * point. If we want 3 intros, we'll allow 3 times the number of possible + * retry. */ + count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES); + + /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we + * have none. */ + multiplier += (service->desc_current) ? 1 : 0; + multiplier += (service->desc_next) ? 1 : 0; + + return (count * multiplier); +} + +/* For the given service, return 1 if the service is allowed to launch more + * introduction circuits else 0 if the maximum has been reached for the retry + * period of INTRO_CIRC_RETRY_PERIOD. */ +STATIC int +can_service_launch_intro_circuit(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* Consider the intro circuit retry period of the service. */ + if (now > (service->state.intro_circ_retry_started_time + + INTRO_CIRC_RETRY_PERIOD)) { + service->state.intro_circ_retry_started_time = now; + service->state.num_intro_circ_launched = 0; + goto allow; + } + /* Check if we can still launch more circuits in this period. */ + if (service->state.num_intro_circ_launched <= + get_max_intro_circ_per_period(service)) { + goto allow; + } + + /* Rate limit log that we've reached our circuit creation limit. */ + { + char *msg; + time_t elapsed_time = now - service->state.intro_circ_retry_started_time; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, now))) { + log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit " + "of %u per %d seconds. It launched %u circuits in " + "the last %ld seconds. Will retry in %ld seconds.", + safe_str_client(service->onion_address), + get_max_intro_circ_per_period(service), + INTRO_CIRC_RETRY_PERIOD, + service->state.num_intro_circ_launched, + (long int) elapsed_time, + (long int) (INTRO_CIRC_RETRY_PERIOD - elapsed_time)); + tor_free(msg); + } + } + + /* Not allow. */ + return 0; + allow: + return 1; +} + +/* Scheduled event run from the main loop. Make sure we have all the circuits + * we need for each service. */ +static void +run_build_circuit_event(time_t now) +{ + /* Make sure we can actually have enough information or able to build + * internal circuits as required by services. */ + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN || + !have_completed_a_circuit()) { + return; + } + + /* Run v2 check. */ + if (rend_num_services() > 0) { + rend_consider_services_intro_points(now); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* For introduction circuit, we need to make sure we don't stress too much + * circuit creation so make sure this service is respecting that limit. */ + if (can_service_launch_intro_circuit(service, now)) { + /* Launch intro point circuits if needed. */ + launch_intro_point_circuits(service); + /* Once the circuits have opened, we'll make sure to update the + * descriptor intro point list and cleanup any extraneous. */ + } + } FOR_EACH_SERVICE_END; +} + +/* Encode and sign the service descriptor desc and upload it to the given + * hidden service directory. This does nothing if PublishHidServDescriptors + * is false. */ +static void +upload_descriptor_to_hsdir(const hs_service_t *service, + hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char version_str[4] = {0}, *encoded_desc = NULL; + directory_request_t *dir_req; + hs_ident_dir_conn_t ident; + + tor_assert(service); + tor_assert(desc); + tor_assert(hsdir); + + memset(&ident, 0, sizeof(ident)); + + /* Let's avoid doing that if tor is configured to not publish. */ + if (!get_options()->PublishHidServDescriptors) { + log_info(LD_REND, "Service %s not publishing descriptor. " + "PublishHidServDescriptors is set to 1.", + safe_str_client(service->onion_address)); + goto end; + } + + /* First of all, we'll encode the descriptor. This should NEVER fail but + * just in case, let's make sure we have an actual usable descriptor. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto end; + } + + /* Setup the connection identifier. */ + hs_ident_dir_conn_init(&service->keys.identity_pk, &desc->blinded_kp.pubkey, + &ident); + + /* This is our resource when uploading which is used to construct the URL + * with the version number: "/tor/hs/<version>/publish". */ + tor_snprintf(version_str, sizeof(version_str), "%u", + service->config.version); + + /* Build the directory request for this HSDir. */ + dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC); + directory_request_set_routerstatus(dir_req, hsdir->rs); + directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS); + directory_request_set_resource(dir_req, version_str); + directory_request_set_payload(dir_req, encoded_desc, + strlen(encoded_desc)); + /* The ident object is copied over the directory connection object once + * the directory request is initiated. */ + directory_request_upload_set_hs_ident(dir_req, &ident); + + /* Initiate the directory request to the hsdir.*/ + directory_initiate_request(dir_req); + directory_request_free(dir_req); + + /* Add this node to previous_hsdirs list */ + service_desc_note_upload(desc, hsdir); + + /* Logging so we know where it was sent. */ + { + int is_next_desc = (service->desc_next == desc); + const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->store_second: + hsdir->hsdir_index->store_first; + log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64 + " initiated upload request to %s with index %s", + safe_str_client(service->onion_address), + (is_next_desc) ? "next" : "current", + desc->desc->plaintext_data.revision_counter, + safe_str_client(node_describe(hsdir)), + safe_str_client(hex_str((const char *) index, 32))); + } + + /* XXX: Inform control port of the upload event (#20699). */ + end: + tor_free(encoded_desc); + return; +} + +/** Return a newly-allocated string for our state file which contains revision + * counter information for <b>desc</b>. The format is: + * + * HidServRevCounter <blinded_pubkey> <rev_counter> + */ +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc) +{ + char *state_str = NULL; + char blinded_pubkey_b64[ED25519_BASE64_LEN+1]; + uint64_t rev_counter = desc->desc->plaintext_data.revision_counter; + const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey; + + /* Turn the blinded key into b64 so that we save it on state */ + tor_assert(blinded_pubkey); + if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) { + goto done; + } + + /* Format is: <blinded key> <rev counter> */ + tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter); + + log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!", + rev_counter, blinded_pubkey_b64); + + done: + return state_str; +} + +/** Update HS descriptor revision counters in our state by removing the old + * ones and writing down the ones that are currently active. */ +static void +update_revision_counters_in_state(void) +{ + config_line_t *lines = NULL; + config_line_t **nextline = &lines; + or_state_t *state = get_or_state(); + + /* Prepare our state structure with the rev counters */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We don't want to save zero counters */ + if (desc->desc->plaintext_data.revision_counter == 0) { + continue; + } + + *nextline = tor_malloc_zero(sizeof(config_line_t)); + (*nextline)->key = tor_strdup("HidServRevCounter"); + (*nextline)->value = encode_desc_rev_counter_for_state(desc); + nextline = &(*nextline)->next; + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* Remove the old rev counters, and replace them with the new ones */ + config_free_lines(state->HidServRevCounter); + state->HidServRevCounter = lines; + + /* Set the state as dirty since we just edited it */ + if (!get_options()->AvoidDiskWrites) { + or_state_mark_dirty(state, 0); + } +} + +/** Scan the string <b>state_line</b> for the revision counter of the service + * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the + * line is relevant to this service, and return the cached revision + * counter. Else set <b>service_found_out</b> to False. */ +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out) +{ + smartlist_t *items = NULL; + int ok; + ed25519_public_key_t pubkey_in_state; + uint64_t rev_counter = 0; + + tor_assert(service_found_out); + tor_assert(state_line); + tor_assert(blinded_pubkey); + + /* Assume that the line is not for this service */ + *service_found_out = 0; + + /* Start parsing the state line */ + items = smartlist_new(); + smartlist_split_string(items, state_line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 2) { + log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring."); + goto done; + } + + char *b64_key_str = smartlist_get(items, 0); + char *saved_rev_counter_str = smartlist_get(items, 1); + + /* Parse blinded key to check if it's for this hidden service */ + if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) { + log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring."); + goto done; + } + /* State line not for this hidden service */ + if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) { + goto done; + } + + rev_counter = tor_parse_uint64(saved_rev_counter_str, + 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring."); + goto done; + } + + /* Since we got this far, the line was for this service */ + *service_found_out = 1; + + log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64, + b64_key_str, rev_counter); + + done: + tor_assert(items); + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + + return rev_counter; +} + +/** Dig into our state file and find the current revision counter for the + * service with blinded key <b>blinded_pubkey</b>. If no revision counter is + * found, return 0. */ +static uint64_t +get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey) +{ + or_state_t *state = get_or_state(); + config_line_t *line; + + /* Set default value for rev counters (if not found) to 0 */ + uint64_t final_rev_counter = 0; + + for (line = state->HidServRevCounter ; line ; line = line->next) { + int service_found = 0; + uint64_t rev_counter = 0; + + tor_assert(!strcmp(line->key, "HidServRevCounter")); + + /* Scan all the HidServRevCounter lines till we find the line for this + service: */ + rev_counter = check_state_line_for_service_rev_counter(line->value, + blinded_pubkey, + &service_found); + if (service_found) { + final_rev_counter = rev_counter; + goto done; + } + } + + done: + return final_rev_counter; +} + +/** Update the value of the revision counter for <b>hs_desc</b> and save it on + our state file. */ +static void +increment_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + /* Increment the revision counter of <b>hs_desc</b> so the next update (which + * will trigger an upload) will have the right value. We do this at this + * stage to only do it once because a descriptor can have many updates before + * being uploaded. By doing it at upload, we are sure to only increment by 1 + * and thus avoid leaking how many operations we made on the descriptor from + * the previous one before uploading. */ + rev_counter++; + hs_desc->plaintext_data.revision_counter = rev_counter; + + update_revision_counters_in_state(); +} + +/** Set the revision counter in <b>hs_desc</b>, using the state file to find + * the current counter value if it exists. */ +static void +set_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + hs_desc->plaintext_data.revision_counter = rev_counter; +} + +/* Encode and sign the service descriptor desc and upload it to the + * responsible hidden service directories. If for_next_period is true, the set + * of directories are selected using the next hsdir_index. This does nothing + * if PublishHidServDescriptors is false. */ +STATIC void +upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + smartlist_t *responsible_dirs = NULL; + + tor_assert(service); + tor_assert(desc); + + /* We'll first cancel any directory request that are ongoing for this + * descriptor. It is possible that we can trigger multiple uploads in a + * short time frame which can lead to a race where the second upload arrives + * before the first one leading to a 400 malformed descriptor response from + * the directory. Closing all pending requests avoids that. */ + close_directory_connections(service, desc); + + /* Get our list of responsible HSDir. */ + responsible_dirs = smartlist_new(); + /* The parameter 0 means that we aren't a client so tell the function to use + * the spread store consensus paremeter. */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /** Clear list of previous hsdirs since we are about to upload to a new + * list. Let's keep it up to date. */ + service_desc_clear_previous_hsdirs(desc); + + /* For each responsible HSDir we have, initiate an upload command. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, + hsdir_rs) { + const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest); + /* Getting responsible hsdir implies that the node_t object exists for the + * routerstatus_t found in the consensus else we have a problem. */ + tor_assert(hsdir_node); + /* Upload this descriptor to the chosen directory. */ + upload_descriptor_to_hsdir(service, desc, hsdir_node); + } SMARTLIST_FOREACH_END(hsdir_rs); + + /* Set the next upload time for this descriptor. Even if we are configured + * to not upload, we still want to follow the right cycle of life for this + * descriptor. */ + desc->next_upload_time = + (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN, + HS_SERVICE_NEXT_UPLOAD_TIME_MAX)); + { + char fmt_next_time[ISO_TIME_LEN+1]; + format_local_iso_time(fmt_next_time, desc->next_upload_time); + log_debug(LD_REND, "Service %s set to upload a descriptor at %s", + safe_str_client(service->onion_address), fmt_next_time); + } + + /* Update the revision counter of this descriptor */ + increment_descriptor_revision_counter(desc->desc); + + smartlist_free(responsible_dirs); + return; +} + +/** The set of HSDirs have changed: check if the change affects our descriptor + * HSDir placement, and if it does, reupload the desc. */ +STATIC int +service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + int should_reupload = 0; + smartlist_t *responsible_dirs = smartlist_new(); + + /* No desc upload has happened yet: it will happen eventually */ + if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) { + goto done; + } + + /* Get list of responsible hsdirs */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /* Check if any new hsdirs have been added to the responsible hsdirs set: + * Iterate over the list of new hsdirs, and reupload if any of them is not + * present in the list of previous hsdirs. + */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) { + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir_rs->identity_digest); + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + should_reupload = 1; + break; + } + } SMARTLIST_FOREACH_END(hsdir_rs); + + done: + smartlist_free(responsible_dirs); + + return should_reupload; +} + +/* Return 1 if the given descriptor from the given service can be uploaded + * else return 0 if it can not. */ +static int +should_service_upload_descriptor(const hs_service_t *service, + const hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + + /* If this descriptors has missing intro points that is that it couldn't get + * them all when it was time to pick them, it means that we should upload + * instead of waiting an arbitrary amount of time breaking the service. + * Else, if we have no missing intro points, we use the value taken from the + * service configuration. */ + if (desc->missing_intro_points) { + num_intro_points = digest256map_size(desc->intro_points.map); + } else { + num_intro_points = service->config.num_intro_points; + } + + /* This means we tried to pick intro points but couldn't get any so do not + * upload descriptor in this case. We need at least one for the service to + * be reachable. */ + if (desc->missing_intro_points && num_intro_points == 0) { + goto cannot; + } + + /* Check if all our introduction circuit have been established for all the + * intro points we have selected. */ + if (count_desc_circuit_established(desc) != num_intro_points) { + goto cannot; + } + + /* Is it the right time to upload? */ + if (desc->next_upload_time > now) { + goto cannot; + } + + /* Don't upload desc if we don't have a live consensus */ + if (!networkstatus_get_live_consensus(now)) { + goto cannot; + } + + /* Do we know enough router descriptors to have adequate vision of the HSDir + hash ring? */ + if (!router_have_minimum_dir_info()) { + goto cannot; + } + + /* Can upload! */ + return 1; + cannot: + return 0; +} + +/* Scheduled event run from the main loop. Try to upload the descriptor for + * each service. */ +STATIC void +run_upload_descriptor_event(time_t now) +{ + /* v2 services use the same function for descriptor creation and upload so + * we do everything here because the intro circuits were checked before. */ + if (rend_num_services() > 0) { + rend_consider_services_upload(now); + rend_consider_descriptor_republication(); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* If we were asked to re-examine the hash ring, and it changed, then + schedule an upload */ + if (consider_republishing_hs_descriptors && + service_desc_hsdirs_changed(service, desc)) { + service_desc_schedule_upload(desc, now, 0); + } + + /* Can this descriptor be uploaded? */ + if (!should_service_upload_descriptor(service, desc, now)) { + continue; + } + + log_info(LD_REND, "Initiating upload for hidden service %s descriptor " + "for service %s with %u/%u introduction points%s.", + (desc == service->desc_current) ? "current" : "next", + safe_str_client(service->onion_address), + digest256map_size(desc->intro_points.map), + service->config.num_intro_points, + (desc->missing_intro_points) ? " (couldn't pick more)" : ""); + + /* At this point, we have to upload the descriptor so start by building + * the intro points descriptor section which we are now sure to be + * accurate because all circuits have been established. */ + build_desc_intro_points(service, desc, now); + + upload_descriptor_to_all(service, desc); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* We are done considering whether to republish rend descriptors */ + consider_republishing_hs_descriptors = 0; +} + +/* Called when the introduction point circuit is done building and ready to be + * used. */ +static void +service_intro_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + /* Let's do some basic sanity checking of the circ state */ + if (BUG(!circ->cpath)) { + return; + } + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + return; + } + if (BUG(!circ->hs_ident)) { + return; + } + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + log_warn(LD_REND, "Unknown introduction point auth key on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* We can't have an IP object without a descriptor. */ + tor_assert(desc); + + if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) { + /* Getting here means that the circuit has been re-purposed because we + * have enough intro circuit opened. Remove the IP from the service. */ + service_intro_point_remove(service, ip); + service_intro_point_free(ip); + } + + goto done; + + err: + /* Close circuit, we can't use it. */ + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* Called when a rendezvous circuit is done building and ready to be used. */ +static void +service_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(circ->cpath); + /* Getting here means this is a v3 rendezvous circuit. */ + tor_assert(circ->hs_ident); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(circ); + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the rendezvous " + "circuit %u with cookie %s. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN)); + goto err; + } + + /* If the cell can't be sent, the circuit will be closed within this + * function. */ + hs_circ_service_rp_has_opened(service, circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just + * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given + * introduction circuit. Return 0 on success else a negative value. */ +static int +service_handle_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + /* We need the service and intro point for this cell. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, NULL); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_REND, "Introduction circuit established without an intro " + "point object on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. On success, the ip object and circuit purpose is updated to + * reflect the fact that the introduction circuit is established. */ + if (hs_circ_handle_intro_established(service, ip, circ, payload, + payload_len) < 0) { + goto err; + } + + /* Flag that we have an established circuit for this intro point. This value + * is what indicates the upload scheduled event if we are ready to build the + * intro point into the descriptor and upload. */ + ip->circuit_established = 1; + + log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell " + "on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + return 0; + + err: + return -1; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the cell and return 0 on success else a negative value. */ +static int +service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO); + + /* We'll need every object associated with this circuit. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_BUG, "Unknown service identity key %s when handling " + "an INTRODUCE2 cell on circuit %u", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_BUG, "Unknown introduction auth key when handling " + "an INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* If we have an IP object, we MUST have a descriptor object. */ + tor_assert(desc); + + /* The following will parse, decode and launch the rendezvous point circuit. + * Both current and legacy cells are handled. */ + if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential, + payload, payload_len) < 0) { + goto err; + } + + return 0; + err: + return -1; +} + +/* Add to list every filename used by service. This is used by the sandbox + * subsystem. */ +static void +service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) +{ + const char *s_dir; + char fname[128] = {0}; + + tor_assert(service); + tor_assert(list); + + /* Ease our life. */ + s_dir = service->config.directory_path; + /* The hostname file. */ + smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname)); + /* The key files splitted in two. */ + tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); + tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return the number of service we have configured and usable. */ +unsigned int +hs_service_get_num_services(void) +{ + if (hs_service_map == NULL) { + return 0; + } + return HT_SIZE(hs_service_map); +} + +/* Called once an introduction circuit is closed. If the circuit doesn't have + * a v3 identifier, it is ignored. */ +void +hs_service_intro_circ_has_closed(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + if (circ->hs_ident == NULL) { + /* This is not a v3 circuit, ignore. */ + goto end; + } + + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on intro circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + goto end; + } + if (ip == NULL) { + /* The introduction point object has already been removed probably by our + * cleanup process so ignore. */ + goto end; + } + /* Can't have an intro point object without a descriptor. */ + tor_assert(desc); + + /* Circuit disappeared so make sure the intro point is updated. By + * keeping the object in the descriptor, we'll be able to retry. */ + ip->circuit_established = 0; + + end: + return; +} + +/* Given conn, a rendezvous edge connection acting as an exit stream, look up + * the hidden service for the circuit circ, and look up the port and address + * based on the connection port. Assign the actual connection address. + * + * Return 0 on success. Return -1 on failure and the caller should NOT close + * the circuit. Return -2 on failure and the caller MUST close the circuit for + * security reasons. */ +int +hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(conn); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(circ->hs_ident); + + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on rendezvous circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + /* We want the caller to close the circuit because it's not a valid + * service so no danger. Attempting to bruteforce the entire key space by + * opening circuits to learn which service is being hosted here is + * impractical. */ + goto err_close; + } + + /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if + * this circuit will exceed the limit. */ + if (service->config.max_streams_per_rdv_circuit > 0 && + (circ->hs_ident->num_rdv_streams >= + service->config.max_streams_per_rdv_circuit)) { +#define MAX_STREAM_WARN_INTERVAL 600 + static struct ratelim_t stream_ratelim = + RATELIM_INIT(MAX_STREAM_WARN_INTERVAL); + log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND, + "Maximum streams per circuit limit reached on " + "rendezvous circuit %u for service %s. Circuit has " + "%" PRIu64 " out of %" PRIu64 " streams. %s.", + TO_CIRCUIT(circ)->n_circ_id, + service->onion_address, + circ->hs_ident->num_rdv_streams, + service->config.max_streams_per_rdv_circuit, + service->config.max_streams_close_circuit ? + "Closing circuit" : "Ignoring open stream request"); + if (service->config.max_streams_close_circuit) { + /* Service explicitly configured to close immediately. */ + goto err_close; + } + /* Exceeding the limit makes tor silently ignore the stream creation + * request and keep the circuit open. */ + goto err_no_close; + } + + /* Find a virtual port of that service mathcing the one in the connection if + * succesful, set the address in the connection. */ + if (hs_set_conn_addr_port(service->config.ports, conn) < 0) { + log_info(LD_REND, "No virtual port mapping exists for port %d for " + "hidden service %s.", + TO_CONN(conn)->port, service->onion_address); + if (service->config.allow_unknown_ports) { + /* Service explicitly allow connection to unknown ports so close right + * away because we do not care about port mapping. */ + goto err_close; + } + /* If the service didn't explicitly allow it, we do NOT close the circuit + * here to raise the bar in terms of performance for port mapping. */ + goto err_no_close; + } + + /* Success. */ + return 0; + err_close: + /* Indicate the caller that the circuit should be closed. */ + return -2; + err_no_close: + /* Indicate the caller to NOT close the circuit. */ + return -1; +} + +/* Add to file_list every filename used by a configured hidden service, and to + * dir_list every directory path used by a configured hidden service. This is + * used by the sandbox subsystem to whitelist those. */ +void +hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list) +{ + tor_assert(file_list); + tor_assert(dir_list); + + /* Add files and dirs for legacy services. */ + rend_services_add_filenames_to_lists(file_list, dir_list); + + /* Add files and dirs for v3+. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* Skip ephemeral service, they don't touch the disk. */ + if (service->config.is_ephemeral) { + continue; + } + service_add_fnames_to_list(service, file_list); + smartlist_add_strdup(dir_list, service->config.directory_path); + } FOR_EACH_DESCRIPTOR_END; +} + +/* Called when our internal view of the directory has changed. We might have + * received a new batch of descriptors which might affect the shape of the + * HSDir hash ring. Signal that we should reexamine the hash ring and + * re-upload our HS descriptors if needed. */ +void +hs_service_dir_info_changed(void) +{ + if (hs_service_get_num_services() > 0) { + /* New directory information usually goes every consensus so rate limit + * every 30 minutes to not be too conservative. */ + static struct ratelim_t dir_info_changed_ratelim = RATELIM_INIT(30 * 60); + log_fn_ratelim(&dir_info_changed_ratelim, LOG_INFO, LD_REND, + "New dirinfo arrived: consider reuploading descriptor"); + consider_republishing_hs_descriptors = 1; + } +} + +/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and + * launch a circuit to the rendezvous point. */ +int +hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Do some initial validation and logging before we parse the cell */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto done; + } + + if (circ->hs_ident) { + ret = service_handle_introduce2(circ, payload, payload_len); + } else { + ret = rend_service_receive_introduction(circ, payload, payload_len); + } + + done: + return ret; +} + +/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an + * established introduction point. Return 0 on success else a negative value + * and the circuit is closed. */ +int +hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto err; + } + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + if (circ->hs_ident) { + ret = service_handle_intro_established(circ, payload, payload_len); + } else { + ret = rend_service_intro_established(circ, payload, payload_len); + } + + if (ret < 0) { + goto err; + } + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + +/* Called when any kind of hidden service circuit is done building thus + * opened. This is the entry point from the circuit subsystem. */ +void +hs_service_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: + if (circ->hs_ident) { + service_intro_circ_has_opened(circ); + } else { + rend_service_intro_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_S_CONNECT_REND: + if (circ->hs_ident) { + service_rendezvous_circ_has_opened(circ); + } else { + rend_service_rendezvous_has_opened(circ); + } + break; + default: + tor_assert(0); + } +} + +/* Load and/or generate keys for all onion services including the client + * authorization if any. Return 0 on success, -1 on failure. */ +int +hs_service_load_all_keys(void) +{ + /* Load v2 service keys if we have v2. */ + if (rend_num_services() != 0) { + if (rend_service_load_all_keys(NULL) < 0) { + goto err; + } + } + + /* Load or/and generate them for v3+. */ + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) { + /* Ignore ephemeral service, they already have their keys set. */ + if (service->config.is_ephemeral) { + continue; + } + log_info(LD_REND, "Loading v3 onion service keys from %s", + service_escaped_dir(service)); + if (load_service_keys(service) < 0) { + goto err; + } + /* XXX: Load/Generate client authorization keys. (#20700) */ + } SMARTLIST_FOREACH_END(service); + + /* Final step, the staging list contains service in a quiescent state that + * is ready to be used. Register them to the global map. Once this is over, + * the staging list will be cleaned up. */ + register_all_services(); + + /* All keys have been loaded successfully. */ + return 0; + err: + return -1; +} + +/* Put all service object in the given service list. After this, the caller + * looses ownership of every elements in the list and responsible to free the + * list pointer. */ +void +hs_service_stage_services(const smartlist_t *service_list) +{ + tor_assert(service_list); + /* This list is freed at registration time but this function can be called + * multiple time. */ + if (hs_service_staging_list == NULL) { + hs_service_staging_list = smartlist_new(); + } + /* Add all service object to our staging list. Caller is responsible for + * freeing the service_list. */ + smartlist_add_all(hs_service_staging_list, service_list); +} + +/* Allocate and initilize a service object. The service configuration will + * contain the default values. Return the newly allocated object pointer. This + * function can't fail. */ +hs_service_t * +hs_service_new(const or_options_t *options) +{ + hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t)); + /* Set default configuration value. */ + set_service_default_config(&service->config, options); + /* Set the default service version. */ + service->config.version = HS_SERVICE_DEFAULT_VERSION; + /* Allocate the CLIENT_PK replay cache in service state. */ + service->state.replay_cache_rend_cookie = + replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL); + + return service; +} + +/* Free the given <b>service</b> object and all its content. This function + * also takes care of wiping service keys from memory. It is safe to pass a + * NULL pointer. */ +void +hs_service_free(hs_service_t *service) +{ + if (service == NULL) { + return; + } + + /* Free descriptors. Go over both descriptor with this loop. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + service_descriptor_free(desc); + } FOR_EACH_DESCRIPTOR_END; + + /* Free service configuration. */ + service_clear_config(&service->config); + + /* Free replay cache from state. */ + if (service->state.replay_cache_rend_cookie) { + replaycache_free(service->state.replay_cache_rend_cookie); + } + + /* Wipe service keys. */ + memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); + + tor_free(service); +} + +/* Periodic callback. Entry point from the main loop to the HS service + * subsystem. This is call every second. This is skipped if tor can't build a + * circuit or the network is disabled. */ +void +hs_service_run_scheduled_events(time_t now) +{ + /* First thing we'll do here is to make sure our services are in a + * quiescent state for the scheduled events. */ + run_housekeeping_event(now); + + /* Order matters here. We first make sure the descriptor object for each + * service contains the latest data. Once done, we check if we need to open + * new introduction circuit. Finally, we try to upload the descriptor for + * each service. */ + + /* Make sure descriptors are up to date. */ + run_build_descriptor_event(now); + /* Make sure services have enough circuits. */ + run_build_circuit_event(now); + /* Upload the descriptors if needed/possible. */ + run_upload_descriptor_event(now); +} + +/* Initialize the service HS subsystem. */ +void +hs_service_init(void) +{ + /* Should never be called twice. */ + tor_assert(!hs_service_map); + tor_assert(!hs_service_staging_list); + + /* v2 specific. */ + rend_service_init(); + + hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht)); + HT_INIT(hs_service_ht, hs_service_map); + + hs_service_staging_list = smartlist_new(); +} + +/* Release all global storage of the hidden service subsystem. */ +void +hs_service_free_all(void) +{ + rend_service_free_all(); + service_free_all(); +} + +#ifdef TOR_UNIT_TESTS + +/* Return the global service map size. Only used by unit test. */ +STATIC unsigned int +get_hs_service_map_size(void) +{ + return HT_SIZE(hs_service_map); +} + +/* Return the staging list size. Only used by unit test. */ +STATIC int +get_hs_service_staging_list_size(void) +{ + return smartlist_len(hs_service_staging_list); +} + +STATIC hs_service_ht * +get_hs_service_map(void) +{ + return hs_service_map; +} + +STATIC hs_service_t * +get_first_service(void) +{ + hs_service_t **obj = HT_START(hs_service_ht, hs_service_map); + if (obj == NULL) { + return NULL; + } + return *obj; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 3302592762..ed1053d850 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -3,25 +3,352 @@ /** * \file hs_service.h - * \brief Header file for hs_service.c. + * \brief Header file containing service data for the HS subsytem. **/ #ifndef TOR_HS_SERVICE_H #define TOR_HS_SERVICE_H -#include "or.h" +#include "crypto_curve25519.h" +#include "crypto_ed25519.h" +#include "replaycache.h" + +#include "hs_common.h" +#include "hs_descriptor.h" +#include "hs_ident.h" +#include "hs_intropoint.h" + +/* Trunnel */ #include "hs/cell_establish_intro.h" -/* These functions are only used by unit tests and we need to expose them else - * hs_service.o ends up with no symbols in libor.a which makes clang throw a - * warning at compile time. See #21825. */ +/* When loading and configuring a service, this is the default version it will + * be configured for as it is possible that no HiddenServiceVersion is + * present. */ +#define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO + +/* As described in the specification, service publishes their next descriptor + * at a random time between those two values (in seconds). */ +#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60) +#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60) + +/* Service side introduction point. */ +typedef struct hs_service_intro_point_t { + /* Top level intropoint "shared" data between client/service. */ + hs_intropoint_t base; + + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + + /* Authentication keypair used to create the authentication certificate + * which is published in the descriptor. */ + ed25519_keypair_t auth_key_kp; + + /* Encryption keypair for the "ntor" type. */ + curve25519_keypair_t enc_key_kp; + + /* Legacy key if that intro point doesn't support v3. This should be used if + * the base object legacy flag is set. */ + crypto_pk_t *legacy_key; + + /* Amount of INTRODUCE2 cell accepted from this intro point. */ + uint64_t introduce2_count; + + /* Maximum number of INTRODUCE2 cell this intro point should accept. */ + uint64_t introduce2_max; + + /* The time at which this intro point should expire and stop being used. */ + time_t time_to_expire; + + /* The amount of circuit creation we've made to this intro point. This is + * incremented every time we do a circuit relaunch on this intro point which + * is triggered when the circuit dies but the node is still in the + * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */ + uint32_t circuit_retries; + + /* Set if this intro point has an established circuit. */ + unsigned int circuit_established : 1; + + /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the + * circuit associated with this intro point has received. This is used to + * prevent replay attacks. */ + replaycache_t *replay_cache; +} hs_service_intro_point_t; + +/* Object handling introduction points of a service. */ +typedef struct hs_service_intropoints_t { + /* The time at which we've started our retry period to build circuits. We + * don't want to stress circuit creation so we can only retry for a certain + * time and then after we stop and wait. */ + time_t retry_period_started; + + /* Number of circuit we've launched during a single retry period. */ + unsigned int num_circuits_launched; + + /* Contains the current hs_service_intro_point_t objects indexed by + * authentication public key. */ + digest256map_t *map; + + /* Contains node's identity key digest that were introduction point for this + * descriptor but were retried to many times. We keep those so we avoid + * re-picking them over and over for a circuit retry period. + * XXX: Once we have #22173, change this to only use ed25519 identity. */ + digestmap_t *failed_id; +} hs_service_intropoints_t; + +/* Representation of a service descriptor. */ +typedef struct hs_service_descriptor_t { + /* Decoded descriptor. This object is used for encoding when the service + * publishes the descriptor. */ + hs_descriptor_t *desc; + + /* Descriptor signing keypair. */ + ed25519_keypair_t signing_kp; + + /* Blinded keypair derived from the master identity public key. */ + ed25519_keypair_t blinded_kp; + + /* When is the next time when we should upload the descriptor. */ + time_t next_upload_time; + + /* Introduction points assign to this descriptor which contains + * hs_service_intropoints_t object indexed by authentication key (the RSA + * key if the node is legacy). */ + hs_service_intropoints_t intro_points; + + /* The time period number this descriptor has been created for. */ + uint64_t time_period_num; + + /* True iff we have missing intro points for this descriptor because we + * couldn't pick any nodes. */ + unsigned int missing_intro_points : 1; + + /** List of the responsible HSDirs (their b64ed identity digest) last time we + * uploaded this descriptor. If the set of responsible HSDirs is different + * from this list, this means we received new dirinfo and we need to + * reupload our descriptor. */ + smartlist_t *previous_hsdirs; +} hs_service_descriptor_t; + +/* Service key material. */ +typedef struct hs_service_keys_t { + /* Master identify public key. */ + ed25519_public_key_t identity_pk; + /* Master identity private key. */ + ed25519_secret_key_t identity_sk; + /* True iff the key is kept offline which means the identity_sk MUST not be + * used in that case. */ + unsigned int is_identify_key_offline : 1; +} hs_service_keys_t; + +/* Service configuration. The following are set from the torrc options either + * set by the configuration file or by the control port. Nothing else should + * change those values. */ +typedef struct hs_service_config_t { + /* Protocol version of the service. Specified by HiddenServiceVersion + * option. */ + uint32_t version; + + /* List of rend_service_port_config_t */ + smartlist_t *ports; + + /* Path on the filesystem where the service persistent data is stored. NULL + * if the service is ephemeral. Specified by HiddenServiceDir option. */ + char *directory_path; + + /* The maximum number of simultaneous streams per rendezvous circuit that + * are allowed to be created. No limit if 0. Specified by + * HiddenServiceMaxStreams option. */ + uint64_t max_streams_per_rdv_circuit; + + /* If true, we close circuits that exceed the max_streams_per_rdv_circuit + * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */ + unsigned int max_streams_close_circuit : 1; + + /* How many introduction points this service has. Specified by + * HiddenServiceNumIntroductionPoints option. */ + unsigned int num_intro_points; + + /* True iff we allow request made on unknown ports. Specified by + * HiddenServiceAllowUnknownPorts option. */ + unsigned int allow_unknown_ports : 1; + + /* If true, this service is a Single Onion Service. Specified by + * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */ + unsigned int is_single_onion : 1; + + /* If true, allow group read permissions on the directory_path. Specified by + * HiddenServiceDirGroupReadable option. */ + unsigned int dir_group_readable : 1; + + /* Is this service ephemeral? */ + unsigned int is_ephemeral : 1; +} hs_service_config_t; + +/* Service state. */ +typedef struct hs_service_state_t { + /* The time at which we've started our retry period to build circuits. We + * don't want to stress circuit creation so we can only retry for a certain + * time and then after we stop and wait. */ + time_t intro_circ_retry_started_time; + + /* Number of circuit we've launched during a single retry period. This + * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */ + unsigned int num_intro_circ_launched; + + /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect + * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point + * through two or more different introduction points; when they do, this + * keeps us from launching multiple simultaneous attempts to connect to the + * same rend point. */ + replaycache_t *replay_cache_rend_cookie; + + /* When is the next time we should rotate our descriptors. This is has to be + * done at the start time of the next SRV protocol run. */ + time_t next_rotation_time; +} hs_service_state_t; + +/* Representation of a service running on this tor instance. */ +typedef struct hs_service_t { + /* Onion address base32 encoded and NUL terminated. We keep it for logging + * purposes so we don't have to build it everytime. */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + /* Hashtable node: use to look up the service by its master public identity + * key in the service global map. */ + HT_ENTRY(hs_service_t) hs_service_node; + + /* Service state which contains various flags and counters. */ + hs_service_state_t state; + + /* Key material of the service. */ + hs_service_keys_t keys; + + /* Configuration of the service. */ + hs_service_config_t config; + + /* Current descriptor. */ + hs_service_descriptor_t *desc_current; + /* Next descriptor. */ + hs_service_descriptor_t *desc_next; + + /* XXX: Credential (client auth.) #20700. */ + +} hs_service_t; + +/* For the service global hash map, we define a specific type for it which + * will make it safe to use and specific to some controlled parameters such as + * the hashing function and how to compare services. */ +typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht; + +/* API */ + +/* Global initializer and cleanup function. */ +void hs_service_init(void); +void hs_service_free_all(void); + +/* Service new/free functions. */ +hs_service_t *hs_service_new(const or_options_t *options); +void hs_service_free(hs_service_t *service); + +unsigned int hs_service_get_num_services(void); +void hs_service_stage_services(const smartlist_t *service_list); +int hs_service_load_all_keys(void); +void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list); +int hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn); + +void hs_service_dir_info_changed(void); +void hs_service_run_scheduled_events(time_t now); +void hs_service_circuit_has_opened(origin_circuit_t *circ); +int hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_service_receive_introduce2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + +void hs_service_intro_circ_has_closed(origin_circuit_t *circ); + +#ifdef HS_SERVICE_PRIVATE + +#ifdef TOR_UNIT_TESTS + +/* Useful getters for unit tests. */ +STATIC unsigned int get_hs_service_map_size(void); +STATIC int get_hs_service_staging_list_size(void); +STATIC hs_service_ht *get_hs_service_map(void); +STATIC hs_service_t *get_first_service(void); + +/* Service accessors. */ +STATIC hs_service_t *find_service(hs_service_ht *map, + const ed25519_public_key_t *pk); +STATIC void remove_service(hs_service_ht *map, hs_service_t *service); +STATIC int register_service(hs_service_ht *map, hs_service_t *service); +/* Service introduction point functions. */ +STATIC hs_service_intro_point_t *service_intro_point_new( + const extend_info_t *ei, + unsigned int is_legacy); +STATIC void service_intro_point_free(hs_service_intro_point_t *ip); +STATIC void service_intro_point_add(digest256map_t *map, + hs_service_intro_point_t *ip); +STATIC void service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip); +STATIC hs_service_intro_point_t *service_intro_point_find( + const hs_service_t *service, + const ed25519_public_key_t *auth_key); +STATIC hs_service_intro_point_t *service_intro_point_find_by_ident( + const hs_service_t *service, + const hs_ident_circuit_t *ident); +/* Service descriptor functions. */ +STATIC hs_service_descriptor_t *service_descriptor_new(void); +STATIC hs_service_descriptor_t *service_desc_find_by_intro( + const hs_service_t *service, + const hs_service_intro_point_t *ip); +/* Helper functions. */ +STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, + hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc); +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip); +STATIC int can_service_launch_intro_circuit(hs_service_t *service, + time_t now); +STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now); +STATIC void run_housekeeping_event(time_t now); +STATIC void rotate_all_descriptors(time_t now); +STATIC void build_all_descriptors(time_t now); +STATIC void update_all_descriptors(time_t now); +STATIC void run_upload_descriptor_event(time_t now); + +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc); + +STATIC void service_descriptor_free(hs_service_descriptor_t *desc); + +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out); + +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_); + +STATIC void upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc); + +STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed); + +STATIC int service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc); + +#endif /* defined(TOR_UNIT_TESTS) */ -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len); -ssize_t -get_establish_intro_payload(uint8_t *buf, size_t buf_len, - const trn_cell_establish_intro_t *cell); +#endif /* defined(HS_SERVICE_PRIVATE) */ -#endif /* TOR_HS_SERVICE_H */ +#endif /* !defined(TOR_HS_SERVICE_H) */ diff --git a/src/or/include.am b/src/or/include.am index 1ef5afa013..7216aba9af 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -20,7 +20,6 @@ EXTRA_DIST+= src/or/ntmain.c src/or/Makefile.nmake LIBTOR_A_SOURCES = \ src/or/addressmap.c \ src/or/bridges.c \ - src/or/buffers.c \ src/or/channel.c \ src/or/channelpadding.c \ src/or/channeltls.c \ @@ -50,16 +49,21 @@ LIBTOR_A_SOURCES = \ src/or/dnsserv.c \ src/or/fp_pair.c \ src/or/geoip.c \ - src/or/hs_intropoint.c \ - src/or/hs_circuitmap.c \ - src/or/hs_ntor.c \ - src/or/hs_service.c \ src/or/entrynodes.c \ src/or/ext_orport.c \ src/or/hibernate.c \ src/or/hs_cache.c \ + src/or/hs_cell.c \ + src/or/hs_circuit.c \ + src/or/hs_circuitmap.c \ + src/or/hs_client.c \ src/or/hs_common.c \ + src/or/hs_config.c \ src/or/hs_descriptor.c \ + src/or/hs_ident.c \ + src/or/hs_intropoint.c \ + src/or/hs_ntor.c \ + src/or/hs_service.c \ src/or/keypin.c \ src/or/main.c \ src/or/microdesc.c \ @@ -74,6 +78,11 @@ LIBTOR_A_SOURCES = \ src/or/parsecommon.c \ src/or/periodic.c \ src/or/protover.c \ + src/or/proto_cell.c \ + src/or/proto_control0.c \ + src/or/proto_ext_or.c \ + src/or/proto_http.c \ + src/or/proto_socks.c \ src/or/policies.c \ src/or/reasons.c \ src/or/relay.c \ @@ -90,6 +99,8 @@ LIBTOR_A_SOURCES = \ src/or/routerparse.c \ src/or/routerset.c \ src/or/scheduler.c \ + src/or/scheduler_kist.c \ + src/or/scheduler_vanilla.c \ src/or/statefile.c \ src/or/status.c \ src/or/torcert.c \ @@ -122,10 +133,11 @@ src_or_tor_LDADD = src/or/libtor.a src/common/libor.a src/common/libor-ctime.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \ - @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ if COVERAGE_ENABLED src_or_tor_cov_SOURCES = src/or/tor_main.c @@ -144,7 +156,6 @@ endif ORHEADERS = \ src/or/addressmap.h \ src/or/bridges.h \ - src/or/buffers.h \ src/or/channel.h \ src/or/channelpadding.h \ src/or/channeltls.h \ @@ -180,12 +191,17 @@ ORHEADERS = \ src/or/entrynodes.h \ src/or/hibernate.h \ src/or/hs_cache.h \ + src/or/hs_cell.h \ + src/or/hs_config.h \ + src/or/hs_circuit.h \ + src/or/hs_circuitmap.h \ + src/or/hs_client.h \ src/or/hs_common.h \ src/or/hs_descriptor.h \ - src/or/hs_intropoint.h \ - src/or/hs_circuitmap.h \ - src/or/hs_ntor.h \ - src/or/hs_service.h \ + src/or/hs_ident.h \ + src/or/hs_intropoint.h \ + src/or/hs_ntor.h \ + src/or/hs_service.h \ src/or/keypin.h \ src/or/main.h \ src/or/microdesc.h \ @@ -204,6 +220,11 @@ ORHEADERS = \ src/or/periodic.h \ src/or/policies.h \ src/or/protover.h \ + src/or/proto_cell.h \ + src/or/proto_control0.h \ + src/or/proto_ext_or.h \ + src/or/proto_http.h \ + src/or/proto_socks.h \ src/or/reasons.h \ src/or/relay.h \ src/or/rendcache.h \ diff --git a/src/or/keypin.h b/src/or/keypin.h index 2564f5befb..fbb77e5c35 100644 --- a/src/or/keypin.h +++ b/src/or/keypin.h @@ -41,7 +41,7 @@ STATIC keypin_ent_t * keypin_parse_journal_line(const char *cp); STATIC int keypin_load_journal_impl(const char *data, size_t size); MOCK_DECL(STATIC void, keypin_add_entry_to_map, (keypin_ent_t *ent)); -#endif +#endif /* defined(KEYPIN_PRIVATE) */ -#endif +#endif /* !defined(TOR_KEYPIN_H) */ diff --git a/src/or/main.c b/src/or/main.c index 0d91803d4e..c340e4128b 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -52,6 +52,7 @@ #include "backtrace.h" #include "bridges.h" #include "buffers.h" +#include "buffers_tls.h" #include "channel.h" #include "channeltls.h" #include "channelpadding.h" @@ -80,6 +81,7 @@ #include "hibernate.h" #include "hs_cache.h" #include "hs_circuitmap.h" +#include "hs_client.h" #include "keypin.h" #include "main.h" #include "microdesc.h" @@ -120,9 +122,9 @@ * Coverity. Here's a kludge to unconfuse it. */ # define __INCLUDE_LEVEL__ 2 -# endif +#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ #include <systemd/sd-daemon.h> -#endif +#endif /* defined(HAVE_SYSTEMD) */ void evdns_shutdown(int); @@ -740,7 +742,7 @@ conn_read_callback(evutil_socket_t fd, short event, void *_conn) "(fd %d); removing", conn_type_to_string(conn->type), (int)conn->s); tor_fragile_assert(); -#endif +#endif /* !defined(_WIN32) */ if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); connection_mark_for_close(conn); @@ -835,7 +837,7 @@ conn_close_if_marked(int i) (int)conn->outbuf_flushlen, conn->marked_for_close_file, conn->marked_for_close); if (conn->linked_conn) { - retval = move_buf_to_buf(conn->linked_conn->inbuf, conn->outbuf, + retval = buf_move_to_buf(conn->linked_conn->inbuf, conn->outbuf, &conn->outbuf_flushlen); if (retval >= 0) { /* The linked conn will notice that it has data when it notices that @@ -849,12 +851,13 @@ conn_close_if_marked(int i) connection_wants_to_flush(conn)); } else if (connection_speaks_cells(conn)) { if (conn->state == OR_CONN_STATE_OPEN) { - retval = flush_buf_tls(TO_OR_CONN(conn)->tls, conn->outbuf, sz, + retval = buf_flush_to_tls(conn->outbuf, TO_OR_CONN(conn)->tls, sz, &conn->outbuf_flushlen); } else retval = -1; /* never flush non-open broken tls connections */ } else { - retval = flush_buf(conn->s, conn->outbuf, sz, &conn->outbuf_flushlen); + retval = buf_flush_to_socket(conn->outbuf, conn->s, sz, + &conn->outbuf_flushlen); } if (retval >= 0 && /* Technically, we could survive things like TLS_WANT_WRITE here. But don't bother for now. */ @@ -1142,7 +1145,7 @@ signewnym_impl(time_t now) circuit_mark_all_dirty_circs_as_unusable(); addressmap_clear_transient(); - rend_client_purge_state(); + hs_client_purge_state(); time_of_last_signewnym = now; signewnym_is_pending = 0; @@ -1194,6 +1197,7 @@ CALLBACK(heartbeat); CALLBACK(clean_consdiffmgr); CALLBACK(reset_padding_counts); CALLBACK(check_canonical_channels); +CALLBACK(hs_service); #undef CALLBACK @@ -1229,6 +1233,7 @@ static periodic_event_item_t periodic_events[] = { CALLBACK(clean_consdiffmgr), CALLBACK(reset_padding_counts), CALLBACK(check_canonical_channels), + CALLBACK(hs_service), END_OF_PERIODIC_EVENTS }; #undef CALLBACK @@ -1461,12 +1466,6 @@ run_scheduled_events(time_t now) /* 6. And remove any marked circuits... */ circuit_close_all_marked(); - /* 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, * 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. @@ -1713,7 +1712,7 @@ check_expired_networkstatus_callback(time_t now, const or_options_t *options) * networkstatus_get_reasonably_live_consensus(), but that value is way * way too high. Arma: is the bridge issue there resolved yet? -NM */ #define NS_EXPIRY_SLOP (24*60*60) - if (ns && ns->valid_until < now+NS_EXPIRY_SLOP && + if (ns && ns->valid_until < (now - NS_EXPIRY_SLOP) && router_have_minimum_dir_info()) { router_dir_info_changed(); } @@ -1831,8 +1830,8 @@ clean_caches_callback(time_t now, const or_options_t *options) { /* Remove old information from rephist and the rend cache. */ rep_history_clean(now - options->RephistTrackTime); - rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); rend_cache_clean(now, REND_CACHE_TYPE_SERVICE); + hs_cache_clean_as_client(now); hs_cache_clean_as_dir(now); microdesc_cache_rebuild(NULL, 0); #define CLEAN_CACHES_INTERVAL (30*60) @@ -1851,6 +1850,7 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options) * clean it as soon as we can since we want to make sure the client waits * as little as possible for reachability reasons. */ rend_cache_failure_clean(now); + hs_cache_client_intro_state_clean(now); return 30; } @@ -2040,7 +2040,8 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options) { if (net_is_disabled() || ! server_mode(options) || - ! options->PortForwarding) { + ! options->PortForwarding || + options->NoExec) { return PERIODIC_EVENT_NO_UPDATE; } /* 11. check the port forwarding app */ @@ -2060,6 +2061,9 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options) /** * Periodic callback: write the heartbeat message in the logs. + * + * If writing the heartbeat message to the logs fails for some reason, retry + * again after <b>MIN_HEARTBEAT_PERIOD</b> seconds. */ static int heartbeat_callback(time_t now, const or_options_t *options) @@ -2071,14 +2075,20 @@ heartbeat_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } - /* Write the heartbeat message */ + /* Skip the first one. */ if (first) { - first = 0; /* Skip the first one. */ - } else { - log_heartbeat(now); + first = 0; + return options->HeartbeatPeriod; } - return options->HeartbeatPeriod; + /* Write the heartbeat message */ + if (log_heartbeat(now) == 0) { + return options->HeartbeatPeriod; + } else { + /* If we couldn't write the heartbeat log message, try again in the minimum + * interval of time. */ + return MIN_HEARTBEAT_PERIOD; + } } #define CDM_CLEAN_CALLBACK_INTERVAL 600 @@ -2092,6 +2102,29 @@ clean_consdiffmgr_callback(time_t now, const or_options_t *options) return CDM_CLEAN_CALLBACK_INTERVAL; } +/* + * Periodic callback: Run scheduled events for HS service. This is called + * every second. + */ +static int +hs_service_callback(time_t now, const or_options_t *options) +{ + (void) options; + + /* We need to at least be able to build circuits and that we actually have + * a working network. */ + if (!have_completed_a_circuit() || net_is_disabled() || + networkstatus_get_live_consensus(now) == NULL) { + goto end; + } + + hs_service_run_scheduled_events(now); + + end: + /* Every 1 second. */ + return 1; +} + /** Timer: used to invoke second_elapsed_callback() once per second. */ static periodic_timer_t *second_timer = NULL; /** Number of libevent errors in the last second: we die if we get too many. */ @@ -2194,7 +2227,7 @@ systemd_watchdog_callback(periodic_timer_t *timer, void *arg) (void)arg; sd_notify(0, "WATCHDOG=1"); } -#endif +#endif /* defined(HAVE_SYSTEMD_209) */ /** Timer: used to invoke refill_callback(). */ static periodic_timer_t *refill_timer = NULL; @@ -2258,7 +2291,7 @@ got_libevent_error(void) } return 0; } -#endif +#endif /* !defined(_WIN32) */ #define UPTIME_CUTOFF_FOR_NEW_BANDWIDTH_TEST (6*60*60) @@ -2355,7 +2388,7 @@ do_hup(void) tor_free(msg); } } - if (authdir_mode_handles_descs(options, -1)) { + if (authdir_mode(options)) { /* reload the approved-routers file */ if (dirserv_load_fingerprint_file() < 0) { /* warnings are logged from dirserv_load_fingerprint_file() directly */ @@ -2499,9 +2532,6 @@ do_main_loop(void) } } - /* Initialize relay-side HS circuitmap */ - hs_circuitmap_init(); - /* set up once-a-second callback. */ if (! second_timer) { struct timeval one_second; @@ -2535,7 +2565,7 @@ do_main_loop(void) tor_assert(systemd_watchdog_timer); } } -#endif +#endif /* defined(HAVE_SYSTEMD_209) */ if (!refill_timer) { struct timeval refill_interval; @@ -2563,7 +2593,7 @@ do_main_loop(void) log_info(LD_GENERAL, "Systemd NOTIFY_SOCKET not present."); } } -#endif +#endif /* defined(HAVE_SYSTEMD) */ return run_main_loop_until_done(); } @@ -2616,7 +2646,7 @@ run_main_loop_once(void) log_warn(LD_NET, "EINVAL from libevent: should you upgrade libevent?"); if (got_libevent_error()) return -1; -#endif +#endif /* !defined(_WIN32) */ } else { tor_assert_nonfatal_once(! ERRNO_IS_EINPROGRESS(e)); log_debug(LD_NET,"libevent call interrupted."); @@ -2877,7 +2907,6 @@ dumpstats(int severity) rep_hist_dump_stats(now,severity); rend_service_dump_stats(severity); - dump_pk_ops(severity); dump_distinct_digest_count(severity); } @@ -2973,7 +3002,7 @@ handle_signals(int is_parent) #ifdef SIGXFSZ sigaction(SIGXFSZ, &action, NULL); #endif -#endif +#endif /* !defined(_WIN32) */ } } @@ -3014,9 +3043,10 @@ tor_init(int argc, char *argv[]) rep_hist_init(); /* Initialize the service cache. */ rend_cache_init(); - hs_cache_init(); addressmap_init(); /* Init the client dns cache. Do it always, since it's * cheap. */ + /* Initialize the HS subsystem. */ + hs_init(); { /* We search for the "quiet" option first, since it decides whether we @@ -3216,10 +3246,8 @@ tor_free_all(int postfork) networkstatus_free_all(); addressmap_free_all(); dirserv_free_all(); - rend_service_free_all(); rend_cache_free_all(); rend_service_authorization_free_all(); - hs_cache_free_all(); rep_hist_free_all(); dns_free_all(); clear_pending_onions(); @@ -3232,7 +3260,6 @@ tor_free_all(int postfork) connection_edge_free_all(); scheduler_free_all(); nodelist_free_all(); - hs_circuitmap_free_all(); microdesc_free_all(); routerparse_free_all(); ext_orport_free_all(); @@ -3241,6 +3268,7 @@ tor_free_all(int postfork) protover_free_all(); bridges_free_all(); consdiffmgr_free_all(); + hs_free_all(); if (!postfork) { config_free_all(); or_state_free_all(); @@ -3478,7 +3506,7 @@ sandbox_init_filter(void) if (options->BridgeAuthoritativeDir) OPEN_DATADIR_SUFFIX("networkstatus-bridges", ".tmp"); - if (authdir_mode_handles_descs(options, -1)) + if (authdir_mode(options)) OPEN_DATADIR("approved-routers"); if (options->ServerDNSResolvConfFile) @@ -3550,7 +3578,7 @@ sandbox_init_filter(void) { smartlist_t *files = smartlist_new(); smartlist_t *dirs = smartlist_new(); - rend_services_add_filenames_to_lists(files, dirs); + hs_service_lists_fnames_for_sandbox(files, dirs); SMARTLIST_FOREACH(files, char *, file_name, { char *tmp_name = NULL; tor_asprintf(&tmp_name, "%s.tmp", file_name); @@ -3702,7 +3730,7 @@ tor_main(int argc, char *argv[]) setdeppolicy(3); } } -#endif +#endif /* defined(_WIN32) */ configure_backtrace_handler(get_version()); @@ -3718,14 +3746,14 @@ tor_main(int argc, char *argv[]) int r = crypto_use_tor_alloc_functions(); tor_assert(r == 0); } -#endif +#endif /* defined(USE_DMALLOC) */ #ifdef NT_SERVICE { int done = 0; result = nt_service_parse_options(argc, argv, &done); if (done) return result; } -#endif +#endif /* defined(NT_SERVICE) */ if (tor_init(argc, argv)<0) return -1; @@ -3754,6 +3782,10 @@ tor_main(int argc, char *argv[]) case CMD_KEYGEN: result = load_ed_keys(get_options(), time(NULL)) < 0; break; + case CMD_KEY_EXPIRATION: + init_keys(); + result = log_cert_expiration(); + break; case CMD_LIST_FINGERPRINT: result = do_list_fingerprint(); break; diff --git a/src/or/main.h b/src/or/main.h index 57aa372750..132ab12bbb 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -95,7 +95,7 @@ STATIC void teardown_periodic_events(void); #ifdef TOR_UNIT_TESTS extern smartlist_t *connection_array; #endif -#endif +#endif /* defined(MAIN_PRIVATE) */ -#endif +#endif /* !defined(TOR_MAIN_H) */ diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 32242d0054..fe327c6c82 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -977,7 +977,7 @@ update_microdesc_downloads(time_t now) smartlist_free(missing); } -/** For every microdescriptor listed in the current microdecriptor consensus, +/** For every microdescriptor listed in the current microdescriptor consensus, * update its last_listed field to be at least as recent as the publication * time of the current microdescriptor consensus. */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 36e62020e3..e0a3e4cdc6 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -61,22 +61,17 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "scheduler.h" #include "shared_random.h" #include "transports.h" #include "torcert.h" #include "channelpadding.h" -/** Map from lowercase nickname to identity digest of named server, if any. */ -static strmap_t *named_server_map = NULL; -/** Map from lowercase nickname to (void*)1 for all names that are listed - * as unnamed for some server in the consensus. */ -static strmap_t *unnamed_server_map = NULL; - /** Most recently received and validated v3 "ns"-flavored consensus network * status. */ STATIC networkstatus_t *current_ns_consensus = NULL; -/** Most recently received and validated v3 "microdec"-flavored consensus +/** Most recently received and validated v3 "microdesc"-flavored consensus * network status. */ STATIC networkstatus_t *current_md_consensus = NULL; @@ -142,7 +137,6 @@ static int have_warned_about_old_version = 0; * listed by the authorities. */ static int have_warned_about_new_version = 0; -static void routerstatus_list_update_named_server_map(void); static void update_consensus_bootstrap_multiple_downloads( time_t now, const or_options_t *options); @@ -250,13 +244,6 @@ router_reload_consensus_networkstatus(void) } } - if (!networkstatus_get_latest_consensus()) { - if (!named_server_map) - named_server_map = strmap_new(); - if (!unnamed_server_map) - unnamed_server_map = strmap_new(); - } - update_certificate_downloads(time(NULL)); routers_update_all_from_networkstatus(time(NULL), 3); @@ -794,41 +781,6 @@ router_get_consensus_status_by_id(const char *digest) return router_get_mutable_consensus_status_by_id(digest); } -/** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding routerstatus_t, or NULL if none exists. Warn the - * user if <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ -const routerstatus_t * -router_get_consensus_status_by_nickname(const char *nickname, - int warn_if_unnamed) -{ - const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); - if (node) - return node->rs; - else - return NULL; -} - -/** Return the identity digest that's mapped to officially by - * <b>nickname</b>. */ -const char * -networkstatus_get_router_digest_by_nickname(const char *nickname) -{ - if (!named_server_map) - return NULL; - return strmap_get_lc(named_server_map, nickname); -} - -/** Return true iff <b>nickname</b> is disallowed from being the nickname - * of any server. */ -int -networkstatus_nickname_is_unnamed(const char *nickname) -{ - if (!unnamed_server_map) - return 0; - return strmap_get_lc(unnamed_server_map, nickname) != NULL; -} - /** How frequently do directory authorities re-download fresh networkstatus * documents? */ #define AUTHORITY_NS_CACHE_INTERVAL (10*60) @@ -1257,7 +1209,9 @@ should_delay_dir_fetches(const or_options_t *options, const char **msg_out) } if (options->UseBridges) { - if (!any_bridge_descriptors_known()) { + /* If we know that none of our bridges can possibly work, avoid fetching + * directory documents. But if some of them might work, try again. */ + if (num_bridges_usable(1) == 0) { if (msg_out) { *msg_out = "No running bridges"; } @@ -1393,14 +1347,21 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) MOCK_IMPL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)) { - if (networkstatus_get_latest_consensus() && - networkstatus_get_latest_consensus()->valid_after <= now && - now <= networkstatus_get_latest_consensus()->valid_until) - return networkstatus_get_latest_consensus(); + networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && networkstatus_is_live(ns, now)) + return ns; else return NULL; } +/** Given a consensus in <b>ns</b>, return true iff currently live and + * unexpired. */ +int +networkstatus_is_live(const networkstatus_t *ns, time_t now) +{ + return (ns->valid_after <= now && now <= ns->valid_until); +} + /** Determine if <b>consensus</b> is valid or expired recently enough that * we can still use it. * @@ -1603,6 +1564,15 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c, smartlist_free(changed); } +/* Called when the consensus has changed from old_c to new_c. */ +static void +notify_networkstatus_changed(const networkstatus_t *old_c, + const networkstatus_t *new_c) +{ + notify_control_networkstatus_changed(old_c, new_c); + scheduler_notify_networkstatus_changed(old_c, new_c); +} + /** Copy all the ancillary information (like router download status and so on) * from <b>old_c</b> to <b>new_c</b>. */ static void @@ -1660,7 +1630,7 @@ networkstatus_set_current_consensus_from_ns(networkstatus_t *c, } return current_md_consensus ? 0 : -1; } -#endif //TOR_UNIT_TESTS +#endif /* defined(TOR_UNIT_TESTS) */ /** * Return true if any option is set in <b>options</b> to make us behave @@ -1676,7 +1646,8 @@ any_client_port_set(const or_options_t *options) options->TransPort_set || options->NATDPort_set || options->ControlPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); } /** @@ -1783,7 +1754,7 @@ networkstatus_set_current_consensus(const char *consensus, if (from_cache && !was_waiting_for_certs) { /* We previously stored this; check _now_ to make sure that version-kills - * really work. This happens even before we check signatures: we did so + * really work. This happens even before we check signatures: we did so * before when we stored this to disk. This does mean an attacker who can * write to the datadir can make us not start: such an attacker could * already harm us by replacing our guards, which would be worse. */ @@ -1927,8 +1898,7 @@ networkstatus_set_current_consensus(const char *consensus, const int is_usable_flavor = flav == usable_consensus_flavor(); if (is_usable_flavor) { - notify_control_networkstatus_changed( - networkstatus_get_latest_consensus(), c); + notify_networkstatus_changed(networkstatus_get_latest_consensus(), c); } if (flav == FLAV_NS) { if (current_ns_consensus) { @@ -1971,14 +1941,17 @@ networkstatus_set_current_consensus(const char *consensus, } if (is_usable_flavor) { + /* The "current" consensus has just been set and it is a usable flavor so + * the first thing we need to do is recalculate the voting schedule static + * object so we can use the timings in there needed by some subsystems + * such as hidden service and shared random. */ + dirvote_recalculate_timing(options, now); + nodelist_set_consensus(c); /* XXXXNM Microdescs: needs a non-ns variant. ???? NM*/ update_consensus_networkstatus_fetch_time(now); - dirvote_recalculate_timing(options, now); - routerstatus_list_update_named_server_map(); - /* Update ewma and adjust policy if needed; first cache the old value */ old_ewma_enabled = cell_ewma_enabled(); /* Change the cell EWMA settings */ @@ -2031,14 +2004,16 @@ networkstatus_set_current_consensus(const char *consensus, char tbuf[ISO_TIME_LEN+1]; char dbuf[64]; long delta = now - c->valid_after; + char *flavormsg = NULL; format_iso_time(tbuf, c->valid_after); format_time_interval(dbuf, sizeof(dbuf), delta); log_warn(LD_GENERAL, "Our clock is %s behind the time published in the " "consensus network status document (%s UTC). Tor needs an " "accurate clock to work correctly. Please check your time and " "date settings!", dbuf, tbuf); - control_event_general_status(LOG_WARN, - "CLOCK_SKEW MIN_SKEW=%ld SOURCE=CONSENSUS", delta); + tor_asprintf(&flavormsg, "%s flavor consensus", flavor); + clock_skew_warning(NULL, delta, 1, LD_GENERAL, flavormsg, "CONSENSUS"); + tor_free(flavormsg); } /* We got a new consesus. Reset our md fetch fail cache */ @@ -2149,31 +2124,6 @@ routers_update_all_from_networkstatus(time_t now, int dir_version) } } -/** Update our view of the list of named servers from the most recently - * retrieved networkstatus consensus. */ -static void -routerstatus_list_update_named_server_map(void) -{ - networkstatus_t *ns = networkstatus_get_latest_consensus(); - if (!ns) - return; - - strmap_free(named_server_map, tor_free_); - named_server_map = strmap_new(); - strmap_free(unnamed_server_map, NULL); - unnamed_server_map = strmap_new(); - smartlist_t *rslist = ns->routerstatus_list; - SMARTLIST_FOREACH_BEGIN(rslist, const routerstatus_t *, rs) { - if (rs->is_named) { - strmap_set_lc(named_server_map, rs->nickname, - tor_memdup(rs->identity_digest, DIGEST_LEN)); - } - if (rs->is_unnamed) { - strmap_set_lc(unnamed_server_map, rs->nickname, (void*)1); - } - } SMARTLIST_FOREACH_END(rs); -} - /** Given a list <b>routers</b> of routerinfo_t *, update each status field * according to our current consensus networkstatus. May re-order * <b>routers</b>. */ @@ -2382,9 +2332,9 @@ get_net_param_from_list(smartlist_t *net_params, const char *param_name, * Make sure the value parsed from the consensus is at least * <b>min_val</b> and at most <b>max_val</b> and raise/cap the parsed value * if necessary. */ -int32_t -networkstatus_get_param(const networkstatus_t *ns, const char *param_name, - int32_t default_val, int32_t min_val, int32_t max_val) +MOCK_IMPL(int32_t, +networkstatus_get_param, (const networkstatus_t *ns, const char *param_name, + int32_t default_val, int32_t min_val, int32_t max_val)) { if (!ns) /* if they pass in null, go find it ourselves */ ns = networkstatus_get_latest_consensus(); @@ -2551,7 +2501,8 @@ getinfo_helper_networkstatus(control_connection_t *conn, } status = router_get_consensus_status_by_id(d); } else if (!strcmpstart(question, "ns/name/")) { - status = router_get_consensus_status_by_nickname(question+8, 0); + const node_t *n = node_get_by_nickname(question+8, 0); + status = n ? n->rs : NULL; } else if (!strcmpstart(question, "ns/purpose/")) { *answer = networkstatus_getinfo_by_purpose(question+11, time(NULL)); return *answer ? 0 : -1; @@ -2658,8 +2609,5 @@ networkstatus_free_all(void) } tor_free(waiting->body); } - - strmap_free(named_server_map, tor_free_); - strmap_free(unnamed_server_map, NULL); } diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index e774c4d266..39a0f753d8 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -62,11 +62,6 @@ const routerstatus_t *router_get_consensus_status_by_descriptor_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); -const char *networkstatus_get_router_digest_by_nickname(const char *nickname); -int networkstatus_nickname_is_unnamed(const char *nickname); int we_want_to_fetch_flavor(const or_options_t *options, int flavor); int we_want_to_fetch_unknown_auth_certs(const or_options_t *options); void networkstatus_consensus_download_failed(int status_code, @@ -81,6 +76,7 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void)); MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, (consensus_flavor_t f)); MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)); +int networkstatus_is_live(const networkstatus_t *ns, time_t now); int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, time_t now); int networkstatus_valid_until_is_reasonably_live(time_t valid_until, @@ -113,10 +109,9 @@ void signed_descs_update_status_from_consensus_networkstatus( char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); void networkstatus_dump_bridge_status_to_file(time_t now); -int32_t networkstatus_get_param(const networkstatus_t *ns, - const char *param_name, - int32_t default_val, int32_t min_val, - int32_t max_val); +MOCK_DECL(int32_t, networkstatus_get_param, + (const networkstatus_t *ns, const char *param_name, + int32_t default_val, int32_t min_val, int32_t max_val)); int32_t networkstatus_get_overridable_param(const networkstatus_t *ns, int32_t torrc_value, const char *param_name, @@ -142,8 +137,8 @@ STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c, const char *flavor); extern networkstatus_t *current_ns_consensus; extern networkstatus_t *current_md_consensus; -#endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ +#endif /* defined(NETWORKSTATUS_PRIVATE) */ -#endif +#endif /* !defined(TOR_NETWORKSTATUS_H) */ diff --git a/src/or/nodelist.c b/src/or/nodelist.c index d09989d93f..f2e979be8b 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -38,6 +38,8 @@ * used for authorities and fallback directories.) */ +#define NODELIST_PRIVATE + #include "or.h" #include "address.h" #include "config.h" @@ -45,6 +47,8 @@ #include "dirserv.h" #include "entrynodes.h" #include "geoip.h" +#include "hs_common.h" +#include "hs_client.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -54,6 +58,7 @@ #include "rendservice.h" #include "router.h" #include "routerlist.h" +#include "routerparse.h" #include "routerset.h" #include "torcert.h" @@ -90,7 +95,14 @@ typedef struct nodelist_t { smartlist_t *nodes; /* Hash table to map from node ID digest to node. */ HT_HEAD(nodelist_map, node_t) nodes_by_id; - + /* Hash table to map from node Ed25519 ID to node. + * + * Whenever a node's routerinfo or microdescriptor is about to change, + * you should remove it from this map with node_remove_from_ed25519_map(). + * Whenever a node's routerinfo or microdescriptor has just chaned, + * you should add it to this map with node_add_to_ed25519_map(). + */ + HT_HEAD(nodelist_ed_map, node_t) nodes_by_ed_id; } nodelist_t; static inline unsigned int @@ -109,6 +121,23 @@ HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq) HT_GENERATE2(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, 0.6, tor_reallocarray_, tor_free_) +static inline unsigned int +node_ed_id_hash(const node_t *node) +{ + return (unsigned) siphash24g(node->ed25519_id.pubkey, ED25519_PUBKEY_LEN); +} + +static inline unsigned int +node_ed_id_eq(const node_t *node1, const node_t *node2) +{ + return ed25519_pubkey_eq(&node1->ed25519_id, &node2->ed25519_id); +} + +HT_PROTOTYPE(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq) +HT_GENERATE2(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_) + /** The global nodelist. */ static nodelist_t *the_nodelist=NULL; @@ -119,6 +148,7 @@ init_nodelist(void) if (PREDICT_UNLIKELY(the_nodelist == NULL)) { the_nodelist = tor_malloc_zero(sizeof(nodelist_t)); HT_INIT(nodelist_map, &the_nodelist->nodes_by_id); + HT_INIT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); the_nodelist->nodes = smartlist_new(); } } @@ -136,6 +166,21 @@ node_get_mutable_by_id(const char *identity_digest) return node; } +/** As node_get_by_ed25519_id, but returns a non-const pointer */ +node_t * +node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id) +{ + node_t search, *node; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return NULL; + if (BUG(ed_id == NULL) || BUG(ed25519_public_key_is_zero(ed_id))) + return NULL; + + memcpy(&search.ed25519_id, ed_id, sizeof(search.ed25519_id)); + node = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, &search); + return node; +} + /** Return the node_t whose identity is <b>identity_digest</b>, or NULL * if no such node exists. */ MOCK_IMPL(const node_t *, @@ -144,6 +189,14 @@ node_get_by_id,(const char *identity_digest)) return node_get_mutable_by_id(identity_digest); } +/** Return the node_t whose ed25519 identity is <b>ed_id</b>, or NULL + * if no such node exists. */ +MOCK_IMPL(const node_t *, +node_get_by_ed25519_id,(const ed25519_public_key_t *ed_id)) +{ + return node_get_mutable_by_ed25519_id(ed_id); +} + /** Internal: return the node_t whose identity_digest is * <b>identity_digest</b>. If none exists, create a new one, add it to the * nodelist, and return it. @@ -164,12 +217,181 @@ node_get_or_create(const char *identity_digest) smartlist_add(the_nodelist->nodes, node); node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1; + node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t)); node->country = -1; return node; } +/** Remove <b>node</b> from the ed25519 map (if it present), and + * set its ed25519_id field to zero. */ +static int +node_remove_from_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + int rv = 0; + node_t *search = + HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(search != node)) { + goto clear_and_return; + } + + search = HT_REMOVE(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + tor_assert(search == node); + rv = 1; + + clear_and_return: + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return rv; +} + +/** If <b>node</b> has an ed25519 id, and it is not already in the ed25519 id + * map, set its ed25519_id field, and add it to the ed25519 map. + */ +static int +node_add_to_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (! ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + const ed25519_public_key_t *key = node_get_ed25519_id(node); + if (!key) { + return 0; + } + + node_t *old; + memcpy(&node->ed25519_id, key, sizeof(node->ed25519_id)); + old = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(old)) { + /* XXXX order matters here, and this may mean that authorities aren't + * pinning. */ + if (old != node) + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return 0; + } + + HT_INSERT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + return 1; +} + +/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index + * for the node, both current and next if possible. This can only fails if the + * node_t ed25519 identity key can't be found which would be a bug. */ +STATIC void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns) +{ + time_t now = approx_time(); + const ed25519_public_key_t *node_identity_pk; + uint8_t *fetch_srv = NULL, *store_first_srv = NULL, *store_second_srv = NULL; + uint64_t next_time_period_num, current_time_period_num; + uint64_t fetch_tp, store_first_tp, store_second_tp; + + tor_assert(node); + tor_assert(ns); + + if (!networkstatus_is_live(ns, now)) { + static struct ratelim_t live_consensus_ratelim = RATELIM_INIT(30 * 60); + log_fn_ratelim(&live_consensus_ratelim, LOG_INFO, LD_GENERAL, + "Not setting hsdir index with a non-live consensus."); + goto done; + } + + node_identity_pk = node_get_ed25519_id(node); + if (node_identity_pk == NULL) { + log_debug(LD_GENERAL, "ed25519 identity public key not found when " + "trying to build the hsdir indexes for node %s", + node_describe(node)); + goto done; + } + + /* Get the current and next time period number. */ + current_time_period_num = hs_get_time_period_num(0); + next_time_period_num = hs_get_next_time_period_num(0); + + /* We always use the current time period for fetching descs */ + fetch_tp = current_time_period_num; + + /* Now extract the needed SRVs and time periods for building hsdir indices */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + fetch_srv = hs_get_current_srv(fetch_tp, ns); + + store_first_tp = hs_get_previous_time_period_num(0); + store_second_tp = current_time_period_num; + } else { + fetch_srv = hs_get_previous_srv(fetch_tp, ns); + + store_first_tp = current_time_period_num; + store_second_tp = next_time_period_num; + } + + /* We always use the old SRV for storing the first descriptor and the latest + * SRV for storing the second descriptor */ + store_first_srv = hs_get_previous_srv(store_first_tp, ns); + store_second_srv = hs_get_current_srv(store_second_tp, ns); + + /* Build the fetch index. */ + hs_build_hsdir_index(node_identity_pk, fetch_srv, fetch_tp, + node->hsdir_index->fetch); + + /* If we are in the time segment between SRV#N and TP#N, the fetch index is + the same as the first store index */ + if (!hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_first, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_first)); + } else { + hs_build_hsdir_index(node_identity_pk, store_first_srv, store_first_tp, + node->hsdir_index->store_first); + } + + /* If we are in the time segment between TP#N and SRV#N+1, the fetch index is + the same as the second store index */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_second, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_second)); + } else { + hs_build_hsdir_index(node_identity_pk, store_second_srv, store_second_tp, + node->hsdir_index->store_second); + } + + done: + tor_free(fetch_srv); + tor_free(store_first_srv); + tor_free(store_second_srv); + return; +} + +/** Recompute all node hsdir indices. */ +void +nodelist_recompute_all_hsdir_indices(void) +{ + networkstatus_t *consensus; + if (!the_nodelist) { + return; + } + + /* Get a live consensus. Abort if not found */ + consensus = networkstatus_get_live_consensus(approx_time()); + if (!consensus) { + return; + } + + /* Recompute all hsdir indices */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + node_set_hsdir_index(node, consensus); + } SMARTLIST_FOREACH_END(node); +} + /** Called when a node's address changes. */ static void node_addrs_changed(node_t *node) @@ -194,6 +416,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) id_digest = ri->cache_info.identity_digest; node = node_get_or_create(id_digest); + node_remove_from_ed25519_map(node); + if (node->ri) { if (!routers_have_same_or_addrs(node->ri, ri)) { node_addrs_changed(node); @@ -207,6 +431,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) } node->ri = ri; + node_add_to_ed25519_map(node); + if (node->country == -1) node_set_country(node); @@ -216,6 +442,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) dirserv_set_node_flags_from_authoritative_status(node, status); } + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (node->rs && node->rs->supports_v3_hsdir) { + node_set_hsdir_index(node, + networkstatus_get_latest_consensus()); + } + return node; } @@ -242,11 +476,21 @@ nodelist_add_microdesc(microdesc_t *md) return NULL; node = node_get_mutable_by_id(rs->identity_digest); if (node) { + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; + node->md = md; md->held_by_nodes++; + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } + node_add_to_ed25519_map(node); } + return node; } @@ -274,15 +518,20 @@ nodelist_set_consensus(networkstatus_t *ns) if (ns->flavor == FLAV_MICRODESC) { if (node->md == NULL || tor_memneq(node->md->digest,rs->descriptor_digest,DIGEST256_LEN)) { + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; node->md = microdesc_cache_lookup_by_digest256(NULL, rs->descriptor_digest); if (node->md) node->md->held_by_nodes++; + node_add_to_ed25519_map(node); } } + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } node_set_country(node); /* If we're not an authdir, believe others. */ @@ -341,6 +590,9 @@ nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md) if (node && node->md == md) { node->md = NULL; md->held_by_nodes--; + if (! node_get_ed25519_id(node)) { + node_remove_from_ed25519_map(node); + } } } @@ -369,6 +621,7 @@ nodelist_drop_node(node_t *node, int remove_from_ht) tmp = HT_REMOVE(nodelist_map, &the_nodelist->nodes_by_id, node); tor_assert(tmp == node); } + node_remove_from_ed25519_map(node); idx = node->nodelist_idx; tor_assert(idx >= 0); @@ -410,6 +663,7 @@ node_free(node_t *node) if (node->md) node->md->held_by_nodes--; tor_assert(node->nodelist_idx == -1); + tor_free(node->hsdir_index); tor_free(node); } @@ -451,6 +705,7 @@ nodelist_free_all(void) return; HT_CLEAR(nodelist_map, &the_nodelist->nodes_by_id); + HT_CLEAR(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { node->nodelist_idx = -1; node_free(node); @@ -515,9 +770,27 @@ nodelist_assert_ok(void) tor_assert(node_sl_idx == node->nodelist_idx); } SMARTLIST_FOREACH_END(node); + /* Every node listed with an ed25519 identity should be listed by that + * identity. + */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + if (!ed25519_public_key_is_zero(&node->ed25519_id)) { + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + } SMARTLIST_FOREACH_END(node); + + node_t **idx; + HT_FOREACH(idx, nodelist_ed_map, &the_nodelist->nodes_by_ed_id) { + node_t *node = *idx; + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + tor_assert((long)smartlist_len(the_nodelist->nodes) == (long)HT_SIZE(&the_nodelist->nodes_by_id)); + tor_assert((long)smartlist_len(the_nodelist->nodes) >= + (long)HT_SIZE(&the_nodelist->nodes_by_ed_id)); + digestmap_free(dm, NULL); } @@ -534,28 +807,23 @@ nodelist_get_list,(void)) /** Given a hex-encoded nickname of the format DIGEST, $DIGEST, $DIGEST=name, * or $DIGEST~name, return the node with the matching identity digest and * nickname (if any). Return NULL if no such node exists, or if <b>hex_id</b> - * is not well-formed. */ + * is not well-formed. DOCDOC flags */ const node_t * -node_get_by_hex_id(const char *hex_id) +node_get_by_hex_id(const char *hex_id, unsigned flags) { char digest_buf[DIGEST_LEN]; char nn_buf[MAX_NICKNAME_LEN+1]; char nn_char='\0'; + (void) flags; // XXXX + if (hex_digest_nickname_decode(hex_id, digest_buf, &nn_char, nn_buf)==0) { const node_t *node = node_get_by_id(digest_buf); if (!node) return NULL; - if (nn_char) { - const char *real_name = node_get_nickname(node); - if (!real_name || strcasecmp(real_name, nn_buf)) - return NULL; - if (nn_char == '=') { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nn_buf); - if (!named_id || tor_memneq(named_id, digest_buf, DIGEST_LEN)) - return NULL; - } + if (nn_char == '=') { + /* "=" indicates a Named relay, but there aren't any of those now. */ + return NULL; } return node; } @@ -564,42 +832,27 @@ node_get_by_hex_id(const char *hex_id) } /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding node_t, or NULL if none exists. Warn the user if - * <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ + * the corresponding node_t, or NULL if none exists. Warn the user if they + * have specified a router by nickname, unless the NNF_NO_WARN_UNNAMED bit is + * set in <b>flags</b>. */ MOCK_IMPL(const node_t *, -node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) +node_get_by_nickname,(const char *nickname, unsigned flags)) { + const int warn_if_unnamed = !(flags & NNF_NO_WARN_UNNAMED); + if (!the_nodelist) return NULL; /* Handle these cases: DIGEST, $DIGEST, $DIGEST=name, $DIGEST~name. */ { const node_t *node; - if ((node = node_get_by_hex_id(nickname)) != NULL) + if ((node = node_get_by_hex_id(nickname, flags)) != NULL) return node; } if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) return NULL; - /* Okay, so if we get here, the nickname is just a nickname. Is there - * a binding for it in the consensus? */ - { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nickname); - if (named_id) - return node_get_by_id(named_id); - } - - /* Is it marked as owned-by-someone-else? */ - if (networkstatus_nickname_is_unnamed(nickname)) { - log_info(LD_GENERAL, "The name %s is listed as Unnamed: there is some " - "router that holds it, but not one listed in the current " - "consensus.", escaped(nickname)); - return NULL; - } - /* Okay, so the name is not canonical for anybody. */ { smartlist_t *matches = smartlist_new(); @@ -630,11 +883,9 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) if (! node->name_lookup_warned) { base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN); log_warn(LD_CONFIG, - "You specified a server \"%s\" by name, but the directory " - "authorities do not have any key registered for this " - "nickname -- so it could be used by any server, not just " - "the one you meant. " - "To make sure you get the same server in the future, refer " + "You specified a relay \"%s\" by name, but nicknames can be " + "used by any relay, not just the one you meant. " + "To make sure you get the same relay in the future, refer " "to it by key, as \"$%s\".", nickname, fp); node->name_lookup_warned = 1; } @@ -653,22 +904,37 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) const ed25519_public_key_t * node_get_ed25519_id(const node_t *node) { + const ed25519_public_key_t *ri_pk = NULL; + const ed25519_public_key_t *md_pk = NULL; if (node->ri) { if (node->ri->cache_info.signing_key_cert) { - const ed25519_public_key_t *pk = - &node->ri->cache_info.signing_key_cert->signing_key; - if (BUG(ed25519_public_key_is_zero(pk))) - goto try_the_md; - return pk; + ri_pk = &node->ri->cache_info.signing_key_cert->signing_key; + if (BUG(ed25519_public_key_is_zero(ri_pk))) + ri_pk = NULL; } } - try_the_md: + if (node->md) { if (node->md->ed25519_identity_pkey) { - return node->md->ed25519_identity_pkey; + md_pk = node->md->ed25519_identity_pkey; } } - return NULL; + + if (ri_pk && md_pk) { + if (ed25519_pubkey_eq(ri_pk, md_pk)) { + return ri_pk; + } else { + /* This can happen if the relay gets flagged NoEdConsensus which will be + * triggered on all relays of the network. Thus a protocol warning. */ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Inconsistent ed25519 identities in the nodelist"); + return NULL; + } + } else if (ri_pk) { + return ri_pk; + } else { + return md_pk; + } } /** Return true iff this node's Ed25519 identity matches <b>id</b>. @@ -707,6 +973,79 @@ node_supports_ed25519_link_authentication(const node_t *node) return 0; } +/** Return true iff <b>node</b> supports the hidden service directory version + * 3 protocol (proposal 224). */ +int +node_supports_v3_hsdir(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_v3_hsdir; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + /* Bug #22447 forces us to filter on tor version: + * If platform is a Tor version, and older than 0.3.0.8, return False. + * Else, obey the protocol list. */ + if (node->ri->platform) { + if (!strcmpstart(node->ri->platform, "Tor ") && + !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) { + return 0; + } + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSDIR, PROTOVER_HSDIR_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + +/** Return true iff <b>node</b> supports ed25519 authentication as an hidden + * service introduction point.*/ +int +node_supports_ed25519_hs_intro(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_ed25519_hs_intro; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSINTRO, PROTOVER_HS_INTRO_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + +/** Return true iff <b>node</b> supports to be a rendezvous point for hidden + * service version 3 (HSRend=2). */ +int +node_supports_v3_rendezvous_point(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_v3_rendezvous_point; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSREND, + PROTOVER_HS_RENDEZVOUS_POINT_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + /** Return the RSA ID key's SHA1 digest for the provided node. */ const uint8_t * node_get_rsa_id_digest(const node_t *node) @@ -728,21 +1067,6 @@ node_get_nickname(const node_t *node) return NULL; } -/** Return true iff the nickname of <b>node</b> is canonical, based on the - * latest consensus. */ -int -node_is_named(const node_t *node) -{ - const char *named_id; - const char *nickname = node_get_nickname(node); - if (!nickname) - return 0; - named_id = networkstatus_get_router_digest_by_nickname(nickname); - if (!named_id) - return 0; - return tor_memeq(named_id, node->identity, DIGEST_LEN); -} - /** Return true iff <b>node</b> appears to be a directory authority or * directory cache */ int @@ -790,13 +1114,12 @@ node_get_verbose_nickname(const node_t *node, char *verbose_name_out) { const char *nickname = node_get_nickname(node); - int is_named = node_is_named(node); verbose_name_out[0] = '$'; base16_encode(verbose_name_out+1, HEX_DIGEST_LEN+1, node->identity, DIGEST_LEN); if (!nickname) return; - verbose_name_out[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + verbose_name_out[1+HEX_DIGEST_LEN] = '~'; strlcpy(verbose_name_out+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); } @@ -1356,8 +1679,7 @@ node_nickname_matches(const node_t *node, const char *nickname) return 1; return hex_digest_nickname_matches(nickname, node->identity, - n, - node_is_named(node)); + n); } /** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ @@ -1459,7 +1781,7 @@ nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { const node_t *node2; const smartlist_t *family2; - if (!(node2 = node_get_by_nickname(name, 0))) + if (!(node2 = node_get_by_nickname(name, NNF_NO_WARN_UNNAMED))) continue; if (!(family2 = node_get_declared_family(node2))) continue; @@ -1611,8 +1933,8 @@ static char dir_info_status[512] = ""; * 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) +MOCK_IMPL(int, +router_have_minimum_dir_info,(void)) { static int logged_delay=0; const char *delay_fetches_msg = NULL; @@ -1659,6 +1981,8 @@ router_dir_info_changed(void) { need_to_update_have_min_dir_info = 1; rend_hsdir_routers_changed(); + hs_service_dir_info_changed(); + hs_client_dir_info_changed(); } /** Return a string describing what we're missing before we have enough @@ -1968,6 +2292,7 @@ update_router_have_minimum_dir_info(void) { time_t now = time(NULL); int res; + int num_present=0, num_usable=0; const or_options_t *options = get_options(); const networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); @@ -1986,17 +2311,9 @@ update_router_have_minimum_dir_info(void) using_md = consensus->flavor == FLAV_MICRODESC; - if (! entry_guards_have_enough_dir_info_to_build_circuits()) { - strlcpy(dir_info_status, "We're missing descriptors for some of our " - "primary entry guards", sizeof(dir_info_status)); - res = 0; - goto done; - } - /* Check fraction of available paths */ { char *status = NULL; - int num_present=0, num_usable=0; double paths = compute_frac_paths_available(consensus, options, now, &num_present, &num_usable, &status); @@ -2017,6 +2334,18 @@ update_router_have_minimum_dir_info(void) res = 1; } + { /* Check entry guard dirinfo status */ + char *guard_error = entry_guards_get_err_str_if_dir_info_missing(using_md, + num_present, + num_usable); + if (guard_error) { + strlcpy(dir_info_status, guard_error, sizeof(dir_info_status)); + tor_free(guard_error); + res = 0; + goto done; + } + } + done: /* If paths have just become available in this update. */ diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 95ae778a5b..754990ac8d 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -18,7 +18,14 @@ node_t *node_get_mutable_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 *node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id); +MOCK_DECL(const node_t *, node_get_by_ed25519_id, + (const ed25519_public_key_t *ed_id)); + +#define NNF_NO_WARN_UNNAMED (1u<<0) + +const node_t *node_get_by_hex_id(const char *identity_digest, + unsigned flags); node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out); node_t *nodelist_add_microdesc(microdesc_t *md); void nodelist_set_consensus(networkstatus_t *ns); @@ -28,16 +35,17 @@ void nodelist_remove_routerinfo(routerinfo_t *ri); void nodelist_purge(void); smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md); +void nodelist_recompute_all_hsdir_indices(void); + void nodelist_free_all(void); void nodelist_assert_ok(void); MOCK_DECL(const node_t *, node_get_by_nickname, - (const char *nickname, int warn_if_unnamed)); + (const char *nickname, unsigned flags)); 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); int node_get_purpose(const node_t *node); @@ -58,6 +66,9 @@ const ed25519_public_key_t *node_get_ed25519_id(const node_t *node); int node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id); int node_supports_ed25519_link_authentication(const node_t *node); +int node_supports_v3_hsdir(const node_t *node); +int node_supports_ed25519_hs_intro(const node_t *node); +int node_supports_v3_rendezvous_point(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); int node_has_ipv6_addr(const node_t *node); @@ -103,7 +114,7 @@ int addrs_in_same_network_family(const tor_addr_t *a1, * 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); +MOCK_DECL(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 @@ -131,5 +142,16 @@ void router_dir_info_changed(void); const char *get_dir_info_status_string(void); int count_loading_descriptors_progress(void); -#endif +#ifdef NODELIST_PRIVATE + +#ifdef TOR_UNIT_TESTS + +STATIC void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(NODELIST_PRIVATE) */ + +#endif /* !defined(TOR_NODELIST_H) */ diff --git a/src/or/ntmain.c b/src/or/ntmain.c index d0d5276c48..508e5844eb 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -329,9 +329,10 @@ nt_service_main(void) case CMD_VERIFY_CONFIG: case CMD_DUMP_CONFIG: case CMD_KEYGEN: + case CMD_KEY_EXPIRATION: log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, " - "--hash-password, --keygen, --dump-config, or --verify-config) " - "in NT service."); + "--hash-password, --keygen, --dump-config, --verify-config, " + "or --key-expiration) in NT service."); break; case CMD_RUN_UNITTESTS: default: @@ -501,7 +502,7 @@ nt_service_command_line(int *using_default_torrc) tor_exe_ascii[sizeof(tor_exe_ascii)-1] = '\0'; #else strlcpy(tor_exe_ascii, tor_exe, sizeof(tor_exe_ascii)); -#endif +#endif /* defined(UNICODE) */ /* Allocate a string for the NT service command line and */ /* Format the service command */ @@ -777,5 +778,5 @@ nt_service_parse_options(int argc, char **argv, int *should_exit) return 0; } -#endif +#endif /* defined(_WIN32) */ diff --git a/src/or/ntmain.h b/src/or/ntmain.h index 4b771b1828..81b7159855 100644 --- a/src/or/ntmain.h +++ b/src/or/ntmain.h @@ -22,7 +22,7 @@ int nt_service_is_stopping(void); void nt_service_set_state(DWORD state); #else #define nt_service_is_stopping() 0 -#endif +#endif /* defined(NT_SERVICE) */ -#endif +#endif /* !defined(TOR_NTMAIN_H) */ diff --git a/src/or/onion.c b/src/or/onion.c index a98b97cb1d..7e1e89df1b 100644 --- a/src/or/onion.c +++ b/src/or/onion.c @@ -1219,7 +1219,7 @@ extend_cell_format(uint8_t *command_out, uint16_t *len_out, *command_out = RELAY_COMMAND_EXTEND; *len_out = 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN; set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr)); - set_uint16(p+4, ntohs(cell_in->orport_ipv4.port)); + set_uint16(p+4, htons(cell_in->orport_ipv4.port)); if (cell_in->create_cell.handshake_type == ONION_HANDSHAKE_TYPE_NTOR) { memcpy(p+6, NTOR_CREATE_MAGIC, 16); memcpy(p+22, cell_in->create_cell.onionskin, NTOR_ONIONSKIN_LEN); diff --git a/src/or/onion.h b/src/or/onion.h index 37a7b08cb6..95544dfac1 100644 --- a/src/or/onion.h +++ b/src/or/onion.h @@ -119,5 +119,5 @@ int extend_cell_format(uint8_t *command_out, uint16_t *len_out, int extended_cell_format(uint8_t *command_out, uint16_t *len_out, uint8_t *payload_out, const extended_cell_t *cell_in); -#endif +#endif /* !defined(TOR_ONION_H) */ diff --git a/src/or/onion_fast.h b/src/or/onion_fast.h index b31f8e9492..3a5aefea3f 100644 --- a/src/or/onion_fast.h +++ b/src/or/onion_fast.h @@ -35,5 +35,5 @@ int fast_client_handshake(const fast_handshake_state_t *handshake_state, size_t key_out_len, const char **msg_out); -#endif +#endif /* !defined(TOR_ONION_FAST_H) */ diff --git a/src/or/onion_ntor.h b/src/or/onion_ntor.h index 158c499de4..02dea2dfc1 100644 --- a/src/or/onion_ntor.h +++ b/src/or/onion_ntor.h @@ -55,7 +55,7 @@ struct ntor_handshake_state_t { curve25519_public_key_t pubkey_X; /** @} */ }; -#endif +#endif /* defined(ONION_NTOR_PRIVATE) */ -#endif +#endif /* !defined(TOR_ONION_NTOR_H) */ diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c index 294fc0df6d..c71fa236ed 100644 --- a/src/or/onion_tap.c +++ b/src/or/onion_tap.c @@ -72,10 +72,8 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key, if (crypto_dh_get_public(dh, challenge, dhbytes)) goto err; - note_crypto_pk_op(ENC_ONIONSKIN); - /* set meeting point, meeting cookie, etc here. Leave zero for now. */ - if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out, + if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out, TAP_ONIONSKIN_CHALLENGE_LEN, challenge, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1)<0) @@ -124,8 +122,7 @@ onion_skin_TAP_server_handshake( k = i==0?private_key:prev_private_key; if (!k) break; - note_crypto_pk_op(DEC_ONIONSKIN); - len = crypto_pk_private_hybrid_decrypt(k, challenge, + len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge, TAP_ONIONSKIN_CHALLENGE_LEN, onion_skin, TAP_ONIONSKIN_CHALLENGE_LEN, diff --git a/src/or/onion_tap.h b/src/or/onion_tap.h index bd625231f4..713c1d7391 100644 --- a/src/or/onion_tap.h +++ b/src/or/onion_tap.h @@ -34,5 +34,5 @@ int onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, size_t key_out_len, const char **msg_out); -#endif +#endif /* !defined(TOR_ONION_TAP_H) */ diff --git a/src/or/or.h b/src/or/or.h index 95281e701f..03e20b80de 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -64,7 +64,7 @@ #include <process.h> #include <direct.h> #include <windows.h> -#endif +#endif /* defined(_WIN32) */ #include "crypto.h" #include "crypto_format.h" @@ -226,8 +226,10 @@ typedef enum { #define CONN_TYPE_EXT_OR 16 /** Type for sockets listening for Extended ORPort connections. */ #define CONN_TYPE_EXT_OR_LISTENER 17 +/** Type for sockets listening for HTTP CONNECT tunnel connections. */ +#define CONN_TYPE_AP_HTTP_CONNECT_LISTENER 18 -#define CONN_TYPE_MAX_ 17 +#define CONN_TYPE_MAX_ 19 /* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in * connection_t. */ @@ -348,7 +350,9 @@ typedef enum { /** State for a transparent natd connection: waiting for original * destination. */ #define AP_CONN_STATE_NATD_WAIT 12 -#define AP_CONN_STATE_MAX_ 12 +/** State for an HTTP tunnel: waiting for an HTTP CONNECT command. */ +#define AP_CONN_STATE_HTTP_CONNECT_WAIT 13 +#define AP_CONN_STATE_MAX_ 13 /** True iff the AP_CONN_STATE_* value <b>s</b> means that the corresponding * edge connection is not attached to any circuit. */ @@ -421,15 +425,23 @@ typedef enum { #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 /** A connection to a directory server: download a microdescriptor. */ #define DIR_PURPOSE_FETCH_MICRODESC 19 -#define DIR_PURPOSE_MAX_ 19 +/** A connection to a hidden service directory: upload a v3 descriptor. */ +#define DIR_PURPOSE_UPLOAD_HSDESC 20 +/** A connection to a hidden service directory: fetch a v3 descriptor. */ +#define DIR_PURPOSE_FETCH_HSDESC 21 +/** A connection to a directory server: set after a hidden service descriptor + * is downloaded. */ +#define DIR_PURPOSE_HAS_FETCHED_HSDESC 22 +#define DIR_PURPOSE_MAX_ 22 /** True iff <b>p</b> is a purpose corresponding to uploading * data to a directory server. */ #define DIR_PURPOSE_IS_UPLOAD(p) \ ((p)==DIR_PURPOSE_UPLOAD_DIR || \ (p)==DIR_PURPOSE_UPLOAD_VOTE || \ - (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ - (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2) + (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ + (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 || \ + (p)==DIR_PURPOSE_UPLOAD_HSDESC) #define EXIT_PURPOSE_MIN_ 1 /** This exit stream wants to do an ordinary connect. */ @@ -640,6 +652,10 @@ typedef enum { /** The target address is in a private network (like 127.0.0.1 or 10.0.0.1); * you don't want to do that over a randomly chosen exit */ #define END_STREAM_REASON_PRIVATE_ADDR 262 +/** This is an HTTP tunnel connection and the client used or misused HTTP in a + * way we can't handle. + */ +#define END_STREAM_REASON_HTTPPROTOCOL 263 /** Bitwise-and this value with endreason to mask out all flags. */ #define END_STREAM_REASON_MASK 511 @@ -731,15 +747,15 @@ typedef enum { #define REND_NUMBER_OF_CONSECUTIVE_REPLICAS 3 /** Length of v2 descriptor ID (32 base32 chars = 160 bits). */ -#define REND_DESC_ID_V2_LEN_BASE32 32 +#define REND_DESC_ID_V2_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the base32-encoded secret ID part of versioned hidden service * descriptors. */ -#define REND_SECRET_ID_PART_LEN_BASE32 32 +#define REND_SECRET_ID_PART_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the base32-encoded hash of an introduction point's * identity key. */ -#define REND_INTRO_POINT_ID_LEN_BASE32 32 +#define REND_INTRO_POINT_ID_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the descriptor cookie that is used for client authorization * to hidden services. */ @@ -846,6 +862,13 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d) return DOWNCAST(rend_data_v2_t, d); } +/* Stub because we can't include hs_ident.h. */ +struct hs_ident_edge_conn_t; +struct hs_ident_dir_conn_t; +struct hs_ident_circuit_t; +/* Stub because we can't include hs_common.h. */ +struct hsdir_index_t; + /** Time interval for tracking replays of DH public keys received in * INTRODUCE2 cells. Used only to avoid launching multiple * simultaneous attempts to connect to the same rendezvous point. */ @@ -1179,11 +1202,8 @@ typedef struct { uint16_t length; /**< How long is the payload body? */ } relay_header_t; -typedef struct buf_t buf_t; typedef struct socks_request_t socks_request_t; -#define buf_t buf_t - typedef struct entry_port_cfg_t { /* Client port types (socks, dns, trans, natd) only: */ uint8_t isolation_flags; /**< Zero or more isolation flags */ @@ -1243,6 +1263,8 @@ typedef struct server_port_cfg_t { #define CONTROL_CONNECTION_MAGIC 0x8abc765du #define LISTENER_CONNECTION_MAGIC 0x1a1ac741u +struct buf_t; + /** Description of a connection to another host or process, and associated * data. * @@ -1314,8 +1336,9 @@ typedef struct connection_t { struct event *read_event; /**< Libevent event structure. */ struct event *write_event; /**< Libevent event structure. */ - buf_t *inbuf; /**< Buffer holding data read over this connection. */ - buf_t *outbuf; /**< Buffer holding data to write over this connection. */ + struct buf_t *inbuf; /**< Buffer holding data read over this connection. */ + struct buf_t *outbuf; /**< Buffer holding data to write over this + * connection. */ size_t outbuf_flushlen; /**< How much data should we try to flush from the * outbuf? */ time_t timestamp_lastread; /**< When was the last time libevent said we could @@ -1410,7 +1433,7 @@ typedef struct listener_connection_t { * session as described in RFC 5705. * * Not used by today's tors, since everything that supports this - * also supports ED25519_SHA3_5705, which is better. + * also supports ED25519_SHA256_5705, which is better. **/ #define AUTHTYPE_RSA_SHA256_RFC5705 2 /** As AUTHTYPE_RSA_SHA256_RFC5705, but uses an Ed25519 identity key to @@ -1648,6 +1671,11 @@ typedef struct edge_connection_t { * an exit)? */ rend_data_t *rend_data; + /* Hidden service connection identifier for edge connections. Used by the HS + * client-side code to identify client SOCKS connections and by the + * service-side code to match HS circuits with their streams. */ + struct hs_ident_edge_conn_t *hs_ident; + uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit * connection. Exit connections only. */ uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell @@ -1717,11 +1745,11 @@ typedef struct entry_connection_t { /** For AP connections only: buffer for data that we have sent * optimistically, which we might need to re-send if we have to * retry this connection. */ - buf_t *pending_optimistic_data; + struct buf_t *pending_optimistic_data; /* For AP connections only: buffer for data that we previously sent * optimistically which we are currently re-sending as we retry this * connection. */ - buf_t *sending_optimistic_data; + struct buf_t *sending_optimistic_data; /** If this is a DNSPort connection, this field holds the pending DNS * request that we're going to try to answer. */ @@ -1798,6 +1826,11 @@ typedef struct dir_connection_t { /** What rendezvous service are we querying for? */ rend_data_t *rend_data; + /* Hidden service connection identifier for dir connections: Used by HS + client-side code to fetch HS descriptors, and by the service-side code to + upload descriptors. */ + struct hs_ident_dir_conn_t *hs_ident; + /** If this is a one-hop connection, tracks the state of the directory guard * for this connection (if any). */ struct circuit_guard_state_t *guard_state; @@ -1816,7 +1849,7 @@ typedef struct dir_connection_t { /** Number of RELAY_DATA cells sent. */ uint32_t data_cells_sent; -#endif +#endif /* defined(MEASUREMENTS_21206) */ } dir_connection_t; /** Subtype of connection_t for an connection to a controller. */ @@ -2073,7 +2106,9 @@ typedef struct download_status_t { * or after each failure? */ download_schedule_backoff_bitfield_t backoff : 1; /**< do we use the * deterministic schedule, or random - * exponential backoffs? */ + * exponential backoffs? + * Increment on failure schedules + * always use exponential backoff. */ uint8_t last_backoff_position; /**< number of attempts/failures, depending * on increment_on, when we last recalculated * the delay. Only updated if backoff @@ -2309,6 +2344,11 @@ typedef struct routerstatus_t { * requires HSDir=2. */ unsigned int supports_v3_hsdir : 1; + /** True iff this router has a protocol list that allows it to be an hidden + * service rendezvous point supporting version 3 as seen in proposal 224. + * This requires HSRend=2. */ + unsigned int supports_v3_rendezvous_point: 1; + unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with @@ -2434,6 +2474,8 @@ typedef struct node_t { /** Used to look up the node_t by its identity digest. */ HT_ENTRY(node_t) ht_ent; + /** Used to look up the node_t by its ed25519 identity digest. */ + HT_ENTRY(node_t) ed_ht_ent; /** Position of the node within the list of nodes */ int nodelist_idx; @@ -2441,6 +2483,13 @@ typedef struct node_t { * identity may exist at a time. */ char identity[DIGEST_LEN]; + /** The ed25519 identity of this node_t. This field is nonzero iff we + * currently have an ed25519 identity for this node in either md or ri, + * _and_ this node has been inserted to the ed25519-to-node map in the + * nodelist. + */ + ed25519_public_key_t ed25519_id; + microdesc_t *md; routerinfo_t *ri; routerstatus_t *rs; @@ -2488,6 +2537,10 @@ typedef struct node_t { time_t last_reachable; /* IPv4. */ time_t last_reachable6; /* IPv6. */ + /* Hidden service directory index data. This is used by a service or client + * in order to know what's the hs directory index for this node at the time + * the consensus is set. */ + struct hsdir_index_t *hsdir_index; } node_t; /** Linked list of microdesc hash lines for a single router in a directory @@ -3201,6 +3254,10 @@ typedef struct origin_circuit_t { /** Holds all rendezvous data on either client or service side. */ rend_data_t *rend_data; + /** Holds hidden service identifier on either client or service side. This + * is for both introduction and rendezvous circuit. */ + struct hs_ident_circuit_t *hs_ident; + /** Holds the data that the entry guard system uses to track the * status of the guard this circuit is using, and thereby to determine * whether this circuit can be used. */ @@ -3431,9 +3488,6 @@ typedef struct or_circuit_t { /* We have already received an INTRODUCE1 cell on this circuit. */ unsigned int already_received_introduce1 : 1; - /** 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; @@ -3582,7 +3636,8 @@ typedef struct { enum { CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD, CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG, - CMD_KEYGEN + CMD_KEYGEN, + CMD_KEY_EXPIRATION, } command; char *command_arg; /**< Argument for command-line option. */ @@ -3664,8 +3719,8 @@ typedef struct { 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? */ + char *TransProxyType; /**< What kind of transparent proxy + * implementation are we using? */ /** Parsed value of TransProxyType. */ enum { TPT_DEFAULT, @@ -3675,6 +3730,8 @@ typedef struct { } TransProxyType_parsed; config_line_t *NATDPort_lines; /**< Ports to listen on for transparent natd * connections. */ + /** Ports to listen on for HTTP Tunnel connections. */ + config_line_t *HTTPTunnelPort_lines; config_line_t *ControlPort_lines; /**< Ports to listen on for control * connections. */ config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on @@ -3701,7 +3758,8 @@ typedef struct { * configured in one of the _lines options above. * For client ports, also true if there is a unix socket configured. * If you are checking for client ports, you may want to use: - * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set + * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set || + * HTTPTunnelPort_set * rather than SocksPort_set. * * @{ @@ -3714,6 +3772,7 @@ typedef struct { unsigned int DirPort_set : 1; unsigned int DNSPort_set : 1; unsigned int ExtORPort_set : 1; + unsigned int HTTPTunnelPort_set : 1; /**@}*/ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ @@ -3726,6 +3785,10 @@ typedef struct { int BridgeAuthoritativeDir; /**< Boolean: is this an authoritative directory * that aggregates bridge descriptors? */ + /** If set on a bridge relay, it will include this value on a new + * "bridge-distribution-request" line in its bridge descriptor. */ + char *BridgeDistribution; + /** If set on a bridge authority, it will answer requests on its dirport * for bridge statuses -- but only if the requests use this password. */ char *BridgePassword; @@ -4035,8 +4098,6 @@ typedef struct { 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 ? \ - LOG_WARN : LOG_INFO) int ProtocolWarnings; /**< Boolean: when other parties screw up the Tor * protocol, is it a warn or an info in our logs? */ int TestSocks; /**< Boolean: when we get a socks connection, do we loudly @@ -4122,13 +4183,6 @@ typedef struct { * if we are a cache). For authorities, this is always true. */ int DownloadExtraInfo; - /** If true, we convert "www.google.com.foo.exit" addresses on the - * socks/trans/natd ports into "www.google.com" addresses that - * exit from the node "foo". Disabled by default since attacking - * websites and exit relays can use it to manipulate your path - * selection. */ - int AllowDotExit; - /** If true, we're configured to collect statistics on clients * requesting network statuses from us as directory. */ int DirReqStatistics_option; @@ -4293,6 +4347,10 @@ typedef struct { * altered on testing networks. */ smartlist_t *TestingBridgeDownloadSchedule; + /** Schedule for when clients should download bridge descriptors when they + * have no running bridges. Only altered on testing networks. */ + smartlist_t *TestingBridgeBootstrapDownloadSchedule; + /** 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. */ @@ -4497,19 +4555,6 @@ typedef struct { /** How long (seconds) do we keep a guard before picking a new one? */ int GuardLifetime; - /** 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." @@ -4578,8 +4623,29 @@ typedef struct { * consensuses around so that we can generate diffs from them. If 0, * use the default. */ int MaxConsensusAgeForDiffs; + + /** Bool (default: 0). Tells Tor to never try to exec another program. + */ + int NoExec; + + /** Have the KIST scheduler run every X milliseconds. If less than zero, do + * not use the KIST scheduler but use the old vanilla scheduler instead. If + * zero, do what the consensus says and fall back to using KIST as if this is + * set to "10 msec" if the consensus doesn't say anything. */ + int KISTSchedRunInterval; + + /** A multiplier for the KIST per-socket limit calculation. */ + double KISTSockBufSizeFactor; + + /** The list of scheduler type string ordered by priority that is first one + * has to be tried first. Default: KIST,KISTLite,Vanilla */ + smartlist_t *Schedulers; + /* An ordered list of scheduler_types mapped from Schedulers. */ + smartlist_t *SchedulerTypes_; } or_options_t; +#define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level()) + /** Persistent state for an onion router, as saved to disk. */ typedef struct { uint32_t magic_; @@ -4609,6 +4675,9 @@ typedef struct { config_line_t *TransportProxies; + /** Cached revision counters for active hidden services on this host */ + config_line_t *HidServRevCounter; + /** These fields hold information on the history of bandwidth usage for * servers. The "Ends" fields hold the time when we last updated the * bandwidth usage. The "Interval" fields hold the granularity, in seconds, @@ -4636,8 +4705,8 @@ typedef struct { /** Build time histogram */ config_line_t * BuildtimeHistogram; - unsigned int TotalBuildTimes; - unsigned int CircuitBuildAbandonedCount; + int TotalBuildTimes; + int CircuitBuildAbandonedCount; /** What version of Tor wrote this state file? */ char *TorVersion; @@ -4998,7 +5067,7 @@ typedef struct measured_bw_line_t { long int bw_kb; } measured_bw_line_t; -#endif +#endif /* defined(DIRSERV_PRIVATE) */ /********************************* dirvote.c ************************/ @@ -5353,7 +5422,10 @@ typedef enum { CRN_PREF_ADDR = 1<<7, /* On clients, only provide nodes that we can connect to directly, based on * our firewall rules */ - CRN_DIRECT_CONN = 1<<8 + CRN_DIRECT_CONN = 1<<8, + /* On clients, only provide nodes with HSRend >= 2 protocol version which + * is required for hidden service version >= 3. */ + CRN_RENDEZVOUS_V3 = 1<<9, } router_crn_flags_t; /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ @@ -5408,5 +5480,5 @@ typedef struct tor_version_t { char git_tag[DIGEST_LEN]; } tor_version_t; -#endif +#endif /* !defined(TOR_OR_H) */ diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c index 7959867875..6c3dd3100e 100644 --- a/src/or/parsecommon.c +++ b/src/or/parsecommon.c @@ -161,6 +161,7 @@ get_token_arguments(memarea_t *area, directory_token_t *tok, char *cp = mem; int j = 0; char *args[MAX_ARGS]; + memset(args, 0, sizeof(args)); while (*cp) { if (j == MAX_ARGS) return -1; @@ -436,7 +437,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword) * in the same order in which they occur in <b>s</b>. Otherwise return * NULL. */ smartlist_t * -find_all_by_keyword(smartlist_t *s, directory_keyword k) +find_all_by_keyword(const smartlist_t *s, directory_keyword k) { smartlist_t *out = NULL; SMARTLIST_FOREACH(s, directory_token_t *, t, diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h index b9f1613457..903d94478b 100644 --- a/src/or/parsecommon.h +++ b/src/or/parsecommon.h @@ -160,6 +160,7 @@ typedef enum { R3_INTRO_AUTH_REQUIRED, R3_SINGLE_ONION_SERVICE, R3_INTRODUCTION_POINT, + R3_INTRO_ONION_KEY, R3_INTRO_AUTH_KEY, R3_INTRO_ENC_KEY, R3_INTRO_ENC_KEY_CERT, @@ -315,7 +316,7 @@ directory_token_t *find_by_keyword_(smartlist_t *s, directory_token_t *find_opt_by_keyword(smartlist_t *s, directory_keyword keyword); -smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k); +smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k); -#endif /* TOR_PARSECOMMON_H */ +#endif /* !defined(TOR_PARSECOMMON_H) */ diff --git a/src/or/periodic.h b/src/or/periodic.h index 88d00cc7e9..8baf3994eb 100644 --- a/src/or/periodic.h +++ b/src/or/periodic.h @@ -33,5 +33,5 @@ void periodic_event_setup(periodic_event_item_t *event); void periodic_event_destroy(periodic_event_item_t *event); void periodic_event_reschedule(periodic_event_item_t *event); -#endif +#endif /* !defined(TOR_PERIODIC_H) */ diff --git a/src/or/policies.c b/src/or/policies.c index 3d49a6110c..78451db8fc 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -2186,21 +2186,16 @@ exit_policy_is_general_exit_helper(smartlist_t *policy, int port) } /** Return true iff <b>ri</b> is "useful as an exit node", meaning - * it allows exit to at least one /8 address space for at least - * two of ports 80, 443, and 6667. */ + * it allows exit to at least one /8 address space for each of ports 80 + * and 443. */ int exit_policy_is_general_exit(smartlist_t *policy) { - static const int ports[] = { 80, 443, 6667 }; - int n_allowed = 0; - int i; if (!policy) /*XXXX disallow NULL policies? */ return 0; - for (i = 0; i < 3; ++i) { - n_allowed += exit_policy_is_general_exit_helper(policy, ports[i]); - } - return n_allowed >= 2; + return (exit_policy_is_general_exit_helper(policy, 80) && + exit_policy_is_general_exit_helper(policy, 443)); } /** Return false if <b>policy</b> might permit access to some addr:port; @@ -2731,7 +2726,7 @@ parse_short_policy(const char *summary) } { - size_t size = STRUCT_OFFSET(short_policy_t, entries) + + size_t size = offsetof(short_policy_t, entries) + sizeof(short_policy_entry_t)*(n_entries); result = tor_malloc_zero(size); diff --git a/src/or/policies.h b/src/or/policies.h index ce08d497e9..52ff4e2f99 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -141,7 +141,7 @@ STATIC const tor_addr_port_t * fascist_firewall_choose_address( firewall_connection_t fw_connection, int pref_only, int pref_ipv6); -#endif +#endif /* defined(POLICIES_PRIVATE) */ -#endif +#endif /* !defined(TOR_POLICIES_H) */ diff --git a/src/or/proto_cell.c b/src/or/proto_cell.c new file mode 100644 index 0000000000..75eb2a7e7f --- /dev/null +++ b/src/or/proto_cell.c @@ -0,0 +1,84 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_cell.h" + +#include "connection_or.h" + +/** True iff the cell command <b>command</b> is one that implies a + * variable-length cell in Tor link protocol <b>linkproto</b>. */ +static inline int +cell_command_is_var_length(uint8_t command, int linkproto) +{ + /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells + * work as implemented here. If it's 1, there are no variable-length cells. + * Tor does not support other versions right now, and so can't negotiate + * them. + */ + switch (linkproto) { + case 1: + /* Link protocol version 1 has no variable-length cells. */ + return 0; + case 2: + /* In link protocol version 2, VERSIONS is the only variable-length cell */ + return command == CELL_VERSIONS; + case 0: + case 3: + default: + /* In link protocol version 3 and later, and in version "unknown", + * commands 128 and higher indicate variable-length. VERSIONS is + * grandfathered in. */ + return command == CELL_VERSIONS || command >= 128; + } +} + +/** Check <b>buf</b> for a variable-length cell according to the rules of link + * protocol version <b>linkproto</b>. If one is found, pull it off the buffer + * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. + * Return 0 if whatever is on the start of buf_t is not a variable-length + * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start + * of a variable-length cell on <b>buf</b>, but the whole thing isn't there + * yet. */ +int +fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) +{ + char hdr[VAR_CELL_MAX_HEADER_SIZE]; + var_cell_t *result; + uint8_t command; + uint16_t length; + const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; + const int circ_id_len = get_circ_id_size(wide_circ_ids); + const unsigned header_len = get_var_cell_header_size(wide_circ_ids); + *out = NULL; + if (buf_datalen(buf) < header_len) + return 0; + buf_peek(buf, hdr, header_len); + + command = get_uint8(hdr + circ_id_len); + if (!(cell_command_is_var_length(command, linkproto))) + return 0; + + length = ntohs(get_uint16(hdr + circ_id_len + 1)); + if (buf_datalen(buf) < (size_t)(header_len+length)) + return 1; + + result = var_cell_new(length); + result->command = command; + if (wide_circ_ids) + result->circ_id = ntohl(get_uint32(hdr)); + else + result->circ_id = ntohs(get_uint16(hdr)); + + buf_drain(buf, header_len); + buf_peek(buf, (char*) result->payload, length); + buf_drain(buf, length); + + *out = result; + return 1; +} + diff --git a/src/or/proto_cell.h b/src/or/proto_cell.h new file mode 100644 index 0000000000..bbc14b9a02 --- /dev/null +++ b/src/or/proto_cell.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CELL_H +#define TOR_PROTO_CELL_H + +struct buf_t; +struct var_cell_t; + +int fetch_var_cell_from_buf(struct buf_t *buf, struct var_cell_t **out, + int linkproto); + +#endif /* !defined(TOR_PROTO_CELL_H) */ + diff --git a/src/or/proto_control0.c b/src/or/proto_control0.c new file mode 100644 index 0000000000..c17ba34948 --- /dev/null +++ b/src/or/proto_control0.c @@ -0,0 +1,26 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_control0.h" + +/** Return 1 iff buf looks more like it has an (obsolete) v0 controller + * command on it than any valid v1 controller command. */ +int +peek_buf_has_control0_command(buf_t *buf) +{ + if (buf_datalen(buf) >= 4) { + char header[4]; + uint16_t cmd; + buf_peek(buf, header, sizeof(header)); + cmd = ntohs(get_uint16(header+2)); + if (cmd <= 0x14) + return 1; /* This is definitely not a v1 control command. */ + } + return 0; +} + diff --git a/src/or/proto_control0.h b/src/or/proto_control0.h new file mode 100644 index 0000000000..0cc8eacad0 --- /dev/null +++ b/src/or/proto_control0.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CONTROL0_H +#define TOR_PROTO_CONTROL0_H + +struct buf_t; +int peek_buf_has_control0_command(struct buf_t *buf); + +#endif /* !defined(TOR_PROTO_CONTROL0_H) */ + diff --git a/src/or/proto_ext_or.c b/src/or/proto_ext_or.c new file mode 100644 index 0000000000..057cf109ec --- /dev/null +++ b/src/or/proto_ext_or.c @@ -0,0 +1,40 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "ext_orport.h" +#include "proto_ext_or.h" + +/** 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; + + if (buf_datalen(buf) < EXT_OR_CMD_HEADER_SIZE) + return 0; + buf_peek(buf, hdr, sizeof(hdr)); + len = ntohs(get_uint16(hdr+2)); + if (buf_datalen(buf) < (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_drain(buf, EXT_OR_CMD_HEADER_SIZE); + buf_get_bytes(buf, (*out)->body, len); + return 1; +} + diff --git a/src/or/proto_ext_or.h b/src/or/proto_ext_or.h new file mode 100644 index 0000000000..cc504d18e3 --- /dev/null +++ b/src/or/proto_ext_or.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_EXT_OR_H +#define TOR_PROTO_EXT_OR_H + +struct buf_t; +struct ext_or_cmt_t; + +int fetch_ext_or_command_from_buf(struct buf_t *buf, + struct ext_or_cmd_t **out); + +#endif /* !defined(TOR_PROTO_EXT_OR_H) */ + diff --git a/src/or/proto_http.c b/src/or/proto_http.c new file mode 100644 index 0000000000..3762429e1e --- /dev/null +++ b/src/or/proto_http.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define PROTO_HTTP_PRIVATE +#include "or.h" +#include "buffers.h" +#include "proto_http.h" + +/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */ +int +peek_buf_has_http_command(const buf_t *buf) +{ + if (buf_peek_startswith(buf, "CONNECT ") || + buf_peek_startswith(buf, "DELETE ") || + buf_peek_startswith(buf, "GET ") || + buf_peek_startswith(buf, "POST ") || + buf_peek_startswith(buf, "PUT " )) + return 1; + return 0; +} + +/** There is a (possibly incomplete) http statement on <b>buf</b>, of the + * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) + * If a) the headers include a Content-Length field and all bytes in + * the body are present, or b) there's no Content-Length field and + * all headers are present, then: + * + * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. + * - memdup body into <b>*body_out</b>, and NUL-terminate it. + * - Then remove them from <b>buf</b>, and return 1. + * + * - If headers or body is NULL, discard that part of the buf. + * - If a headers or body doesn't fit in the arg, return -1. + * (We ensure that the headers or body don't exceed max len, + * _even if_ we're planning to discard them.) + * - If force_complete is true, then succeed even if not all of the + * content has arrived. + * + * Else, change nothing and return 0. + */ +int +fetch_from_buf_http(buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete) +{ + const char *headers; + size_t headerlen, bodylen, contentlen=0; + int crlf_offset; + int r; + + if (buf_datalen(buf) == 0) + return 0; + + crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); + if (crlf_offset > (int)max_headerlen || + (crlf_offset < 0 && buf_datalen(buf) > max_headerlen)) { + log_debug(LD_HTTP,"headers too long."); + return -1; + } else if (crlf_offset < 0) { + log_debug(LD_HTTP,"headers not all here yet."); + return 0; + } + /* Okay, we have a full header. Make sure it all appears in the first + * chunk. */ + headerlen = crlf_offset + 4; + size_t headers_in_chunk = 0; + buf_pullup(buf, headerlen, &headers, &headers_in_chunk); + + bodylen = buf_datalen(buf) - headerlen; + log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); + + if (max_headerlen <= headerlen) { + log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", + (int)headerlen, (int)max_headerlen-1); + return -1; + } + if (max_bodylen <= bodylen) { + log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", + (int)bodylen, (int)max_bodylen-1); + return -1; + } + + r = buf_http_find_content_length(headers, headerlen, &contentlen); + if (r == -1) { + log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " + "someone is trying to crash us."); + return -1; + } else if (r == 1) { + /* if content-length is malformed, then our body length is 0. fine. */ + log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); + if (bodylen < contentlen) { + if (!force_complete) { + log_debug(LD_HTTP,"body not all here yet."); + return 0; /* not all there yet */ + } + } + if (bodylen > contentlen) { + bodylen = contentlen; + log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); + } + } else { + tor_assert(r == 0); + /* Leave bodylen alone */ + } + + /* all happy. copy into the appropriate places, and return 1 */ + if (headers_out) { + *headers_out = tor_malloc(headerlen+1); + buf_get_bytes(buf, *headers_out, headerlen); + (*headers_out)[headerlen] = 0; /* NUL terminate it */ + } + if (body_out) { + tor_assert(body_used); + *body_used = bodylen; + *body_out = tor_malloc(bodylen+1); + buf_get_bytes(buf, *body_out, bodylen); + (*body_out)[bodylen] = 0; /* NUL terminate it */ + } + return 1; +} + +/** + * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at + * <b>headers</b>, looking for a "Content-Length" header. Try to set + * *<b>result_out</b> to the numeric value of that header if possible. + * Return -1 if the header was malformed, 0 if it was missing, and 1 if + * it was present and well-formed. + */ +STATIC int +buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out) +{ + const char *p, *newline; + char *len_str, *eos=NULL; + size_t remaining, result; + int ok; + *result_out = 0; /* The caller shouldn't look at this unless the + * return value is 1, but let's prevent confusion */ + +#define CONTENT_LENGTH "\r\nContent-Length: " + p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); + if (p == NULL) + return 0; + + tor_assert(p >= headers && p < headers+headerlen); + remaining = (headers+headerlen)-p; + p += strlen(CONTENT_LENGTH); + remaining -= strlen(CONTENT_LENGTH); + + newline = memchr(p, '\n', remaining); + if (newline == NULL) + return -1; + + len_str = tor_memdup_nulterm(p, newline-p); + /* We limit the size to INT_MAX because other parts of the buffer.c + * code don't like buffers to be any bigger than that. */ + result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); + if (eos && !tor_strisspace(eos)) { + ok = 0; + } else { + *result_out = result; + } + tor_free(len_str); + + return ok ? 1 : -1; +} + diff --git a/src/or/proto_http.h b/src/or/proto_http.h new file mode 100644 index 0000000000..805686070f --- /dev/null +++ b/src/or/proto_http.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_HTTP_H +#define TOR_PROTO_HTTP_H + +struct buf_t; + +int fetch_from_buf_http(struct buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete); +int peek_buf_has_http_command(const struct buf_t *buf); + +#ifdef PROTO_HTTP_PRIVATE +STATIC int buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out); +#endif + +#endif /* !defined(TOR_PROTO_HTTP_H) */ + diff --git a/src/or/proto_socks.c b/src/or/proto_socks.c new file mode 100644 index 0000000000..7649fcc4be --- /dev/null +++ b/src/or/proto_socks.c @@ -0,0 +1,710 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "addressmap.h" +#include "buffers.h" +#include "control.h" +#include "config.h" +#include "ext_orport.h" +#include "proto_socks.h" +#include "reasons.h" + +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); +static int parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out); +/** + * Wait this many seconds before warning the user about using SOCKS unsafely + * again. */ +#define SOCKS_WARN_INTERVAL 5 + +/** Warn that the user application has made an unsafe socks request using + * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than + * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ +static void +log_unsafe_socks_warning(int socks_protocol, const char *address, + uint16_t port, int safe_socks) +{ + static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); + + if (safe_socks) { + log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, + "Your application (using socks%d to port %d) is giving " + "Tor only an IP address. Applications that do DNS resolves " + "themselves may leak information. Consider using Socks4A " + "(e.g. via privoxy or socat) instead. For more information, " + "please see https://wiki.torproject.org/TheOnionRouter/" + "TorFAQ#SOCKSAndDNS.%s", + socks_protocol, + (int)port, + safe_socks ? " Rejecting." : ""); + } + control_event_client_status(LOG_WARN, + "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", + socks_protocol, address, (int)port); +} + +/** Do not attempt to parse socks messages longer than this. This value is + * actually significantly higher than the longest possible socks message. */ +#define MAX_SOCKS_MESSAGE_LEN 512 + +/** Return a new socks_request_t. */ +socks_request_t * +socks_request_new(void) +{ + return tor_malloc_zero(sizeof(socks_request_t)); +} + +/** Free all storage held in the socks_request_t <b>req</b>. */ +void +socks_request_free(socks_request_t *req) +{ + if (!req) + return; + if (req->username) { + memwipe(req->username, 0x10, req->usernamelen); + tor_free(req->username); + } + if (req->password) { + memwipe(req->password, 0x04, req->passwordlen); + tor_free(req->password); + } + memwipe(req, 0xCC, sizeof(socks_request_t)); + tor_free(req); +} + +/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one + * of the forms + * - socks4: "socksheader username\\0" + * - socks4a: "socksheader username\\0 destaddr\\0" + * - socks5 phase one: "version #methods methods" + * - socks5 phase two: "version command 0 addresstype..." + * If it's a complete and valid handshake, and destaddr fits in + * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, + * assign to <b>req</b>, and return 1. + * + * If it's invalid or too big, return -1. + * + * Else it's not all there yet, leave buf alone and return 0. + * + * If you want to specify the socks reply, write it into <b>req->reply</b> + * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. + * + * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether + * the connection is possibly leaking DNS requests locally or not. + * + * If <b>safe_socks</b> is true, then reject unsafe socks protocols. + * + * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are + * undefined. + */ +int +fetch_from_buf_socks(buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks) +{ + int res; + ssize_t n_drain; + size_t want_length = 128; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) /* version and another byte */ + return 0; + + do { + n_drain = 0; + buf_pullup(buf, want_length, &head, &datalen); + tor_assert(head && datalen >= 2); + want_length = 0; + + res = parse_socks(head, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + buf_clear(buf); + else if (n_drain > 0) + buf_drain(buf, n_drain); + + } while (res == 0 && head && want_length < buf_datalen(buf) && + buf_datalen(buf) >= 2); + + return res; +} + +/** 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. +} + +static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>This is a SOCKS Proxy, Not An HTTP Proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>This is a SOCKs proxy, not an HTTP proxy.</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use this Tor port as\n" + "an HTTP proxy.\n" + "</p><p>\n" + "This is not correct: This port is configured as a SOCKS proxy, not\n" + "an HTTP proxy. If you need an HTTP proxy tunnel, use the HTTPTunnelPort\n" + "configuration option in place of, or in addition to, SOCKSPort.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "</p>\n" + "</body>\n" + "</html>\n"; + +/** Implementation helper to implement fetch_from_*_socks. Instead of looking + * at a buffer's contents, we look at the <b>datalen</b> bytes of data in + * <b>data</b>. Instead of removing data from the buffer, we set + * <b>drain_out</b> to the amount of data that should be removed (or -1 if the + * buffer should be cleared). Instead of pulling more data into the first + * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes + * we'd like to see in the input buffer, if they're available. */ +static int +parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out) +{ + unsigned int len; + char tmpbuf[TOR_ADDR_BUF_LEN+1]; + tor_addr_t destaddr; + uint32_t destip; + uint8_t socksver; + char *next, *startaddr; + unsigned char usernamelen, passlen; + struct in_addr in; + + if (datalen < 2) { + /* We always need at least 2 bytes. */ + *want_length_out = 2; + return 0; + } + + if (req->socks_version == 5 && !req->got_auth) { + /* See if we have received authentication. Strictly speaking, we should + also check whether we actually negotiated username/password + authentication. But some broken clients will send us authentication + even if we negotiated SOCKS_NO_AUTH. */ + if (*data == 1) { /* username/pass version 1 */ + /* Format is: authversion [1 byte] == 1 + usernamelen [1 byte] + username [usernamelen bytes] + passlen [1 byte] + password [passlen bytes] */ + usernamelen = (unsigned char)*(data + 1); + if (datalen < 2u + usernamelen + 1u) { + *want_length_out = 2u + usernamelen + 1u; + return 0; + } + passlen = (unsigned char)*(data + 2u + usernamelen); + if (datalen < 2u + usernamelen + 1u + passlen) { + *want_length_out = 2u + usernamelen + 1u + passlen; + return 0; + } + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 1; /* authversion == 1 */ + req->reply[1] = 0; /* authentication successful */ + log_debug(LD_APP, + "socks5: Accepted username/password without checking."); + if (usernamelen) { + req->username = tor_memdup(data+2u, usernamelen); + req->usernamelen = usernamelen; + } + if (passlen) { + req->password = tor_memdup(data+3u+usernamelen, passlen); + req->passwordlen = passlen; + } + *drain_out = 2u + usernamelen + 1u + passlen; + req->got_auth = 1; + *want_length_out = 7; /* Minimal socks5 command. */ + return 0; + } else if (req->auth_type == SOCKS_USER_PASS) { + /* unknown version byte */ + log_warn(LD_APP, "Socks5 username/password version %d not recognized; " + "rejecting.", (int)*data); + return -1; + } + } + + socksver = *data; + + switch (socksver) { /* which version of socks? */ + case 5: /* socks5 */ + + if (req->socks_version != 5) { /* we need to negotiate a method */ + unsigned char nummethods = (unsigned char)*(data+1); + int have_user_pass, have_no_auth; + int r=0; + tor_assert(!req->socks_version); + if (datalen < 2u+nummethods) { + *want_length_out = 2u+nummethods; + return 0; + } + if (!nummethods) + return -1; + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; /* socks5 reply */ + have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); + have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); + if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { + req->auth_type = SOCKS_USER_PASS; + req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" + auth method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); + r=0; + } else if (have_no_auth) { + req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth + method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); + r=0; + } else { + log_warn(LD_APP, + "socks5: offered methods don't include 'no auth' or " + "username/password. Rejecting."); + req->reply[1] = '\xFF'; /* reject all methods */ + r=-1; + } + /* Remove packet from buf. Some SOCKS clients will have sent extra + * junk at this point; let's hope it's an authentication message. */ + *drain_out = 2u + nummethods; + + return r; + } + if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { + log_warn(LD_APP, + "socks5: negotiated authentication, but none provided"); + return -1; + } + /* we know the method; read in the request */ + log_debug(LD_APP,"socks5: checking request"); + if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ + *want_length_out = 7; + return 0; /* not yet */ + } + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + 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; + } + switch (*(data+3)) { /* address type */ + case 1: /* IPv4 address */ + case 4: /* IPv6 address */ { + const int is_v6 = *(data+3) == 4; + const unsigned addrlen = is_v6 ? 16 : 4; + log_debug(LD_APP,"socks5: ipv4 address type"); + if (datalen < 6+addrlen) {/* ip/port there? */ + *want_length_out = 6+addrlen; + return 0; /* not yet */ + } + + if (is_v6) + tor_addr_from_ipv6_bytes(&destaddr, data+4); + else + tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); + + tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); + + if (BUG(strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- This branch is unreachable, given the + * size of tmpbuf and the actual value of 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.", + (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); + return -1; + /* LCOV_EXCL_STOP */ + } + strlcpy(req->address,tmpbuf,sizeof(req->address)); + req->port = ntohs(get_uint16(data+4+addrlen)); + *drain_out = 6+addrlen; + if (req->command != SOCKS_COMMAND_RESOLVE_PTR && + !addressmap_have_mapping(req->address,0)) { + log_unsafe_socks_warning(5, req->address, req->port, safe_socks); + 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; + } + len = (unsigned char)*(data+4); + if (datalen < 7+len) { /* addr/port there? */ + *want_length_out = 7+len; + return 0; /* not yet */ + } + if (BUG(len+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- unreachable, since len is at most 255, + * and MAX_SOCKS_ADDR_LEN is 256. */ + 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); + return -1; + /* LCOV_EXCL_STOP */ + } + memcpy(req->address,data+5,len); + req->address[len] = 0; + req->port = ntohs(get_uint16(data+5+len)); + *drain_out = 5+len+2; + + 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.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks5 to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "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; + } + tor_assert(0); + break; + case 4: { /* socks4 */ + enum {socks4, socks4a} socks4_prot = socks4a; + const char *authstart, *authend; + /* http://ss5.sourceforge.net/socks4.protocol.txt */ + /* http://ss5.sourceforge.net/socks4A.protocol.txt */ + + req->socks_version = 4; + if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ + *want_length_out = SOCKS4_NETWORK_LEN; + return 0; /* not yet */ + } + // buf_pullup(buf, 1280); + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + req->command != SOCKS_COMMAND_RESOLVE) { + /* not a connect or resolve? we don't support it. (No resolve_ptr with + * socks4.) */ + log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", + req->command); + return -1; + } + + req->port = ntohs(get_uint16(data+2)); + destip = ntohl(get_uint32(data+4)); + if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { + log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); + return -1; + } + if (destip >> 8) { + log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); + in.s_addr = htonl(destip); + tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + if (BUG(strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- This branch is unreachable, given the + * size of tmpbuf and the actual value of MAX_SOCKS_ADDR_LEN */ + log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", + (int)strlen(tmpbuf)); + return -1; + /* LCOV_EXCL_STOP */ + } + log_debug(LD_APP, + "socks4: successfully read destip (%s)", + safe_str_client(tmpbuf)); + socks4_prot = socks4; + } + + authstart = data + SOCKS4_NETWORK_LEN; + next = memchr(authstart, 0, + datalen-SOCKS4_NETWORK_LEN); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP, "Socks4 user name too long; rejecting."); + return -1; + } + log_debug(LD_APP,"socks4: Username not here yet."); + *want_length_out = datalen+1024; /* More than we need, but safe */ + return 0; + } + authend = next; + tor_assert(next < data+datalen); + + startaddr = NULL; + if (socks4_prot != socks4a && + !addressmap_have_mapping(tmpbuf,0)) { + log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); + + if (safe_socks) + return -1; + } + if (socks4_prot == socks4a) { + if (next+1 == data+datalen) { + log_debug(LD_APP,"socks4: No part of destaddr here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + startaddr = next+1; + next = memchr(startaddr, 0, data + datalen - startaddr); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP,"socks4: Destaddr too long."); + return -1; + } + log_debug(LD_APP,"socks4: Destaddr not all here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { + log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); + return -1; + } + // tor_assert(next < buf->cur+buf_datalen(buf)); + + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks4a to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); + } + log_debug(LD_APP,"socks4: Everything is here. Success."); + strlcpy(req->address, startaddr ? startaddr : tmpbuf, + sizeof(req->address)); + if (!string_is_valid_hostname(req->address)) { + log_warn(LD_PROTOCOL, + "Your application (using socks4 to port %d) gave Tor " + "a malformed hostname: %s. Rejecting the connection.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (authend != authstart) { + req->got_auth = 1; + req->usernamelen = authend - authstart; + req->username = tor_memdup(authstart, authend - authstart); + } + /* next points to the final \0 on inbuf */ + *drain_out = next - data + 1; + return 1; + } + case 'G': /* get */ + case 'H': /* head */ + case 'P': /* put/post */ + case 'C': /* connect */ + strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG, + MAX_SOCKS_REPLY_LEN); + req->replylen = strlen((char*)req->reply)+1; + /* fall through */ + default: /* version is not socks4 or socks5 */ + log_warn(LD_APP, + "Socks version %d not recognized. (This port is not an " + "HTTP proxy; did you want to use HTTPTunnelPort?)", + *(data)); + { + /* Tell the controller the first 8 bytes. */ + char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); + control_event_client_status(LOG_WARN, + "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", + escaped(tmp)); + tor_free(tmp); + } + return -1; + } +} + +/** Inspect a reply from SOCKS server stored in <b>buf</b> according + * to <b>state</b>, removing the protocol data upon success. Return 0 on + * incomplete response, 1 on success and -1 on error, in which case + * <b>reason</b> is set to a descriptive message (free() when finished + * with it). + * + * As a special case, 2 is returned when user/pass is required + * during SOCKS5 handshake and user/pass is configured. + */ +int +fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) +{ + ssize_t drain = 0; + int r; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) + return 0; + + buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, &head, &datalen); + tor_assert(head && datalen >= 2); + + r = parse_socks_client((uint8_t*)head, datalen, + state, reason, &drain); + if (drain > 0) + buf_drain(buf, drain); + else if (drain < 0) + buf_clear(buf); + + return r; +} + +/** Implementation logic for fetch_from_*_socks_client. */ +static int +parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out) +{ + unsigned int addrlen; + *drain_out = 0; + if (datalen < 2) + return 0; + + switch (state) { + case PROXY_SOCKS4_WANT_CONNECT_OK: + /* Wait for the complete response */ + if (datalen < 8) + return 0; + + if (data[1] != 0x5a) { + *reason = tor_strdup(socks4_response_code_to_string(data[1])); + return -1; + } + + /* Success */ + *drain_out = 8; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: + /* we don't have any credentials */ + if (data[1] != 0x00) { + *reason = tor_strdup("server doesn't support any of our " + "available authentication methods"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: + /* we have a username and password. return 1 if we can proceed without + * providing authentication, or 2 otherwise. */ + switch (data[1]) { + case 0x00: + log_info(LD_NET, "SOCKS 5 client: we have auth details but server " + "doesn't require authentication."); + *drain_out = -1; + return 1; + case 0x02: + log_info(LD_NET, "SOCKS 5 client: need authentication."); + *drain_out = -1; + return 2; + /* fall through */ + } + + *reason = tor_strdup("server doesn't support any of our available " + "authentication methods"); + return -1; + + case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: + /* handle server reply to rfc1929 authentication */ + if (data[1] != 0x00) { + *reason = tor_strdup("authentication failed"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: authentication successful."); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_CONNECT_OK: + /* response is variable length. BND.ADDR, etc, isn't needed + * (don't bother with buf_pullup()), but make sure to eat all + * the data used */ + + /* wait for address type field to arrive */ + if (datalen < 4) + return 0; + + switch (data[3]) { + case 0x01: /* ip4 */ + addrlen = 4; + break; + case 0x04: /* ip6 */ + addrlen = 16; + break; + case 0x03: /* fqdn (can this happen here?) */ + if (datalen < 5) + return 0; + addrlen = 1 + data[4]; + break; + default: + *reason = tor_strdup("invalid response to connect request"); + return -1; + } + + /* wait for address and port */ + if (datalen < 6 + addrlen) + return 0; + + if (data[1] != 0x00) { + *reason = tor_strdup(socks5_response_code_to_string(data[1])); + return -1; + } + + *drain_out = 6 + addrlen; + return 1; + } + + /* LCOV_EXCL_START */ + /* shouldn't get here if the input state is one we know about... */ + tor_assert(0); + + return -1; + /* LCOV_EXCL_STOP */ +} + diff --git a/src/or/proto_socks.h b/src/or/proto_socks.h new file mode 100644 index 0000000000..a714151414 --- /dev/null +++ b/src/or/proto_socks.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_SOCKS_H +#define TOR_PROTO_SOCKS_H + +struct socks_request_t; +struct buf_t; + +struct socks_request_t *socks_request_new(void); +void socks_request_free(struct socks_request_t *req); +int fetch_from_buf_socks(struct buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks); +int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); + +#endif /* !defined(TOR_PROTO_SOCKS_H) */ + diff --git a/src/or/protover.h b/src/or/protover.h index 22667bed79..657977279e 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -17,6 +17,13 @@ /* This is a guess. */ #define FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS "0.2.9.3-alpha" +/** The protover version number that signifies HSDir support for HSv3 */ +#define PROTOVER_HSDIR_V3 2 +/** The protover version number that signifies HSv3 intro point support */ +#define PROTOVER_HS_INTRO_V3 4 +/** The protover version number that signifies HSv3 rendezvous point support */ +#define PROTOVER_HS_RENDEZVOUS_POINT_V3 2 + /** List of recognized subprotocols. */ typedef enum protocol_type_t { PRT_LINK, @@ -68,7 +75,7 @@ STATIC void proto_entry_free(proto_entry_t *entry); STATIC char *encode_protocol_list(const smartlist_t *sl); STATIC const char *protocol_type_to_str(protocol_type_t pr); STATIC int str_to_protocol_type(const char *s, protocol_type_t *pr_out); -#endif +#endif /* defined(PROTOVER_PRIVATE) */ -#endif +#endif /* !defined(TOR_PROTOVER_H) */ diff --git a/src/or/reasons.c b/src/or/reasons.c index e6c325f1b3..03d49418da 100644 --- a/src/or/reasons.c +++ b/src/or/reasons.c @@ -45,6 +45,8 @@ stream_end_reason_to_control_string(int reason) case END_STREAM_REASON_CANT_ATTACH: return "CANT_ATTACH"; case END_STREAM_REASON_NET_UNREACHABLE: return "NET_UNREACHABLE"; case END_STREAM_REASON_SOCKSPROTOCOL: return "SOCKS_PROTOCOL"; + // XXXX Controlspec + case END_STREAM_REASON_HTTPPROTOCOL: return "HTTP_PROTOCOL"; case END_STREAM_REASON_PRIVATE_ADDR: return "PRIVATE_ADDR"; @@ -138,6 +140,11 @@ stream_end_reason_to_socks5_response(int reason) return SOCKS5_NET_UNREACHABLE; case END_STREAM_REASON_SOCKSPROTOCOL: return SOCKS5_GENERAL_ERROR; + case END_STREAM_REASON_HTTPPROTOCOL: + // LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + return SOCKS5_GENERAL_ERROR; + // LCOV_EXCL_STOP case END_STREAM_REASON_PRIVATE_ADDR: return SOCKS5_GENERAL_ERROR; @@ -160,7 +167,7 @@ stream_end_reason_to_socks5_response(int reason) #else #define E_CASE(s) case s #define S_CASE(s) case s -#endif +#endif /* defined(_WIN32) */ /** Given an errno from a failed exit connection, return a reason code * appropriate for use in a RELAY END cell. */ @@ -442,3 +449,48 @@ bandwidth_weight_rule_to_string(bandwidth_weight_rule_t rule) } } +/** Given a RELAY_END reason value, convert it to an HTTP response to be + * send over an HTTP tunnel connection. */ +const char * +end_reason_to_http_connect_response_line(int endreason) +{ + endreason &= END_STREAM_REASON_MASK; + /* XXXX these are probably all wrong. Should they all be 502? */ + switch (endreason) { + case 0: + return "HTTP/1.0 200 OK\r\n\r\n"; + case END_STREAM_REASON_MISC: + return "HTTP/1.0 500 Internal Server Error\r\n\r\n"; + case END_STREAM_REASON_RESOLVEFAILED: + return "HTTP/1.0 404 Not Found (resolve failed)\r\n\r\n"; + case END_STREAM_REASON_NOROUTE: + return "HTTP/1.0 404 Not Found (no route)\r\n\r\n"; + case END_STREAM_REASON_CONNECTREFUSED: + return "HTTP/1.0 403 Forbidden (connection refused)\r\n\r\n"; + case END_STREAM_REASON_EXITPOLICY: + return "HTTP/1.0 403 Forbidden (exit policy)\r\n\r\n"; + case END_STREAM_REASON_DESTROY: + return "HTTP/1.0 502 Bad Gateway (destroy cell received)\r\n\r\n"; + case END_STREAM_REASON_DONE: + return "HTTP/1.0 502 Bad Gateway (unexpected close)\r\n\r\n"; + case END_STREAM_REASON_TIMEOUT: + return "HTTP/1.0 504 Gateway Timeout\r\n\r\n"; + case END_STREAM_REASON_HIBERNATING: + return "HTTP/1.0 502 Bad Gateway (hibernating server)\r\n\r\n"; + case END_STREAM_REASON_INTERNAL: + return "HTTP/1.0 502 Bad Gateway (internal error)\r\n\r\n"; + case END_STREAM_REASON_RESOURCELIMIT: + return "HTTP/1.0 502 Bad Gateway (resource limit)\r\n\r\n"; + case END_STREAM_REASON_CONNRESET: + return "HTTP/1.0 403 Forbidden (connection reset)\r\n\r\n"; + case END_STREAM_REASON_TORPROTOCOL: + return "HTTP/1.0 502 Bad Gateway (tor protocol violation)\r\n\r\n"; + case END_STREAM_REASON_ENTRYPOLICY: + return "HTTP/1.0 403 Forbidden (entry policy violation)\r\n\r\n"; + case END_STREAM_REASON_NOTDIRECTORY: /* Fall Through */ + default: + tor_assert_nonfatal_unreached(); + return "HTTP/1.0 500 Internal Server Error (weird end reason)\r\n\r\n"; + } +} + diff --git a/src/or/reasons.h b/src/or/reasons.h index 1cadf4e89e..3d6ba8fc83 100644 --- a/src/or/reasons.h +++ b/src/or/reasons.h @@ -26,6 +26,7 @@ const char *socks4_response_code_to_string(uint8_t code); const char *socks5_response_code_to_string(uint8_t code); const char *bandwidth_weight_rule_to_string(enum bandwidth_weight_rule_t rule); +const char *end_reason_to_http_connect_response_line(int endreason); -#endif +#endif /* !defined(TOR_REASONS_H) */ diff --git a/src/or/relay.c b/src/or/relay.c index a86ae9bc43..66e10567c1 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -185,18 +185,12 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell) /** Apply <b>cipher</b> to CELL_PAYLOAD_SIZE bytes of <b>in</b> * (in place). * - * If <b>encrypt_mode</b> is 1 then encrypt, else decrypt. - * - * Returns 0. + * Note that we use the same operation for encrypting and for decrypting. */ -static int -relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in, - int encrypt_mode) +static void +relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in) { - (void)encrypt_mode; crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE); - - return 0; } /** @@ -450,8 +444,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, do { /* Remember: cpath is in forward order, that is, first hop first. */ tor_assert(thishop); - if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0) - return -1; + /* decrypt one layer */ + relay_crypt_one_payload(thishop->b_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -468,19 +462,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, log_fn(LOG_PROTOCOL_WARN, LD_OR, "Incoming cell at client not recognized. Closing."); return -1; - } else { /* we're in the middle. Just one crypt. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, - cell->payload, 1) < 0) - return -1; -// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not " -// "the client."); + } else { + /* We're in the middle. Encrypt one layer. */ + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload); } } else /* cell_direction == CELL_DIRECTION_OUT */ { - /* we're in the middle. Just one crypt. */ + /* We're in the middle. Decrypt one layer. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, - cell->payload, 0) < 0) - return -1; + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -516,7 +505,15 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, chan = circ->n_chan; if (!chan) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d has n_chan==NULL." - " Dropping.", filename, lineno); + " Dropping. Circuit is in state %s (%d), and is " + "%smarked for close. (%s:%d, %d)", filename, lineno, + circuit_state_to_string(circ->state), circ->state, + circ->marked_for_close ? "" : "not ", + circ->marked_for_close_file?circ->marked_for_close_file:"", + circ->marked_for_close, circ->marked_for_close_reason); + if (CIRCUIT_IS_ORIGIN(circ)) { + circuit_log_path(LOG_WARN, LD_BUG, TO_ORIGIN_CIRCUIT(circ)); + } log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } @@ -533,11 +530,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, /* moving from farthest to nearest hop */ do { tor_assert(thishop); - /* XXXX RD This is a bug, right? */ - log_debug(LD_OR,"crypting a layer of the relay cell."); - if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) { - return -1; - } + log_debug(LD_OR,"encrypting a layer of the relay cell."); + relay_crypt_one_payload(thishop->f_crypto, cell->payload); thishop = thishop->prev; } while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev); @@ -554,8 +548,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, or_circ = TO_OR_CIRCUIT(circ); chan = or_circ->p_chan; relay_set_digest(or_circ->p_digest, cell); - if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0) - return -1; + /* encrypt one layer */ + relay_crypt_one_payload(or_circ->p_crypto, cell->payload); } ++stats_n_relay_cells_relayed; @@ -843,7 +837,7 @@ connection_edge_send_command(edge_connection_t *fromconn, if (linked_conn && linked_conn->type == CONN_TYPE_DIR) { ++(TO_DIR_CONN(linked_conn)->data_cells_sent); } -#endif +#endif /* defined(MEASUREMENTS_21206) */ return relay_send_command_from_edge(fromconn->stream_id, circ, relay_command, payload, @@ -1495,8 +1489,9 @@ connection_edge_process_relay_cell_not_open( circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ)); /* don't send a socks reply to transparent conns */ tor_assert(entry_conn->socks_request != NULL); - if (!entry_conn->socks_request->has_finished) + if (!entry_conn->socks_request->has_finished) { connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0); + } /* Was it a linked dir conn? If so, a dir request just started to * fetch something; this could be a bootstrap status milestone. */ @@ -1698,7 +1693,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } stats_n_data_bytes_received += rh.length; - connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE), + connection_buf_add((char*)(cell->payload + RELAY_HEADER_SIZE), rh.length, TO_CONN(conn)); #ifdef MEASUREMENTS_21206 @@ -1709,7 +1704,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (linked_conn && linked_conn->type == CONN_TYPE_DIR) { ++(TO_DIR_CONN(linked_conn)->data_cells_received); } -#endif +#endif /* defined(MEASUREMENTS_21206) */ if (!optimistic_data) { /* Only send a SENDME if we're not getting optimistic data; otherwise @@ -2066,13 +2061,13 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, /* XXXX We could be more efficient here by sometimes packing * previously-sent optimistic data in the same cell with data * from the inbuf. */ - fetch_from_buf(payload, length, entry_conn->sending_optimistic_data); + buf_get_bytes(entry_conn->sending_optimistic_data, payload, length); if (!buf_datalen(entry_conn->sending_optimistic_data)) { buf_free(entry_conn->sending_optimistic_data); entry_conn->sending_optimistic_data = NULL; } } else { - connection_fetch_from_buf(payload, length, TO_CONN(conn)); + connection_buf_get_bytes(payload, length, TO_CONN(conn)); } log_debug(domain,TOR_SOCKET_T_FORMAT": Packaging %d bytes (%d waiting).", @@ -2084,7 +2079,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, retry */ if (!entry_conn->pending_optimistic_data) entry_conn->pending_optimistic_data = buf_new(); - write_to_buf(payload, length, entry_conn->pending_optimistic_data); + buf_add(entry_conn->pending_optimistic_data, payload, length); } if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, @@ -2407,7 +2402,7 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) assert_circuit_mux_okay(chan) #else #define assert_cmux_ok_paranoid(chan) -#endif +#endif /* defined(ACTIVE_CIRCUITS_PARANOIA) */ /** The total number of cells we have allocated. */ static size_t total_cells_allocated = 0; @@ -2950,7 +2945,7 @@ get_max_middle_cells(void) { return ORCIRC_MAX_MIDDLE_CELLS; } -#endif +#endif /* 0 */ /** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b> * transmitting in <b>direction</b>. */ @@ -3060,7 +3055,7 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, } } } -#endif +#endif /* 0 */ cell_queue_append_packed_copy(circ, queue, exitward, cell, chan->wide_circ_ids, 1); diff --git a/src/or/relay.h b/src/or/relay.h index 9dc0b5d3a2..4cc1a0fbdb 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -112,7 +112,7 @@ STATIC packed_cell_t *cell_queue_pop(cell_queue_t *queue); STATIC destroy_cell_t *destroy_cell_queue_pop(destroy_cell_queue_t *queue); STATIC size_t cell_queues_get_total_allocation(void); STATIC int cell_queues_check_size(void); -#endif +#endif /* defined(RELAY_PRIVATE) */ -#endif +#endif /* !defined(TOR_RELAY_H) */ diff --git a/src/or/rendcache.c b/src/or/rendcache.c index 11b60b36a1..b98b2bccfa 100644 --- a/src/or/rendcache.c +++ b/src/or/rendcache.c @@ -303,7 +303,7 @@ void rend_cache_purge(void) { if (rend_cache) { - log_info(LD_REND, "Purging HS descriptor cache"); + log_info(LD_REND, "Purging HS v2 descriptor cache"); strmap_free(rend_cache, rend_cache_entry_free_); } rend_cache = strmap_new(); @@ -315,7 +315,7 @@ void rend_cache_failure_purge(void) { if (rend_cache_failure) { - log_info(LD_REND, "Purging HS failure cache"); + log_info(LD_REND, "Purging HS v2 failure cache"); strmap_free(rend_cache_failure, rend_cache_failure_entry_free_); } rend_cache_failure = strmap_new(); @@ -512,7 +512,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) tor_assert(rend_cache); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } @@ -558,7 +558,7 @@ rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e) tor_assert(rend_cache_local_service); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } diff --git a/src/or/rendcache.h b/src/or/rendcache.h index 1bd3be2243..5b13eadfa1 100644 --- a/src/or/rendcache.h +++ b/src/or/rendcache.h @@ -115,8 +115,8 @@ extern strmap_t *rend_cache; extern strmap_t *rend_cache_failure; extern digestmap_t *rend_cache_v2_dir; extern size_t rend_cache_total_allocation; -#endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ +#endif /* defined(RENDCACHE_PRIVATE) */ -#endif /* TOR_RENDCACHE_H */ +#endif /* !defined(TOR_RENDCACHE_H) */ diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 9e2daf0380..3274819241 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -17,6 +17,8 @@ #include "connection_edge.h" #include "directory.h" #include "hs_common.h" +#include "hs_circuit.h" +#include "hs_client.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -41,7 +43,7 @@ rend_client_purge_state(void) rend_cache_purge(); rend_cache_failure_purge(); rend_client_cancel_descriptor_fetches(); - rend_client_purge_last_hid_serv_requests(); + hs_purge_last_hid_serv_requests(); } /** Called when we've established a circuit to an introduction point: @@ -88,46 +90,6 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ) return 0; } -/** Extend the introduction circuit <b>circ</b> to another valid - * introduction point for the hidden service it is trying to connect - * to, or mark it and launch a new circuit if we can't extend it. - * Return 0 on success or possible success. Return -1 and mark the - * introduction circuit for close on permanent failure. - * - * On failure, the caller is responsible for marking the associated - * rendezvous circuit for close. */ -static int -rend_client_reextend_intro_circuit(origin_circuit_t *circ) -{ - extend_info_t *extend_info; - int result; - extend_info = rend_client_get_random_intro(circ->rend_data); - if (!extend_info) { - log_warn(LD_REND, - "No usable introduction points left for %s. Closing.", - safe_str_client(rend_data_get_address(circ->rend_data))); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); - return -1; - } - // XXX: should we not re-extend if hs_circ_has_timed_out? - if (circ->remaining_relay_early_cells) { - log_info(LD_REND, - "Re-extending circ %u, this time to %s.", - (unsigned)circ->base_.n_circ_id, - safe_str_client(extend_info_describe(extend_info))); - result = circuit_extend_to_new_exit(circ, extend_info); - } else { - log_info(LD_REND, - "Closing intro circ %u (out of RELAY_EARLY cells).", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); - /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ - result = 0; - } - extend_info_free(extend_info); - return result; -} - /** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell * down introcirc if possible. */ @@ -201,7 +163,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, introcirc->build_state->chosen_exit)), smartlist_len(entry->parsed->intro_nodes)); - if (rend_client_reextend_intro_circuit(introcirc)) { + if (hs_client_reextend_intro_circuit(introcirc)) { status = -2; goto perm_err; } else { @@ -290,10 +252,9 @@ rend_client_send_introduction(origin_circuit_t *introcirc, goto perm_err; } - note_crypto_pk_op(REND_CLIENT); - /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg, + /*XXX maybe give crypto_pk_obsolete_public_hybrid_encrypt a max_len arg, * to avoid buffer overflows? */ - r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN, + r = crypto_pk_obsolete_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN, sizeof(payload)-DIGEST_LEN, tmp, (int)(dh_offset+DH_KEY_LEN), @@ -396,23 +357,11 @@ rend_client_introduction_acked(origin_circuit_t *circ, origin_circuit_t *rendcirc; (void) request; // XXXX Use this. - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { - log_warn(LD_PROTOCOL, - "Received REND_INTRODUCE_ACK on unexpected circuit %u.", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - tor_assert(circ->build_state); tor_assert(circ->build_state->chosen_exit); assert_circ_anonymity_ok(circ, options); tor_assert(circ->rend_data); - /* For path bias: This circuit was used successfully. Valid - * nacks and acks count. */ - pathbias_mark_use_success(circ); - if (request_len == 0) { /* It's an ACK; the introduction point relayed our introduction request. */ /* Locate the rend circ which is waiting to hear about this ack, @@ -454,7 +403,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, INTRO_POINT_FAILURE_GENERIC)>0) { /* There are introduction points left. Re-extend the circuit to * another intro point and try again. */ - int result = rend_client_reextend_intro_circuit(circ); + int result = hs_client_reextend_intro_circuit(circ); /* XXXX If that call failed, should we close the rend circuit, * too? */ return result; @@ -470,230 +419,6 @@ rend_client_introduction_acked(origin_circuit_t *circ, return 0; } -/** The period for which a hidden service directory cannot be queried for - * the same descriptor ID again. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) -/** Test networks generate a new consensus every 5 or 10 seconds. - * So allow them to requery HSDirs much faster. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) - -/** Return the period for which a hidden service directory cannot be queried - * for the same descriptor ID again, taking TestingTorNetwork into account. */ -static time_t -hsdir_requery_period(const or_options_t *options) -{ - tor_assert(options); - - if (options->TestingTorNetwork) { - return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; - } else { - return REND_HID_SERV_DIR_REQUERY_PERIOD; - } -} - -/** Contains the last request times to hidden service directories for - * certain queries; each key is a string consisting of the - * concatenation of a base32-encoded HS directory identity digest and - * base32-encoded HS descriptor ID; each value is a pointer to a time_t - * holding the time of the last request for that descriptor ID to that - * HS directory. */ -static strmap_t *last_hid_serv_requests_ = NULL; - -/** Returns last_hid_serv_requests_, initializing it to a new strmap if - * necessary. */ -static strmap_t * -get_last_hid_serv_requests(void) -{ - if (!last_hid_serv_requests_) - last_hid_serv_requests_ = strmap_new(); - return last_hid_serv_requests_; -} - -#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \ - REND_DESC_ID_V2_LEN_BASE32) - -/** Look up the last request time to hidden service directory <b>hs_dir</b> - * for descriptor ID <b>desc_id_base32</b>. If <b>set</b> is non-zero, - * assign the current time <b>now</b> and return that. Otherwise, return the - * most recent request time, or 0 if no such request has been sent before. - */ -static time_t -lookup_last_hid_serv_request(routerstatus_t *hs_dir, - const char *desc_id_base32, - time_t now, int set) -{ - char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1]; - time_t *last_request_ptr; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), - hs_dir->identity_digest, DIGEST_LEN); - tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s", - hsdir_id_base32, - desc_id_base32); - /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) == - LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (set) { - time_t *oldptr; - last_request_ptr = tor_malloc_zero(sizeof(time_t)); - *last_request_ptr = now; - oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, - last_request_ptr); - tor_free(oldptr); - } else - last_request_ptr = strmap_get_lc(last_hid_serv_requests, - hsdir_desc_comb_id); - return (last_request_ptr) ? *last_request_ptr : 0; -} - -/** Clean the history of request times to hidden service directories, so that - * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD - * seconds any more. */ -static void -directory_clean_last_hid_serv_requests(time_t now) -{ - strmap_iter_t *iter; - time_t cutoff = now - hsdir_requery_period(get_options()); - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - time_t *ent; - strmap_iter_get(iter, &key, &val); - ent = (time_t *) val; - if (*ent < cutoff) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(ent); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Remove all requests related to the descriptor ID <b>desc_id</b> from the - * history of times of requests to hidden service directories. - * <b>desc_id</b> is an unencoded descriptor ID of size DIGEST_LEN. - * - * 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 *desc_id) -{ - strmap_iter_t *iter; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - - /* Key is stored with the base32 encoded desc_id. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - strmap_iter_get(iter, &key, &val); - /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN - - REND_DESC_ID_V2_LEN_BASE32, - desc_id_base32, - REND_DESC_ID_V2_LEN_BASE32)) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(val); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Purge the history of request times to hidden service directories, - * so that future lookups of an HS descriptor will not fail because we - * accessed all of the HSDir relays responsible for the descriptor - * recently. */ -void -rend_client_purge_last_hid_serv_requests(void) -{ - /* Don't create the table if it doesn't exist yet (and it may very - * well not exist if the user hasn't accessed any HSes)... */ - strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; - /* ... and let get_last_hid_serv_requests re-create it for us if - * necessary. */ - last_hid_serv_requests_ = NULL; - - if (old_last_hid_serv_requests != NULL) { - log_info(LD_REND, "Purging client last-HS-desc-request-time table"); - strmap_free(old_last_hid_serv_requests, tor_free_); - } -} - -/** This returns a good valid hs dir that should be used for the given - * descriptor id. - * - * Return NULL on error else the hsdir node pointer. */ -static routerstatus_t * -pick_hsdir(const char *desc_id, const char *desc_id_base32) -{ - smartlist_t *responsible_dirs = smartlist_new(); - smartlist_t *usable_responsible_dirs = smartlist_new(); - const or_options_t *options = get_options(); - routerstatus_t *hs_dir; - time_t now = time(NULL); - int excluded_some; - - tor_assert(desc_id); - tor_assert(desc_id_base32); - - /* Determine responsible dirs. Even if we can't get all we want, work with - * the ones we have. If it's empty, we'll notice below. */ - hid_serv_get_responsible_directories(responsible_dirs, desc_id); - - /* Clean request history first. */ - directory_clean_last_hid_serv_requests(now); - - /* Only select those hidden service directories to which we did not send a - * request recently and for which we have a router descriptor here. */ - SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { - time_t last = lookup_last_hid_serv_request(dir, desc_id_base32, - 0, 0); - const node_t *node = node_get_by_id(dir->identity_digest); - if (last + hsdir_requery_period(options) >= now || - !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); - } - } SMARTLIST_FOREACH_END(dir); - - 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."); - } - } else { - /* Remember that we are requesting a descriptor from this hidden service - * directory now. */ - lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); - } - - return hs_dir; -} - /** Determine the responsible hidden service directories for <b>desc_id</b> * and fetch the descriptor with that ID from one of them. Only * send a request to a hidden service directory that we have not yet tried @@ -715,7 +440,7 @@ directory_get_from_hs_dir(const char *desc_id, const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS; #else const int how_to_fetch = DIRIND_ANONYMOUS; -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ tor_assert(desc_id); tor_assert(rend_query); @@ -726,7 +451,12 @@ directory_get_from_hs_dir(const char *desc_id, /* Automatically pick an hs dir if none given. */ if (!rs_hsdir) { - hs_dir = pick_hsdir(desc_id, desc_id_base32); + /* Determine responsible dirs. Even if we can't get all we want, work with + * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */ + smartlist_t *responsible_dirs = smartlist_new(); + hid_serv_get_responsible_directories(responsible_dirs, desc_id); + + hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32); if (!hs_dir) { /* No suitable hs dir can be found, stop right now. */ control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR"); @@ -791,6 +521,20 @@ directory_get_from_hs_dir(const char *desc_id, return 1; } +/** Remove tracked HSDir requests from our history for this hidden service + * descriptor <b>desc_id</b> (of size DIGEST_LEN) */ +static void +purge_v2_hidserv_req(const char *desc_id) +{ + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + + /* The hsdir request tracker stores v2 keys using the base32 encoded + desc_id. Do it: */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + hs_purge_hid_serv_from_last_hid_serv_requests(desc_id_base32); +} + /** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are * given, they will be used instead. * @@ -865,8 +609,7 @@ fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs) sizeof(descriptor_id)) != 0) { /* Not equal from what we currently have so purge the last hid serv * request cache and update the descriptor ID with the new value. */ - purge_hid_serv_from_last_hid_serv_requests( - rend_data->descriptor_id[chosen_replica]); + purge_v2_hidserv_req(rend_data->descriptor_id[chosen_replica]); memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id, sizeof(rend_data->descriptor_id[chosen_replica])); } @@ -1112,118 +855,24 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, return 1; } -/** Called when we receive a RENDEZVOUS_ESTABLISHED cell; changes the state of - * the circuit to C_REND_READY. - */ -int -rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, - size_t request_len) -{ - (void) request; - (void) request_len; - /* we just got an ack for our establish-rendezvous. switch purposes. */ - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { - log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. " - "Closing circ."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for " - "rendezvous."); - circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); - /* Set timestamp_dirty, because circuit_expire_building expects it - * to specify when a circuit entered the _C_REND_READY state. */ - circ->base_.timestamp_dirty = time(NULL); - - /* From a path bias point of view, this circuit is now successfully used. - * Waiting any longer opens us up to attacks from malicious hidden services. - * They could induce the client to attempt to connect to their hidden - * service and never reply to the client's rend requests */ - pathbias_mark_use_success(circ); - - /* XXXX++ This is a pretty brute-force approach. It'd be better to - * attach only the connections that are waiting on this circuit, rather - * than trying to attach them all. See comments bug 743. */ - /* If we already have the introduction circuit built, make sure we send - * the INTRODUCE cell _now_ */ - connection_ap_attach_pending(1); - return 0; -} - /** The service sent us a rendezvous cell; join the circuits. */ int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len) { - crypt_path_t *hop; - char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; - - if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY && - circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) - || !circ->build_state->pending_final_cpath) { - log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not " - "expecting it. Closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - if (request_len != DH_KEY_LEN+DIGEST_LEN) { log_warn(LD_PROTOCOL,"Incorrect length (%d) on RENDEZVOUS2 cell.", (int)request_len); goto err; } - log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service."); - - /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh - * handshake...*/ - tor_assert(circ->build_state); - tor_assert(circ->build_state->pending_final_cpath); - hop = circ->build_state->pending_final_cpath; - tor_assert(hop->rend_dh_handshake_state); - if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, - hop->rend_dh_handshake_state, (char*)request, - DH_KEY_LEN, - keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { - log_warn(LD_GENERAL, "Couldn't complete DH handshake."); + if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); goto err; } - /* ... and set up cpath. */ - if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0) - goto err; - - /* Check whether the digest is right... */ - if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) { - log_warn(LD_PROTOCOL, "Incorrect digest of key material."); - goto err; - } - - crypto_dh_free(hop->rend_dh_handshake_state); - hop->rend_dh_handshake_state = NULL; - - /* All is well. Extend the circuit. */ - circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED); - hop->state = CPATH_STATE_OPEN; - /* set the windows to default. these are the windows - * that the client thinks the service has. - */ - hop->package_window = circuit_initial_package_window(); - hop->deliver_window = CIRCWINDOW_START; - - /* Now that this circuit has finished connecting to its destination, - * make sure circuit_get_open_circ_or_launch is willing to return it - * so we can actually use it. */ - circ->hs_circ_has_timed_out = 0; - - onion_append_to_cpath(&circ->cpath, hop); - circ->build_state->pending_final_cpath = NULL; /* prevent double-free */ - - circuit_try_attaching_streams(circ); - - memwipe(keys, 0, sizeof(keys)); return 0; + err: - memwipe(keys, 0, sizeof(keys)); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); return -1; } @@ -1310,14 +959,14 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data) for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id); replica++) { const char *desc_id = rend_data_v2->descriptor_id[replica]; - purge_hid_serv_from_last_hid_serv_requests(desc_id); + purge_v2_hidserv_req(desc_id); } log_info(LD_REND, "Connection attempt for %s has ended; " "cleaning up temporary state.", safe_str_client(onion_address)); } else { /* We only have an ID for a fetch. Probably used by HSFETCH. */ - purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch); + purge_v2_hidserv_req(rend_data_v2->desc_id_fetch); } } @@ -1516,7 +1165,7 @@ rend_parse_service_authorization(const or_options_t *options, goto err; } strlcpy(auth->onion_address, onion_address, REND_SERVICE_ID_LEN_BASE32+1); - if (!rend_valid_service_id(auth->onion_address)) { + if (!rend_valid_v2_service_id(auth->onion_address)) { log_warn(LD_CONFIG, "Onion address has wrong format: '%s'", onion_address); goto err; @@ -1569,7 +1218,7 @@ rend_client_allow_non_anonymous_connection(const or_options_t *options) #else (void)options; return 0; -#endif +#endif /* defined(NON_ANONYMOUS_MODE_ENABLED) */ } /* At compile-time, was non-anonymous mode enabled via @@ -1584,6 +1233,6 @@ rend_client_non_anonymous_mode_enabled(const or_options_t *options) return 1; #else return 0; -#endif +#endif /* defined(NON_ANONYMOUS_MODE_ENABLED) */ } diff --git a/src/or/rendclient.h b/src/or/rendclient.h index ff0f4084fd..e8495ce09c 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -24,15 +24,11 @@ int rend_client_introduction_acked(origin_circuit_t *circ, void rend_client_refetch_v2_renddesc(rend_data_t *rend_query); int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs); void rend_client_cancel_descriptor_fetches(void); -void rend_client_purge_last_hid_serv_requests(void); int rend_client_report_intro_point_failure(extend_info_t *failed_intro, rend_data_t *rend_data, unsigned int failure_type); -int rend_client_rendezvous_acked(origin_circuit_t *circ, - const uint8_t *request, - size_t request_len); int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len); @@ -54,5 +50,5 @@ void rend_service_authorization_free_all(void); int rend_client_allow_non_anonymous_connection(const or_options_t *options); int rend_client_non_anonymous_mode_enabled(const or_options_t *options); -#endif +#endif /* !defined(TOR_RENDCLIENT_H) */ diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index d18356e67a..458a90058f 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -8,6 +8,8 @@ * introducers, services, clients, and rendezvous points. **/ +#define RENDCOMMON_PRIVATE + #include "or.h" #include "circuitbuild.h" #include "config.h" @@ -17,6 +19,7 @@ #include "rendcommon.h" #include "rendmid.h" #include "hs_intropoint.h" +#include "hs_client.h" #include "rendservice.h" #include "rephist.h" #include "router.h" @@ -395,7 +398,7 @@ rend_encrypt_v2_intro_points_stealth(char **encrypted_out, /** Attempt to parse the given <b>desc_str</b> and return true if this * succeeds, false otherwise. */ -static int +STATIC int rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc) { rend_service_descriptor_t *test_parsed = NULL; @@ -696,7 +699,7 @@ rend_get_service_id(crypto_pk_t *pk, char *out) /** Return true iff <b>query</b> is a syntactically valid service ID (as * generated by rend_get_service_id). */ int -rend_valid_service_id(const char *query) +rend_valid_v2_service_id(const char *query) { if (strlen(query) != REND_SERVICE_ID_LEN_BASE32) return 0; @@ -778,11 +781,11 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_INTRODUCE2: if (origin_circ) - r = rend_service_receive_introduction(origin_circ,payload,length); + r = hs_service_receive_introduce2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRODUCE_ACK: if (origin_circ) - r = rend_client_introduction_acked(origin_circ,payload,length); + r = hs_client_receive_introduce_ack(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS1: if (or_circ) @@ -790,15 +793,15 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_RENDEZVOUS2: if (origin_circ) - r = rend_client_receive_rendezvous(origin_circ,payload,length); + r = hs_client_receive_rendezvous2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) - r = rend_service_intro_established(origin_circ,payload,length); + r = hs_service_receive_intro_established(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) - r = rend_client_rendezvous_acked(origin_circ,payload,length); + r = hs_client_receive_rendezvous_acked(origin_circ,payload,length); break; default: tor_fragile_assert(); @@ -991,7 +994,7 @@ rend_non_anonymous_mode_enabled(const or_options_t *options) * service. */ void -assert_circ_anonymity_ok(origin_circuit_t *circ, +assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options) { tor_assert(options); diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 94c2480d86..c35d7272fd 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -30,7 +30,7 @@ void rend_encoded_v2_service_descriptor_free( rend_encoded_v2_service_descriptor_t *desc); void rend_intro_point_free(rend_intro_point_t *intro); -int rend_valid_service_id(const char *query); +int rend_valid_v2_service_id(const char *query); int rend_valid_descriptor_id(const char *query); int rend_valid_client_name(const char *client_name); int rend_encode_v2_descriptors(smartlist_t *descs_out, @@ -60,8 +60,15 @@ int rend_auth_decode_cookie(const char *cookie_in, int rend_allow_non_anonymous_connection(const or_options_t* options); int rend_non_anonymous_mode_enabled(const or_options_t *options); -void assert_circ_anonymity_ok(origin_circuit_t *circ, +void assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options); -#endif +#ifdef RENDCOMMON_PRIVATE + +STATIC int +rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc); + +#endif /* defined(RENDCOMMON_PRIVATE) */ + +#endif /* !defined(TOR_RENDCOMMON_H) */ diff --git a/src/or/rendmid.c b/src/or/rendmid.c index 23c3deddaa..66d2f93113 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -71,7 +71,6 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, goto err; } /* Rest of body: signature of previous data */ - note_crypto_pk_op(REND_MID); if (crypto_pk_public_checksig_digest(pk, (char*)request, 2+asn1len+DIGEST_LEN, (char*)(request+2+DIGEST_LEN+asn1len), diff --git a/src/or/rendmid.h b/src/or/rendmid.h index daf9e2885e..6cc1fc8d95 100644 --- a/src/or/rendmid.h +++ b/src/or/rendmid.h @@ -21,5 +21,5 @@ int rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, int rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, size_t request_len); -#endif +#endif /* !defined(TOR_RENDMID_H) */ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index b1e8a2f0cd..2cdafa6666 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -18,6 +18,7 @@ #include "control.h" #include "directory.h" #include "hs_common.h" +#include "hs_config.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -82,22 +83,6 @@ static smartlist_t* rend_get_service_list_mutable( smartlist_t* substitute_service_list); static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted); -/** Represents the mapping from a virtual port of a rendezvous service to - * a real port on some IP. - */ -struct rend_service_port_config_s { - /* 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]; -}; - /* Hidden service directory file names: * new file names should be added to rend_service_add_filenames_to_list() * for sandboxing purposes. */ @@ -163,7 +148,7 @@ rend_service_escaped_dir(const struct rend_service_t *s) /** Return the number of rendezvous services we have configured. */ int -num_rend_services(void) +rend_num_services(void) { if (!rend_service_list) return 0; @@ -231,18 +216,41 @@ rend_service_free(rend_service_t *service) tor_free(service); } -/** Release all the storage held in rend_service_list. - */ +/* Release all the storage held in rend_service_staging_list. */ +void +rend_service_free_staging_list(void) +{ + if (rend_service_staging_list) { + SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t*, ptr, + rend_service_free(ptr)); + smartlist_free(rend_service_staging_list); + rend_service_staging_list = NULL; + } +} + +/** Release all the storage held in both rend_service_list and + * rend_service_staging_list. */ void rend_service_free_all(void) { - if (!rend_service_list) - return; + if (rend_service_list) { + SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, + rend_service_free(ptr)); + smartlist_free(rend_service_list); + rend_service_list = NULL; + } + rend_service_free_staging_list(); +} + +/* Initialize the subsystem. */ +void +rend_service_init(void) +{ + tor_assert(!rend_service_list); + tor_assert(!rend_service_staging_list); - SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_list); - rend_service_list = NULL; + rend_service_list = smartlist_new(); + rend_service_staging_list = smartlist_new(); } /* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there @@ -252,8 +260,6 @@ static int rend_validate_service(const smartlist_t *service_list, const rend_service_t *service) { - int dupe = 0; - tor_assert(service_list); tor_assert(service); @@ -286,34 +292,6 @@ rend_validate_service(const smartlist_t *service_list, goto invalid; } - /* XXX This duplicate check has two problems: - * - * a) It's O(n^2), but the same comment from the bottom of - * rend_config_services() should apply. - * - * b) We only compare directory paths as strings, so we can't - * detect two distinct paths that specify the same directory - * (which can arise from symlinks, case-insensitivity, bind - * mounts, etc.). - * - * It also can't detect that two separate Tor instances are trying - * to use the same HiddenServiceDir; for that, we would need a - * lock file. But this is enough to detect a simple mistake that - * at least one person has actually made. - */ - if (!rend_service_is_ephemeral(service)) { - /* Skip dupe for ephemeral services. */ - SMARTLIST_FOREACH(service_list, rend_service_t *, ptr, - dupe = dupe || - !strcmp(ptr->directory, service->directory)); - if (dupe) { - log_warn(LD_REND, "Another hidden service is already configured for " - "directory %s.", - rend_service_escaped_dir(service)); - goto invalid; - } - } - /* Valid. */ return 0; invalid: @@ -335,6 +313,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) /* We must have a service list, even if it's a temporary one, so we can * check for duplicate services */ if (BUG(!s_list)) { + rend_service_free(service); return -1; } @@ -496,39 +475,28 @@ rend_service_port_config_free(rend_service_port_config_t *p) tor_free(p); } -/* Check the directory for <b>service</b>, and add the service to - * <b>service_list</b>, or to the global list if <b>service_list</b> is NULL. - * Only add the service to the list if <b>validate_only</b> is false. - * If <b>validate_only</b> is true, free the service. - * If <b>service</b> is NULL, ignore it, and return 0. - * Returns 0 on success, and -1 on failure. - * Takes ownership of <b>service</b>, either freeing it, or adding it to the - * global service list. - */ -STATIC int -rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only) +/* Copy relevant data from service src to dst while pruning the service lists. + * This should only be called during the pruning process which takes existing + * services and copy their data to the newly configured services. The src + * service replaycache will be set to NULL after this call. */ +static void +copy_service_on_prunning(rend_service_t *dst, rend_service_t *src) { - if (!service) { - /* It is ok for a service to be NULL, this means there are no services */ - return 0; - } - - if (rend_service_check_private_dir(options, service, !validate_only) - < 0) { - rend_service_free(service); - return -1; - } - - smartlist_t *s_list = rend_get_service_list_mutable(service_list); - /* We must have a service list, even if it's a temporary one, so we can - * check for duplicate services */ - if (BUG(!s_list)) { - return -1; - } - return rend_add_service(s_list, service); + tor_assert(dst); + tor_assert(src); + + /* Keep the timestamps for when the content changed and the next upload + * time so we can properly upload the descriptor if needed for the new + * service object. */ + dst->desc_is_dirty = src->desc_is_dirty; + dst->next_upload_time = src->next_upload_time; + /* Move the replaycache to the new object. */ + dst->accepted_intro_dh_parts = src->accepted_intro_dh_parts; + src->accepted_intro_dh_parts = NULL; + /* Copy intro point information to destination service. */ + dst->intro_period_started = src->intro_period_started; + dst->n_intro_circuits_launched = src->n_intro_circuits_launched; + dst->n_intro_points_wanted = src->n_intro_points_wanted; } /* Helper: Actual implementation of the pruning on reload which we've @@ -604,6 +572,10 @@ rend_service_prune_list_impl_(void) smartlist_clear(old->intro_nodes); smartlist_add_all(new->expiring_nodes, old->expiring_nodes); smartlist_clear(old->expiring_nodes); + + /* Copy needed information from old to new. */ + copy_service_on_prunning(new, old); + /* This regular service will survive the closing IPs step after. */ smartlist_add(surviving_services, old); break; @@ -614,7 +586,10 @@ rend_service_prune_list_impl_(void) * matching surviving configured service. If not, close the circuit. */ while ((ocirc = circuit_get_next_service_intro_circ(ocirc))) { int keep_it = 0; - tor_assert(ocirc->rend_data); + if (ocirc->rend_data == NULL) { + /* This is a v3 circuit, ignore it. */ + continue; + } SMARTLIST_FOREACH_BEGIN(surviving_services, const rend_service_t *, s) { if (rend_circuit_pk_digest_eq(ocirc, (uint8_t *) s->pk_digest)) { /* Keep this circuit as we have a matching configured service. */ @@ -657,19 +632,54 @@ rend_service_prune_list(void) } } -/** Set up rend_service_list, based on the values of HiddenServiceDir and - * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on - * failure. (If <b>validate_only</b> is set, parse, warn and return as - * normal, but don't actually change the configured services.) - */ +/* Copy all the relevant data that the hs_service object contains over to the + * rend_service_t object. The reason to do so is because when configuring a + * service, we go through a generic handler that creates an hs_service_t + * object which so we have to copy the parsed values to a rend service object + * which is version 2 specific. */ +static void +service_config_shadow_copy(rend_service_t *service, + hs_service_config_t *config) +{ + tor_assert(service); + tor_assert(config); + + service->directory = tor_strdup(config->directory_path); + service->dir_group_readable = config->dir_group_readable; + service->allow_unknown_ports = config->allow_unknown_ports; + /* This value can't go above HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT (65535) + * if the code flow is right so this cast is safe. But just in case, we'll + * check it. */ + service->max_streams_per_circuit = (int) config->max_streams_per_rdv_circuit; + if (BUG(config->max_streams_per_rdv_circuit > + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) { + service->max_streams_per_circuit = HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT; + } + service->max_streams_close_circuit = config->max_streams_close_circuit; + service->n_intro_points_wanted = config->num_intro_points; + /* Switching ownership of the ports to the rend service object. */ + smartlist_add_all(service->ports, config->ports); + smartlist_free(config->ports); + config->ports = NULL; +} + +/* Parse the hidden service configuration starting at <b>line_</b> using the + * already configured generic service configuration in <b>config</b>. This + * function will translate the config object to a rend_service_t and add it to + * the temporary list if valid. If <b>validate_only</b> is set, parse, warn + * and return as normal but don't actually add the service to the list. */ int -rend_config_services(const or_options_t *options, int validate_only) +rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config) { - config_line_t *line; + const config_line_t *line; rend_service_t *service = NULL; - rend_service_port_config_t *portcfg; - int ok = 0; - int rv = -1; + + /* line_ can be NULL which would mean that the service configuration only + * have one line that is the directory directive. */ + tor_assert(options); + tor_assert(config); /* Use the staging service list so that we can check then do the pruning * process using the main list at the end. */ @@ -677,100 +687,23 @@ rend_config_services(const or_options_t *options, int validate_only) rend_service_staging_list = smartlist_new(); } - for (line = options->RendConfigLines; line; line = line->next) { + /* Initialize service. */ + service = tor_malloc_zero(sizeof(rend_service_t)); + service->intro_period_started = time(NULL); + service->ports = smartlist_new(); + /* From the hs_service object which has been used to load the generic + * options, we'll copy over the useful data to the rend_service_t object. */ + service_config_shadow_copy(service, config); + + for (line = line_; line; line = line->next) { if (!strcasecmp(line->key, "HiddenServiceDir")) { - if (service) { - /* Validate and register the service we just finished parsing this - * code registers every service except the last one parsed, which is - * validated and registered below the loop. */ - if (rend_validate_service(rend_service_staging_list, service) < 0) { - goto free_and_return; - } - if (rend_service_check_dir_and_add(rend_service_staging_list, options, - service, validate_only) < 0) { - /* The above frees the service on error so nullify the pointer. */ - service = NULL; - goto free_and_return; - } - } - service = tor_malloc_zero(sizeof(rend_service_t)); - service->directory = tor_strdup(line->value); - 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); - goto free_and_return; + /* We just hit the next hidden service, stop right now. */ + break; } - if (!strcasecmp(line->key, "HiddenServicePort")) { - char *err_msg = NULL; - portcfg = rend_service_parse_port_config(line->value, " ", &err_msg); - if (!portcfg) { - if (err_msg) - log_warn(LD_CONFIG, "%s", err_msg); - tor_free(err_msg); - goto free_and_return; - } - tor_assert(!err_msg); - 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); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceAllowUnknownPorts=%d for %s", - (int)service->allow_unknown_ports, - rend_service_escaped_dir(service)); - } 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); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceDirGroupReadable=%d for %s", - service->dir_group_readable, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { - service->max_streams_per_circuit = (int)tor_parse_long(line->value, - 10, 0, 65535, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceMaxStreams should be between 0 and %d, not %s", - 65535, line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceMaxStreams=%d for %s", - service->max_streams_per_circuit, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { - service->max_streams_close_circuit = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, " - "not %s", - line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceMaxStreamsCloseCircuit=%d for %s", - (int)service->max_streams_close_circuit, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + int ok = 0; + /* Those are specific defaults for version 2. */ service->n_intro_points_wanted = (unsigned int) tor_parse_long(line->value, 10, 0, NUM_INTRO_POINTS_MAX, &ok, NULL); @@ -779,22 +712,22 @@ rend_config_services(const or_options_t *options, int validate_only) "HiddenServiceNumIntroductionPoints " "should be between %d and %d, not %s", 0, NUM_INTRO_POINTS_MAX, line->value); - goto free_and_return; + goto err; } log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s", - service->n_intro_points_wanted, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + service->n_intro_points_wanted, escaped(service->directory)); + continue; + } + 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."); - goto free_and_return; + goto err; } type_names_split = smartlist_new(); smartlist_split_string(type_names_split, line->value, " ", 0, 2); @@ -802,7 +735,8 @@ rend_config_services(const or_options_t *options, int validate_only) log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " "should have been prevented when parsing the " "configuration."); - goto free_and_return; + smartlist_free(type_names_split); + goto err; } authname = smartlist_get(type_names_split, 0); if (!strcasecmp(authname, "basic")) { @@ -816,7 +750,7 @@ rend_config_services(const or_options_t *options, int validate_only) (char *) smartlist_get(type_names_split, 0)); SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); smartlist_free(type_names_split); - goto free_and_return; + goto err; } service->clients = smartlist_new(); if (smartlist_len(type_names_split) < 2) { @@ -833,14 +767,15 @@ rend_config_services(const or_options_t *options, int validate_only) 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)); - num_clients = smartlist_len(clients); + { + int 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)); + } } SMARTLIST_FOREACH_BEGIN(clients, const char *, client_name) { @@ -853,7 +788,7 @@ rend_config_services(const or_options_t *options, int validate_only) client_name, REND_CLIENTNAME_MAX_LEN); SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp)); smartlist_free(clients); - goto free_and_return; + goto err; } client = tor_malloc_zero(sizeof(rend_authorized_client_t)); client->client_name = tor_strdup(client_name); @@ -875,56 +810,29 @@ rend_config_services(const or_options_t *options, int validate_only) smartlist_len(service->clients), service->auth_type == REND_BASIC_AUTH ? 512 : 16, service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); - goto free_and_return; - } - } else { - tor_assert(!strcasecmp(line->key, "HiddenServiceVersion")); - if (strcmp(line->value, "2")) { - log_warn(LD_CONFIG, - "The only supported HiddenServiceVersion is 2."); - goto free_and_return; + goto err; } + continue; } } - /* Validate the last service that we just parsed. */ - if (service && - rend_validate_service(rend_service_staging_list, service) < 0) { - goto free_and_return; - } - /* register the final service after we have finished parsing all services - * this code only registers the last service, other services are registered - * within the loop. It is ok for this service to be NULL, it is ignored. */ - if (rend_service_check_dir_and_add(rend_service_staging_list, options, - service, validate_only) < 0) { - /* Service object is freed on error so nullify pointer. */ - service = NULL; - goto free_and_return; + /* Validate the service just parsed. */ + if (rend_validate_service(rend_service_staging_list, service) < 0) { + /* Service is in the staging list so don't try to free it. */ + goto err; } - /* The service is in the staging list so nullify pointer to avoid double - * free of this object in case of error because we lost ownership of it at - * this point. */ - service = NULL; - /* Free the newly added services if validating */ - if (validate_only) { - rv = 0; - goto free_and_return; + /* Add it to the temporary list which we will use to prune our current + * list if any after configuring all services. */ + if (rend_add_service(rend_service_staging_list, service) < 0) { + /* The object has been freed on error already. */ + service = NULL; + goto err; } - /* This could be a reload of configuration so try to prune the main list - * using the staging one. And we know we are not in validate mode here. - * After this, the main and staging list will point to the right place and - * be in a quiescent usable state. */ - rend_service_prune_list(); - return 0; - free_and_return: + err: rend_service_free(service); - SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t *, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_staging_list); - rend_service_staging_list = NULL; - return rv; + return -1; } /** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, using @@ -1013,7 +921,7 @@ int rend_service_del_ephemeral(const char *service_id) { rend_service_t *s; - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal."); return -1; } @@ -1038,8 +946,8 @@ rend_service_del_ephemeral(const char *service_id) (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ); - tor_assert(oc->rend_data); - if (!rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { + if (oc->rend_data == NULL || + !rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { continue; } log_debug(LD_REND, "Closing intro point %s for service %s.", @@ -1177,15 +1085,8 @@ rend_service_update_descriptor(rend_service_t *service) static char * rend_service_path(const rend_service_t *service, const char *file_name) { - char *file_path = NULL; - tor_assert(service->directory); - - /* Can never fail: asserts rather than leaving file_path NULL. */ - tor_asprintf(&file_path, "%s%s%s", - service->directory, PATH_SEPARATOR, file_name); - - return file_path; + return hs_path_from_filename(service->directory, file_name); } /* Allocate and return a string containing the path to the single onion @@ -1555,9 +1456,9 @@ rend_service_load_keys(rend_service_t *s) char *fname = NULL; char buf[128]; - /* Make sure the directory was created and single onion poisoning was - * checked before calling this function */ - if (BUG(rend_service_check_private_dir(get_options(), s, 0) < 0)) + /* Create the directory if needed which will also poison it in case of + * single onion service. */ + if (rend_service_check_private_dir(get_options(), s, 1) < 0) goto err; /* Load key */ @@ -1586,7 +1487,7 @@ rend_service_load_keys(rend_service_t *s) log_warn(LD_FS,"Unable to make hidden hostname file %s group-readable.", fname); } -#endif +#endif /* !defined(_WIN32) */ /* If client authorization is configured, load or generate keys. */ if (s->auth_type != REND_NO_AUTH) { @@ -1815,24 +1716,6 @@ rend_service_get_by_service_id(const char *id) return NULL; } -/** Return 1 if any virtual port in <b>service</b> wants a circuit - * to have good uptime. Else return 0. - */ -static int -rend_service_requires_uptime(rend_service_t *service) -{ - int i; - rend_service_port_config_t *p; - - for (i=0; i < smartlist_len(service->ports); ++i) { - p = smartlist_get(service->ports, i); - if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, - p->virtual_port)) - return 1; - } - return 0; -} - /** 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. */ @@ -2152,7 +2035,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, goto err; } - circ_needs_uptime = rend_service_requires_uptime(service); + circ_needs_uptime = hs_service_requires_uptime_circ(service->ports); /* help predict this next time */ rep_hist_note_used_internal(now, circ_needs_uptime, 1); @@ -2204,7 +2087,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit, cpath->rend_dh_handshake_state = dh; dh = NULL; - if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0) + if (circuit_init_cpath_crypto(cpath, + keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, + 1, 0)<0) goto err; memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN); @@ -2267,7 +2152,7 @@ find_rp_for_intro(const rend_intro_cell_t *intro, if (intro->version == 0 || intro->version == 1) { rp_nickname = (const char *)(intro->u.v0_v1.rp); - node = node_get_by_nickname(rp_nickname, 0); + node = node_get_by_nickname(rp_nickname, NNF_NO_WARN_UNNAMED); if (!node) { if (err_msg_out) { tor_asprintf(&err_msg, @@ -2859,10 +2744,8 @@ rend_service_decrypt_intro( } /* Decrypt the encrypted part */ - - note_crypto_pk_op(REND_SERVER); result = - crypto_pk_private_hybrid_decrypt( + crypto_pk_obsolete_private_hybrid_decrypt( key, (char *)buf, sizeof(buf), (const char *)(intro->ciphertext), intro->ciphertext_len, PK_PKCS1_OAEP_PADDING, 1); @@ -3056,29 +2939,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); - /* Don't relaunch the same rend circ twice. */ - if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) { - log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; " - "not relaunching it again.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1; - - if (!oldcirc->build_state || - oldcirc->build_state->failure_count > MAX_REND_FAILURES || - oldcirc->build_state->expiry_time < time(NULL)) { - log_info(LD_REND, - "Attempt to build circuit to %s for rendezvous has failed " - "too many times or expired; giving up.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldstate = oldcirc->build_state; tor_assert(oldstate); @@ -3246,10 +3106,11 @@ count_intro_point_circuits(const rend_service_t *service) crypto material. On success, fill <b>cell_body_out</b> and return the number of bytes written. On fail, return -1. */ -STATIC ssize_t -encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, char *rend_circ_nonce) +ssize_t +rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce) { int retval = -1; int r; @@ -3274,7 +3135,6 @@ encode_establish_intro_cell_legacy(char *cell_body_out, if (crypto_digest(cell_body_out+len, auth, DIGEST_LEN+9)) goto err; len += 20; - note_crypto_pk_op(REND_SERVER); r = crypto_pk_private_sign_digest(intro_key, cell_body_out+len, cell_body_out_len - len, cell_body_out, len); @@ -3387,7 +3247,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) /* Send the ESTABLISH_INTRO cell */ { ssize_t len; - len = encode_establish_intro_cell_legacy(buf, sizeof(buf), + len = rend_service_encode_establish_intro_cell(buf, sizeof(buf), circuit->intro_key, circuit->cpath->prev->rend_circ_nonce); if (len < 0) { @@ -3505,9 +3365,10 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) NULL); rend_cookie = circuit->rend_data->rend_cookie; - /* Declare the circuit dirty to avoid reuse, and for path-bias */ - if (!circuit->base_.timestamp_dirty) - circuit->base_.timestamp_dirty = time(NULL); + /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + circuit->base_.timestamp_dirty = time(NULL); /* This may be redundant */ pathbias_count_use_attempt(circuit); @@ -3567,7 +3428,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) /* Send the cell */ if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit), RELAY_COMMAND_RENDEZVOUS1, - buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN, + buf, HS_LEGACY_RENDEZVOUS_CELL_SIZE, circuit->cpath->prev)<0) { log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell."); goto done; @@ -4123,10 +3984,9 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted) * This is called once a second by the main loop. */ void -rend_consider_services_intro_points(void) +rend_consider_services_intro_points(time_t now) { int i; - time_t now; const or_options_t *options = get_options(); /* Are we in single onion mode? */ const int allow_direct = rend_service_allow_non_anonymous_connection( @@ -4143,7 +4003,6 @@ rend_consider_services_intro_points(void) exclude_nodes = smartlist_new(); retry_nodes = smartlist_new(); - now = time(NULL); SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) { int r; @@ -4421,60 +4280,6 @@ 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 -2 on failure @@ -4487,15 +4292,11 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, { rend_service_t *service; char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; - smartlist_t *matching_ports; - rend_service_port_config_t *chosen_port; - unsigned int warn_once = 0; const char *rend_pk_digest; tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); tor_assert(circ->rend_data); log_debug(LD_REND,"beginning to hunt for addr/port"); - /* XXX: This is version 2 specific (only one supported). */ rend_pk_digest = (char *) rend_data_get_pk_digest(circ->rend_data, NULL); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, rend_pk_digest, REND_SERVICE_ID_LEN); @@ -4525,41 +4326,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, return service->max_streams_close_circuit ? -2 : -1; } } - matching_ports = smartlist_new(); - SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, - { - 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) { - 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); - } - } + + if (hs_set_conn_addr_port(service->ports, conn) == 0) { + /* Successfully set the port to the connection. We are done. */ return 0; } @@ -4635,5 +4404,5 @@ set_rend_rend_service_staging_list(smartlist_t *new_list) rend_service_staging_list = new_list; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 1583a6010b..5946e31861 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -13,11 +13,9 @@ #define TOR_RENDSERVICE_H #include "or.h" +#include "hs_service.h" typedef struct rend_intro_cell_s rend_intro_cell_t; -typedef struct rend_service_port_config_s rend_service_port_config_t; - -#ifdef RENDSERVICE_PRIVATE /* This can be used for both INTRODUCE1 and INTRODUCE2 */ @@ -63,6 +61,8 @@ struct rend_intro_cell_s { uint8_t dh[DH_KEY_LEN]; }; +#ifdef RENDSERVICE_PRIVATE + /** Represents a single hidden service running at this OP. */ typedef struct rend_service_t { /* Fields specified in config file */ @@ -119,37 +119,32 @@ typedef struct rend_service_t { STATIC void rend_service_free(rend_service_t *service); STATIC char *rend_service_sos_poison_path(const rend_service_t *service); -STATIC int rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only); STATIC int rend_service_verify_single_onion_poison( const rend_service_t *s, const or_options_t *options); STATIC int rend_service_poison_new_single_onion_dir( const rend_service_t *s, const or_options_t* options); -STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, - char *rend_circ_nonce); #ifdef TOR_UNIT_TESTS STATIC void set_rend_service_list(smartlist_t *new_list); STATIC void set_rend_rend_service_staging_list(smartlist_t *new_list); STATIC void rend_service_prune_list_impl_(void); -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* RENDSERVICE_PRIVATE */ +#endif /* defined(RENDSERVICE_PRIVATE) */ -int num_rend_services(void); -int rend_config_services(const or_options_t *options, int validate_only); +int rend_num_services(void); +int rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config); void rend_service_prune_list(void); +void rend_service_free_staging_list(void); int rend_service_load_all_keys(const smartlist_t *service_list); void rend_services_add_filenames_to_lists(smartlist_t *open_lst, smartlist_t *stat_lst); -void rend_consider_services_intro_points(void); +void rend_consider_services_intro_points(time_t now); void rend_consider_services_upload(time_t now); void rend_hsdir_routers_changed(void); void rend_consider_descriptor_republication(void); @@ -172,6 +167,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request, char **err_msg_out); int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro, char **err_msg_out); +ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce); int rend_service_validate_intro_late(const rend_intro_cell_t *intro, char **err_msg_out); void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc); @@ -179,6 +178,7 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn, origin_circuit_t *circ); void rend_service_dump_stats(int severity); void rend_service_free_all(void); +void rend_service_init(void); rend_service_port_config_t *rend_service_parse_port_config(const char *string, const char *sep, @@ -214,5 +214,5 @@ int rend_service_allow_non_anonymous_connection(const or_options_t *options); int rend_service_reveal_startup_time(const or_options_t *options); int rend_service_non_anonymous_mode_enabled(const or_options_t *options); -#endif +#endif /* !defined(TOR_RENDSERVICE_H) */ diff --git a/src/or/rephist.c b/src/or/rephist.c index ffc1867955..345722d8ce 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -1811,7 +1811,7 @@ static time_t last_prediction_add_time=0; int predicted_ports_prediction_time_remaining(time_t now) { - time_t idle_delta = now - last_prediction_add_time; + time_t idle_delta; /* Protect against overflow of return value. This can happen if the clock * jumps backwards in time. Update the last prediction time (aka last @@ -1821,6 +1821,8 @@ predicted_ports_prediction_time_remaining(time_t now) if (last_prediction_add_time > now) { last_prediction_add_time = now; idle_delta = 0; + } else { + idle_delta = now - last_prediction_add_time; } /* Protect against underflow of the return value. This can happen for very @@ -2064,105 +2066,6 @@ rep_hist_circbuilding_dormant(time_t now) return 1; } -/** Structure to track how many times we've done each public key operation. */ -static struct { - /** How many directory objects have we signed? */ - unsigned long n_signed_dir_objs; - /** How many routerdescs have we signed? */ - unsigned long n_signed_routerdescs; - /** How many directory objects have we verified? */ - unsigned long n_verified_dir_objs; - /** How many routerdescs have we verified */ - unsigned long n_verified_routerdescs; - /** How many onionskins have we encrypted to build circuits? */ - unsigned long n_onionskins_encrypted; - /** How many onionskins have we decrypted to do circuit build requests? */ - unsigned long n_onionskins_decrypted; - /** How many times have we done the TLS handshake as a client? */ - unsigned long n_tls_client_handshakes; - /** How many times have we done the TLS handshake as a server? */ - unsigned long n_tls_server_handshakes; - /** How many PK operations have we done as a hidden service client? */ - unsigned long n_rend_client_ops; - /** How many PK operations have we done as a hidden service midpoint? */ - unsigned long n_rend_mid_ops; - /** How many PK operations have we done as a hidden service provider? */ - unsigned long n_rend_server_ops; -} pk_op_counts = {0,0,0,0,0,0,0,0,0,0,0}; - -/** Increment the count of the number of times we've done <b>operation</b>. */ -void -note_crypto_pk_op(pk_op_t operation) -{ - switch (operation) - { - case SIGN_DIR: - pk_op_counts.n_signed_dir_objs++; - break; - case SIGN_RTR: - pk_op_counts.n_signed_routerdescs++; - break; - case VERIFY_DIR: - pk_op_counts.n_verified_dir_objs++; - break; - case VERIFY_RTR: - pk_op_counts.n_verified_routerdescs++; - break; - case ENC_ONIONSKIN: - pk_op_counts.n_onionskins_encrypted++; - break; - case DEC_ONIONSKIN: - pk_op_counts.n_onionskins_decrypted++; - break; - case TLS_HANDSHAKE_C: - pk_op_counts.n_tls_client_handshakes++; - break; - case TLS_HANDSHAKE_S: - pk_op_counts.n_tls_server_handshakes++; - break; - case REND_CLIENT: - pk_op_counts.n_rend_client_ops++; - break; - case REND_MID: - pk_op_counts.n_rend_mid_ops++; - break; - case REND_SERVER: - pk_op_counts.n_rend_server_ops++; - break; - default: - log_warn(LD_BUG, "Unknown pk operation %d", operation); - } -} - -/** Log the number of times we've done each public/private-key operation. */ -void -dump_pk_ops(int severity) -{ - tor_log(severity, LD_HIST, - "PK operations: %lu directory objects signed, " - "%lu directory objects verified, " - "%lu routerdescs signed, " - "%lu routerdescs verified, " - "%lu onionskins encrypted, " - "%lu onionskins decrypted, " - "%lu client-side TLS handshakes, " - "%lu server-side TLS handshakes, " - "%lu rendezvous client operations, " - "%lu rendezvous middle operations, " - "%lu rendezvous server operations.", - pk_op_counts.n_signed_dir_objs, - pk_op_counts.n_verified_dir_objs, - pk_op_counts.n_signed_routerdescs, - pk_op_counts.n_verified_routerdescs, - pk_op_counts.n_onionskins_encrypted, - pk_op_counts.n_onionskins_decrypted, - pk_op_counts.n_tls_client_handshakes, - pk_op_counts.n_tls_server_handshakes, - pk_op_counts.n_rend_client_ops, - pk_op_counts.n_rend_mid_ops, - pk_op_counts.n_rend_server_ops); -} - /*** Exit port statistics ***/ /* Some constants */ @@ -2651,7 +2554,7 @@ rep_hist_format_buffer_stats(time_t now) processed_cells_string, queued_cells_string, time_in_queue_string, - (number_of_circuits + SHARES - 1) / SHARES); + CEIL_DIV(number_of_circuits, SHARES)); tor_free(processed_cells_string); tor_free(queued_cells_string); tor_free(time_in_queue_string); diff --git a/src/or/rephist.h b/src/or/rephist.h index 2b1c2e7ec7..496e366865 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -62,9 +62,6 @@ int any_predicted_circuits(time_t now); int rep_hist_circbuilding_dormant(time_t now); int predicted_ports_prediction_time_remaining(time_t now); -void note_crypto_pk_op(pk_op_t operation); -void dump_pk_ops(int severity); - void rep_hist_exit_stats_init(time_t now); void rep_hist_reset_exit_stats(time_t now); void rep_hist_exit_stats_term(void); @@ -146,5 +143,5 @@ void rep_hist_reset_padding_counts(void); void rep_hist_prep_published_padding_counts(time_t now); void rep_hist_padding_count_timers(uint64_t num_timers); -#endif +#endif /* !defined(TOR_REPHIST_H) */ diff --git a/src/or/replaycache.h b/src/or/replaycache.h index 0d637939a4..1cae3497ae 100644 --- a/src/or/replaycache.h +++ b/src/or/replaycache.h @@ -29,7 +29,7 @@ struct replaycache_s { digest256map_t *digests_seen; }; -#endif /* REPLAYCACHE_PRIVATE */ +#endif /* defined(REPLAYCACHE_PRIVATE) */ /* replaycache_t free/new */ @@ -51,7 +51,7 @@ STATIC int replaycache_add_and_test_internal( STATIC void replaycache_scrub_if_needed_internal( time_t present, replaycache_t *r); -#endif /* REPLAYCACHE_PRIVATE */ +#endif /* defined(REPLAYCACHE_PRIVATE) */ /* * replaycache_t methods @@ -62,5 +62,5 @@ int replaycache_add_test_and_elapsed( replaycache_t *r, const void *data, size_t len, time_t *elapsed); void replaycache_scrub_if_needed(replaycache_t *r); -#endif +#endif /* !defined(TOR_REPLAYCACHE_H) */ diff --git a/src/or/router.c b/src/or/router.c index 55b58e5d11..010ee339a0 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1073,7 +1073,7 @@ init_keys(void) /* 4. Build our router descriptor. */ /* Must be called after keys are initialized. */ mydesc = router_get_my_descriptor(); - if (authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)) { + if (authdir_mode_v3(options)) { const char *m = NULL; routerinfo_t *ri; /* We need to add our own fingerprint so it gets recognized. */ @@ -1604,32 +1604,19 @@ authdir_mode_v3(const or_options_t *options) { return authdir_mode(options) && options->V3AuthoritativeDir != 0; } -/** Return true iff we are a v3 directory authority. */ -int -authdir_mode_any_main(const or_options_t *options) -{ - return options->V3AuthoritativeDir; -} -/** Return true if we believe ourselves to be any kind of - * authoritative directory beyond just a hidserv authority. */ -int -authdir_mode_any_nonhidserv(const or_options_t *options) -{ - return options->BridgeAuthoritativeDir || - authdir_mode_any_main(options); -} /** Return true iff we are an authoritative directory server that is * authoritative about receiving and serving descriptors of type - * <b>purpose</b> on its dirport. Use -1 for "any purpose". */ + * <b>purpose</b> on its dirport. + */ int authdir_mode_handles_descs(const or_options_t *options, int purpose) { - if (purpose < 0) - return authdir_mode_any_nonhidserv(options); + if (BUG(purpose < 0)) /* Deprecated. */ + return authdir_mode(options); else if (purpose == ROUTER_PURPOSE_GENERAL) - return authdir_mode_any_main(options); + return authdir_mode_v3(options); else if (purpose == ROUTER_PURPOSE_BRIDGE) - return (options->BridgeAuthoritativeDir); + return authdir_mode_bridge(options); else return 0; } @@ -1641,7 +1628,7 @@ authdir_mode_publishes_statuses(const or_options_t *options) { if (authdir_mode_bridge(options)) return 0; - return authdir_mode_any_nonhidserv(options); + return authdir_mode(options); } /** Return true iff we are an authoritative directory server that * tests reachability of the descriptors it learns about. @@ -1649,7 +1636,7 @@ authdir_mode_publishes_statuses(const or_options_t *options) int authdir_mode_tests_reachability(const or_options_t *options) { - return authdir_mode_handles_descs(options, -1); + return authdir_mode(options); } /** Return true iff we believe ourselves to be a bridge authoritative * directory server. @@ -1877,7 +1864,7 @@ static const char *desc_gen_reason = NULL; * now. */ static time_t desc_clean_since = 0; /** Why did we mark the descriptor dirty? */ -static const char *desc_dirty_reason = NULL; +static const char *desc_dirty_reason = "Tor just started"; /** Boolean: do we need to regenerate the above? */ static int desc_needs_upload = 0; @@ -1958,7 +1945,7 @@ router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port) desc_routerinfo->ipv6_exit_policy && compare_tor_addr_to_short_policy(addr, port, me->ipv6_exit_policy) != ADDR_POLICY_ACCEPTED; -#endif +#endif /* 0 */ } else { return -1; } @@ -2300,7 +2287,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) if (!strcasecmp(name, options->Nickname)) continue; /* Don't list ourself, that's redundant */ else - member = node_get_by_nickname(name, 1); + member = node_get_by_nickname(name, 0); if (!member) { int is_legal = is_legal_nickname_or_hexdigest(name); if (!smartlist_contains_string(warned_nonexistent_family, name) && @@ -2971,10 +2958,13 @@ router_dump_router_to_string(routerinfo_t *router, if (options->BridgeRelay) { const char *bd; - if (options->PublishServerDescriptor_ & BRIDGE_DIRINFO) + if (options->BridgeDistribution && strlen(options->BridgeDistribution)) { + bd = options->BridgeDistribution; + } else { bd = "any"; - else - bd = "none"; + } + if (strchr(bd, '\n') || strchr(bd, '\r')) + bd = escaped(bd); smartlist_add_asprintf(chunks, "bridge-distribution-request %s\n", bd); } @@ -3039,7 +3029,6 @@ router_dump_router_to_string(routerinfo_t *router, crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1); - note_crypto_pk_op(SIGN_RTR); { char *sig; if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) { @@ -3070,7 +3059,7 @@ router_dump_router_to_string(routerinfo_t *router, tor_free(s_dup); routerinfo_free(ri_tmp); } -#endif +#endif /* defined(DEBUG_ROUTER_DUMP_ROUTER_TO_STRING) */ goto done; @@ -3520,7 +3509,7 @@ router_get_description(char *buf, const routerinfo_t *ri) return "<null>"; return format_node_description(buf, ri->cache_info.identity_digest, - router_is_named(ri), + 0, ri->nickname, NULL, ri->addr); @@ -3630,7 +3619,7 @@ routerstatus_describe(const routerstatus_t *rs) return routerstatus_get_description(buf, rs); } -/** Return a human-readable description of the extend_info_t <b>ri</b>. +/** Return a human-readable description of the extend_info_t <b>ei</b>. * * This function is not thread-safe. Each call to this function invalidates * previous values returned by this function. @@ -3646,21 +3635,16 @@ extend_info_describe(const extend_info_t *ei) * verbose representation of the identity of <b>router</b>. The format is: * A dollar sign. * The upper-case hexadecimal encoding of the SHA1 hash of router's identity. - * A "=" if the router is named; a "~" if it is not. + * A "=" if the router is named (no longer implemented); a "~" if it is not. * The router's nickname. **/ void router_get_verbose_nickname(char *buf, const routerinfo_t *router) { - const char *good_digest = networkstatus_get_router_digest_by_nickname( - router->nickname); - int is_named = good_digest && tor_memeq(good_digest, - router->cache_info.identity_digest, - DIGEST_LEN); buf[0] = '$'; base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, DIGEST_LEN); - buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + buf[1+HEX_DIGEST_LEN] = '~'; strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); } diff --git a/src/or/router.h b/src/or/router.h index 9c5def5218..3351400911 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -54,8 +54,6 @@ int net_is_disabled(void); int authdir_mode(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); int authdir_mode_handles_descs(const or_options_t *options, int purpose); int authdir_mode_publishes_statuses(const or_options_t *options); int authdir_mode_tests_reachability(const or_options_t *options); @@ -162,5 +160,5 @@ STATIC void get_platform_str(char *platform, size_t len); STATIC int router_write_fingerprint(int hashed); #endif -#endif +#endif /* !defined(TOR_ROUTER_H) */ diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index fd4c6ce0dd..7295c19653 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -1134,7 +1134,109 @@ init_mock_ed_keys(const crypto_pk_t *rsa_identity_key) } #undef MAKEKEY #undef MAKECERT -#endif +#endif /* defined(TOR_UNIT_TESTS) */ + +/** + * Print the ISO8601-formated <b>expiration</b> for a certificate with + * some <b>description</b> to stdout. + * + * For example, for a signing certificate, this might print out: + * signing-cert-expiry: 2017-07-25 08:30:15 UTC + */ +static void +print_cert_expiration(const char *expiration, + const char *description) +{ + fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration); +} + +/** + * Log when a certificate, <b>cert</b>, with some <b>description</b> and + * stored in a file named <b>fname</b>, is going to expire. + */ +static void +log_ed_cert_expiration(const tor_cert_t *cert, + const char *description, + const char *fname) { + char expiration[ISO_TIME_LEN+1]; + + if (BUG(!cert)) { /* If the specified key hasn't been loaded */ + log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.", + description); + } else { + format_local_iso_time(expiration, cert->valid_until); + log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.", + description, fname, expiration); + print_cert_expiration(expiration, description); + } +} + +/** + * Log when our master signing key certificate expires. Used when tor is given + * the --key-expiration command-line option. + * + * Returns 0 on success and 1 on failure. + */ +static int +log_master_signing_key_cert_expiration(const or_options_t *options) +{ + const tor_cert_t *signing_key; + char *fn = NULL; + int failed = 0; + time_t now = approx_time(); + + fn = options_get_datadir_fname2(options, "keys", "ed25519_signing_cert"); + + /* Try to grab our cached copy of the key. */ + signing_key = get_master_signing_key_cert(); + + tor_assert(server_identity_key_is_set()); + + /* Load our keys from disk, if necessary. */ + if (!signing_key) { + failed = load_ed_keys(options, now) < 0; + signing_key = get_master_signing_key_cert(); + } + + /* If we do have a signing key, log the expiration time. */ + if (signing_key) { + log_ed_cert_expiration(signing_key, "signing", fn); + } else { + log_warn(LD_OR, "Could not load signing key certificate from %s, so " \ + "we couldn't learn anything about certificate expiration.", fn); + } + + tor_free(fn); + + return failed; +} + +/** + * Log when a key certificate expires. Used when tor is given the + * --key-expiration command-line option. + * + * If an command argument is given, which should specify the type of + * key to get expiry information about (currently supported arguments + * are "sign"), get info about that type of certificate. Otherwise, + * print info about the supported arguments. + * + * Returns 0 on success and -1 on failure. + */ +int +log_cert_expiration(void) +{ + const or_options_t *options = get_options(); + const char *arg = options->command_arg; + + if (!strcmp(arg, "sign")) { + return log_master_signing_key_cert_expiration(options); + } else { + fprintf(stderr, "No valid argument to --key-expiration found!\n"); + fprintf(stderr, "Currently recognised arguments are: 'sign'\n"); + + return -1; + } +} const ed25519_public_key_t * get_master_identity_key(void) @@ -1160,7 +1262,7 @@ get_master_identity_keypair(void) { return master_identity_key; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ const ed25519_keypair_t * get_master_signing_keypair(void) diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h index c10cf32a71..3e67952ea0 100644 --- a/src/or/routerkeys.h +++ b/src/or/routerkeys.h @@ -63,6 +63,7 @@ MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert, const ed25519_public_key_t *master_id_pkey, const uint8_t *rsa_id_digest)); +int log_cert_expiration(void); int load_ed_keys(const or_options_t *options, time_t now); int should_make_new_ed_keys(const or_options_t *options, const time_t now); @@ -80,5 +81,5 @@ const ed25519_keypair_t *get_master_identity_keypair(void); void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key); #endif -#endif +#endif /* !defined(TOR_ROUTERKEYS_H) */ diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 4f4d417cce..af4f67dc12 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -150,7 +150,8 @@ typedef struct cert_list_t cert_list_t; /* static function prototypes */ static int compute_weighted_bandwidths(const smartlist_t *sl, bandwidth_weight_rule_t rule, - double **bandwidths_out); + double **bandwidths_out, + double *total_bandwidth_out); static const routerstatus_t *router_pick_trusteddirserver_impl( const smartlist_t *sourcelist, dirinfo_type_t auth, int flags, int *n_busy_out); @@ -1872,7 +1873,7 @@ router_picked_poor_directory_log(const routerstatus_t *rs) || !router_have_minimum_dir_info()) { return; } -#endif +#endif /* !LOG_FALSE_POSITIVES_DURING_BOOTSTRAP */ /* We couldn't find a node, or the one we have doesn't fit our preferences. * Sometimes this is normal, sometimes it can be a reachability issue. */ @@ -2306,7 +2307,7 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) { /* XXXX MOVE ? */ node_t fake_node; - const node_t *node = node_get_by_id(router->cache_info.identity_digest);; + const node_t *node = node_get_by_id(router->cache_info.identity_digest); if (node == NULL) { memset(&fake_node, 0, sizeof(fake_node)); fake_node.ri = (routerinfo_t *)router; @@ -2506,7 +2507,7 @@ smartlist_choose_node_by_bandwidth_weights(const smartlist_t *sl, double *bandwidths_dbl=NULL; uint64_t *bandwidths_u64=NULL; - if (compute_weighted_bandwidths(sl, rule, &bandwidths_dbl) < 0) + if (compute_weighted_bandwidths(sl, rule, &bandwidths_dbl, NULL) < 0) return NULL; bandwidths_u64 = tor_calloc(smartlist_len(sl), sizeof(uint64_t)); @@ -2526,18 +2527,24 @@ smartlist_choose_node_by_bandwidth_weights(const smartlist_t *sl, * smartlist_choose_node_by_bandwidth_weights, compute weighted bandwidth * values for each node and store them in a freshly allocated * *<b>bandwidths_out</b> of the same length as <b>sl</b>, and holding results - * as doubles. Return 0 on success, -1 on failure. */ + * as doubles. If <b>total_bandwidth_out</b> is non-NULL, set it to the total + * of all the bandwidths. + * Return 0 on success, -1 on failure. */ static int compute_weighted_bandwidths(const smartlist_t *sl, bandwidth_weight_rule_t rule, - double **bandwidths_out) + double **bandwidths_out, + double *total_bandwidth_out) { int64_t weight_scale; 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; - double *bandwidths; + double *bandwidths = NULL; + double total_bandwidth = 0.0; + + tor_assert(sl); + tor_assert(bandwidths_out); /* Can't choose exit and guard at same time */ tor_assert(rule == NO_WEIGHTING || @@ -2546,6 +2553,12 @@ compute_weighted_bandwidths(const smartlist_t *sl, rule == WEIGHT_FOR_MID || rule == WEIGHT_FOR_DIR); + *bandwidths_out = NULL; + + if (total_bandwidth_out) { + *total_bandwidth_out = 0.0; + } + if (smartlist_len(sl) == 0) { log_info(LD_CIRC, "Empty routerlist passed in to consensus weight node " @@ -2706,17 +2719,22 @@ compute_weighted_bandwidths(const smartlist_t *sl, final_weight = weight*this_bw; } - bandwidths[node_sl_idx] = final_weight + 0.5; + bandwidths[node_sl_idx] = final_weight; + total_bandwidth += final_weight; } SMARTLIST_FOREACH_END(node); log_debug(LD_CIRC, "Generated weighted bandwidths for rule %s based " "on weights " - "Wg=%f Wm=%f We=%f Wd=%f with total bw "U64_FORMAT, + "Wg=%f Wm=%f We=%f Wd=%f with total bw %f", bandwidth_weight_rule_to_string(rule), - Wg, Wm, We, Wd, U64_PRINTF_ARG(weighted_bw)); + Wg, Wm, We, Wd, total_bandwidth); *bandwidths_out = bandwidths; + if (total_bandwidth_out) { + *total_bandwidth_out = total_bandwidth; + } + return 0; } @@ -2733,7 +2751,8 @@ frac_nodes_with_descriptors(const smartlist_t *sl, if (smartlist_len(sl) == 0) return 0.0; - if (compute_weighted_bandwidths(sl, rule, &bandwidths) < 0) { + if (compute_weighted_bandwidths(sl, rule, &bandwidths, &total) < 0 || + total <= 0.0) { int n_with_descs = 0; SMARTLIST_FOREACH(sl, const node_t *, node, { if (node_has_descriptor(node)) @@ -2742,19 +2761,14 @@ frac_nodes_with_descriptors(const smartlist_t *sl, return ((double)n_with_descs) / (double)smartlist_len(sl); } - total = present = 0.0; + present = 0.0; SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { - const double bw = bandwidths[node_sl_idx]; - total += bw; if (node_has_descriptor(node)) - present += bw; + present += bandwidths[node_sl_idx]; } SMARTLIST_FOREACH_END(node); tor_free(bandwidths); - if (total < 1.0) - return 0; - return present / total; } @@ -2799,6 +2813,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, const int need_desc = (flags & CRN_NEED_DESC) != 0; const int pref_addr = (flags & CRN_PREF_ADDR) != 0; const int direct_conn = (flags & CRN_DIRECT_CONN) != 0; + const int rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0; smartlist_t *sl=smartlist_new(), *excludednodes=smartlist_new(); @@ -2810,12 +2825,19 @@ router_choose_random_node(smartlist_t *excludedsmartlist, rule = weight_for_exit ? WEIGHT_FOR_EXIT : (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID); - /* Exclude relays that allow single hop exit circuits. This is an obsolete - * option since 0.2.9.2-alpha and done by default in 0.3.1.0-alpha. */ - SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { if (node_allows_single_hop_exits(node)) { + /* Exclude relays that allow single hop exit circuits. This is an + * obsolete option since 0.2.9.2-alpha and done by default in + * 0.3.1.0-alpha. */ smartlist_add(excludednodes, node); - }); + } else if (rendezvous_v3 && + !node_supports_v3_rendezvous_point(node)) { + /* Exclude relays that do not support to rendezvous for a hidden service + * version 3. */ + smartlist_add(excludednodes, node); + } + } SMARTLIST_FOREACH_END(node); /* If the node_t is not found we won't be to exclude ourself but we * won't be able to pick ourself in router_choose_random_node() so @@ -2929,7 +2951,7 @@ hex_digest_nickname_decode(const char *hexdigest, * <b>hexdigest</b> is malformed, or it doesn't match. */ int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named) + const char *nickname) { char digest[DIGEST_LEN]; char nn_char='\0'; @@ -2938,30 +2960,20 @@ hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, if (hex_digest_nickname_decode(hexdigest, digest, &nn_char, nn_buf) == -1) return 0; - if (nn_char == '=' || nn_char == '~') { - if (!nickname) + if (nn_char == '=') { + return 0; + } + + if (nn_char == '~') { + if (!nickname) // XXX This seems wrong. -NM return 0; if (strcasecmp(nn_buf, nickname)) return 0; - if (nn_char == '=' && !is_named) - return 0; } return tor_memeq(digest, identity_digest, DIGEST_LEN); } -/** Return true iff <b>router</b> is listed as named in the current - * consensus. */ -int -router_is_named(const routerinfo_t *router) -{ - const char *digest = - networkstatus_get_router_digest_by_nickname(router->nickname); - - return (digest && - tor_memeq(digest, router->cache_info.identity_digest, DIGEST_LEN)); -} - /** 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 (NO_DIRINFO), or ALL_DIRINFO, any authority is okay. */ @@ -4057,7 +4069,7 @@ routerlist_remove_old_cached_routers_with_id(time_t now, signed_descriptor_t *r = smartlist_get(lst, i); tor_assert(tor_memeq(ident, r->identity_digest, DIGEST_LEN)); } -#endif +#endif /* 1 */ /* Check whether we need to do anything at all. */ { int mdpr = directory_caches_dir_info(get_options()) ? 2 : 1; @@ -4974,8 +4986,9 @@ max_dl_per_request(const or_options_t *options, int purpose) } /** Don't split our requests so finely that we are requesting fewer than - * this number per server. */ -#define MIN_DL_PER_REQUEST 4 + * this number per server. (Grouping more than this at once leads to + * diminishing returns.) */ +#define MIN_DL_PER_REQUEST 32 /** To prevent a single screwy cache from confusing us by selective reply, * try to split our requests into at least this many requests. */ #define MIN_REQUESTS 3 @@ -5041,7 +5054,7 @@ launch_descriptor_downloads(int purpose, } } - if (!authdir_mode_any_nonhidserv(options)) { + if (!authdir_mode(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 @@ -5063,8 +5076,9 @@ launch_descriptor_downloads(int purpose, if (n_per_request > max_dl_per_req) n_per_request = max_dl_per_req; - if (n_per_request < MIN_DL_PER_REQUEST) - n_per_request = MIN_DL_PER_REQUEST; + if (n_per_request < MIN_DL_PER_REQUEST) { + n_per_request = MIN(MIN_DL_PER_REQUEST, n_downloadable); + } if (n_downloadable > n_per_request) req_plural = rtr_plural = "s"; @@ -5172,7 +5186,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, smartlist_add(downloadable, rs->descriptor_digest); } SMARTLIST_FOREACH_END(rsp); - if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL) + if (!authdir_mode_v3(options) && smartlist_len(no_longer_old)) { routerlist_t *rl = router_get_routerlist(); log_info(LD_DIR, "%d router descriptors listed in consensus are " diff --git a/src/or/routerlist.h b/src/or/routerlist.h index e0ed4e623a..8384c7eb8c 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -80,7 +80,6 @@ const node_t *router_choose_random_node(smartlist_t *excludedsmartlist, struct routerset_t *excludedset, router_crn_flags_t flags); -int router_is_named(const routerinfo_t *router); int router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type); #define router_digest_is_trusted_dir(d) \ @@ -228,7 +227,7 @@ int hex_digest_nickname_decode(const char *hexdigest, char *nickname_out); int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named); + const char *nickname); #ifdef ROUTERLIST_PRIVATE STATIC int choose_array_element_by_weight(const uint64_t *entries, @@ -252,7 +251,7 @@ MOCK_DECL(STATIC void, initiate_descriptor_downloads, STATIC int router_is_already_dir_fetching(const tor_addr_port_t *ap, int serverdesc, int microdesc); -#endif +#endif /* defined(ROUTERLIST_PRIVATE) */ -#endif +#endif /* !defined(TOR_ROUTERLIST_H) */ diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 22521a3069..15cdb0bbde 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -388,9 +388,9 @@ static int check_signature_token(const char *digest, log_debug(LD_MM, "Area for %s has %lu allocated; using %lu.", \ name, (unsigned long)alloc, (unsigned long)used); \ STMT_END -#else +#else /* !(defined(DEBUG_AREA_ALLOC)) */ #define DUMP_AREA(a,name) STMT_NIL -#endif +#endif /* defined(DEBUG_AREA_ALLOC) */ /* Dump mechanism for unparseable descriptors */ @@ -701,7 +701,7 @@ dump_desc_populate_one_file, (const char *dirname, const char *f)) goto done; /* LCOV_EXCL_STOP */ } -#endif +#endif /* SIZE_MAX > UINT64_MAX */ if (BUG(st.st_size < 0)) { /* LCOV_EXCL_START * Should be impossible, since the OS isn't supposed to be b0rken. */ @@ -1425,9 +1425,9 @@ dump_distinct_digest_count(int severity) verified_digests = digestmap_new(); tor_log(severity, LD_GENERAL, "%d *distinct* router digests verified", digestmap_size(verified_digests)); -#else +#else /* !(defined(COUNT_DISTINCT_DIGESTS)) */ (void)severity; /* suppress "unused parameter" warning */ -#endif +#endif /* defined(COUNT_DISTINCT_DIGESTS) */ } /** Try to find an IPv6 OR port in <b>list</b> of directory_token_t's @@ -1996,7 +1996,6 @@ router_parse_entry_from_string(const char *s, const char *end, } tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE); - note_crypto_pk_op(VERIFY_RTR); #ifdef COUNT_DISTINCT_DIGESTS if (!verified_digests) verified_digests = digestmap_new(); @@ -2231,7 +2230,6 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, } if (key) { - note_crypto_pk_op(VERIFY_RTR); if (check_signature_token(digest, DIGEST_LEN, tok, key, 0, "extra-info") < 0) goto err; @@ -2708,7 +2706,11 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->supports_ed25519_hs_intro = protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4); rs->supports_v3_hsdir = - protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2); + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, + PROTOVER_HSDIR_V3); + rs->supports_v3_rendezvous_point = + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, + PROTOVER_HS_RENDEZVOUS_POINT_V3); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -2720,6 +2722,12 @@ routerstatus_parse_entry_from_string(memarea_t *area, tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha"); rs->protocols_known = 1; } + if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) { + /* Bug #22447 forces us to filter on this version. */ + if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) { + rs->supports_v3_hsdir = 0; + } + } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); } @@ -2856,7 +2864,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b) int networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) { - int64_t weight_scale; int64_t G=0, M=0, E=0, D=0, T=0; double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed; double Gtotal=0, Mtotal=0, Etotal=0; @@ -2864,7 +2871,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) int valid = 1; (void) consensus_method; - weight_scale = networkstatus_get_weight_scale_param(ns); + const int64_t weight_scale = networkstatus_get_weight_scale_param(ns); + tor_assert(weight_scale >= 1); Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1); Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1); Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1); @@ -2926,7 +2934,7 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) } Wgg /= weight_scale; - Wgm /= weight_scale; + Wgm /= weight_scale; (void) Wgm; // unused from here on. Wgd /= weight_scale; Wmg /= weight_scale; @@ -2934,8 +2942,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) Wme /= weight_scale; Wmd /= weight_scale; - Weg /= weight_scale; - Wem /= weight_scale; + Weg /= weight_scale; (void) Weg; // unused from here on. + Wem /= weight_scale; (void) Wem; // unused from here on. Wee /= weight_scale; Wed /= weight_scale; @@ -3360,8 +3368,8 @@ extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens) voter_identity = "consensus"; } - /* We extract both and on error, everything is stopped because it means - * the votes is malformed for the shared random value(s). */ + /* We extract both, and on error everything is stopped because it means + * the vote is malformed for the shared random value(s). */ if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) { log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s", voter_identity); @@ -5288,7 +5296,6 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, } /* Parse and verify signature. */ tok = find_by_keyword(tokens, R_SIGNATURE); - note_crypto_pk_op(VERIFY_RTR); if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0, "v2 rendezvous service descriptor") < 0) goto err; diff --git a/src/or/routerparse.h b/src/or/routerparse.h index 088f773c5e..c4c8635f4b 100644 --- a/src/or/routerparse.h +++ b/src/or/routerparse.h @@ -133,9 +133,9 @@ MOCK_DECL(STATIC int, router_compute_hash_final,(char *digest, digest_algorithm_t alg)); MOCK_DECL(STATIC int, signed_digest_equals, (const uint8_t *d1, const uint8_t *d2, size_t len)); -#endif +#endif /* defined(ROUTERPARSE_PRIVATE) */ #define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1" -#endif +#endif /* !defined(TOR_ROUTERPARSE_H) */ diff --git a/src/or/routerset.c b/src/or/routerset.c index 4906c6a51d..54e26ef943 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -362,7 +362,7 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, /* No routers are specified by type; all are given by name or digest. * we can do a lookup in O(len(routerset)). */ SMARTLIST_FOREACH(routerset->list, const char *, name, { - const node_t *node = node_get_by_nickname(name, 1); + const node_t *node = node_get_by_nickname(name, 0); if (node) { if (!running_only || node->is_running) if (!routerset_contains_node(excludeset, node)) diff --git a/src/or/routerset.h b/src/or/routerset.h index a63677b471..d8819ef3fd 100644 --- a/src/or/routerset.h +++ b/src/or/routerset.h @@ -82,6 +82,6 @@ struct routerset_t { * reloaded. */ bitarray_t *countries; }; -#endif -#endif +#endif /* defined(ROUTERSET_PRIVATE) */ +#endif /* !defined(TOR_ROUTERSET_H) */ diff --git a/src/or/scheduler.c b/src/or/scheduler.c index fac545fba7..cbf51447bf 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -1,46 +1,55 @@ -/* * Copyright (c) 2013-2017, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" - -#define TOR_CHANNEL_INTERNAL_ /* For channel_flush_some_cells() */ -#include "channel.h" +#include "config.h" #include "compat_libevent.h" #define SCHEDULER_PRIVATE_ +#define SCHEDULER_KIST_PRIVATE #include "scheduler.h" +#include "main.h" +#include "buffers.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channeltls.h" #include <event2/event.h> -/* - * 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; - /** * \file scheduler.c * \brief Channel scheduling system: decides which channels should send and * receive when. * - * This module implements a scheduler algorithm, to decide - * which channels should send/receive when. + * This module is the global/common parts of the scheduling system. This system + * is what decides what channels get to send cells on their circuits and when. + * + * Terms: + * - "Scheduling system": the collection of scheduler*.{h,c} files and their + * aggregate behavior. + * - "Scheduler implementation": a scheduler_t. The scheduling system has one + * active scheduling implementation at a time. + * + * In this file you will find state that any scheduler implementation can have + * access to as well as the functions the rest of Tor uses to interact with the + * scheduling system. * * The earliest versions of Tor approximated a kind of round-robin system - * among active connections, but only approximated it. + * among active connections, but only approximated it. It would only consider + * one connection (roughly equal to a channel in today's terms) at a time, and + * thus could only prioritize circuits against others on the same connection. + * + * Then in response to the KIST paper[0], Tor implemented a global + * circuit scheduler. It was supposed to prioritize circuits across many + * channels, but wasn't effective. It is preserved in scheduler_vanilla.c. * - * Now, 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: + * [0]: http://www.robgjansen.com/publications/kist-sec2014.pdf + * + * Then we actually got around to implementing KIST for real. We decided to + * modularize the scheduler so new ones can be implemented. You can find KIST + * in scheduler_kist.c. + * + * Channels have one of four scheduling states based on whether or not they + * have cells to send and whether or not they are able to send. * * <ol> * <li> @@ -90,7 +99,7 @@ static uint32_t sched_max_flush_cells = 16; * <ul> * <li>Not open for writes/no cells by arrival of cells on an attached * circuit - * <li> Open for writes/has cells by filling an output buffer without + * <li>Open for writes/has cells by filling an output buffer without * draining all cells from attached circuits * </ul> * <li> Transitions to: @@ -107,9 +116,9 @@ static uint32_t sched_max_flush_cells = 16; * SCHED_CHAN_PENDING. * <li>Transitions from: * <ul> - * <li> Not open for writes/has cells by the connection_or_flushed_some() + * <li>Not open for writes/has cells by the connection_or_flushed_some() * path - * <li> Open for writes/no cells by the append_cell_to_circuit_queue() + * <li>Open for writes/no cells by the append_cell_to_circuit_queue() * path * </ul> * <li> Transitions to: @@ -125,85 +134,246 @@ static uint32_t sched_max_flush_cells = 16; * </ol> * * 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. + * states by calling scheduler functions. The scheduling system builds up a + * list of channels in the SCHED_CHAN_PENDING state that the scheduler + * implementation should then use when it runs. Scheduling implementations need + * to properly update channel states during their scheduler_t->run() function + * as that is the only opportunity for channels to move from SCHED_CHAN_PENDING + * to any other state. + * + * The remainder of this file is a small amount of state that any scheduler + * implementation should have access to, and the functions the rest of Tor uses + * to interact with the scheduling system. */ -/* Scheduler global data structures */ +/***************************************************************************** + * Scheduling system state + * + * State that can be accessed from any scheduler implementation (but not + * outside the scheduling system) + *****************************************************************************/ -/* +/** DOCDOC */ +STATIC const scheduler_t *the_scheduler; + +/** * 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 + * and can accept them to send. The enum scheduler_state in channel_t * is reserved for our use. + * + * Priority queue of channels that can write and have cells (pending work) */ - -/* 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 int have_logged_kist_suddenly_disabled = 0; -STATIC uint64_t queue_heuristic = 0; +/***************************************************************************** + * Scheduling system static function definitions + * + * Functions that can only be accessed from this file. + *****************************************************************************/ -/* - * Timestamp for last queue heuristic update +/** Return a human readable string for the given scheduler type. */ +static const char * +get_scheduler_type_string(scheduler_types_t type) +{ + switch (type) { + case SCHEDULER_VANILLA: + return "Vanilla"; + case SCHEDULER_KIST: + return "KIST"; + case SCHEDULER_KIST_LITE: + return "KISTLite"; + case SCHEDULER_NONE: + /* fallthrough */ + default: + tor_assert_unreached(); + return "(N/A)"; + } +} + +/** + * 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; -STATIC time_t queue_heuristic_timestamp = 0; + log_debug(LD_SCHED, "Scheduler event callback called"); -/* Scheduler static function declarations */ + /* Run the scheduler. This is a mandatory function. */ -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 + /* We might as well assert on this. If this function doesn't exist, no cells + * are getting scheduled. Things are very broken. scheduler_t says the run() + * function is mandatory. */ + tor_assert(the_scheduler->run); + the_scheduler->run(); -/* Scheduler function implementations */ + /* Schedule itself back in if it has more work. */ -/** Free everything and shut down the scheduling system */ + /* Again, might as well assert on this mandatory scheduler_t function. If it + * doesn't exist, there's no way to tell libevent to run the scheduler again + * in the future. */ + tor_assert(the_scheduler->schedule); + the_scheduler->schedule(); +} -void -scheduler_free_all(void) +/** Using the global options, select the scheduler we should be using. */ +static void +select_scheduler(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"); + scheduler_t *new_scheduler = NULL; + +#ifdef TOR_UNIT_TESTS + /* This is hella annoying to set in the options for every test that passes + * through the scheduler and there are many so if we don't explicitly have + * a list of types set, just put the vanilla one. */ + if (get_options()->SchedulerTypes_ == NULL) { + the_scheduler = get_vanilla_scheduler(); + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + + /* This list is ordered that is first entry has the first priority. Thus, as + * soon as we find a scheduler type that we can use, we use it and stop. */ + SMARTLIST_FOREACH_BEGIN(get_options()->SchedulerTypes_, int *, type) { + switch (*type) { + case SCHEDULER_VANILLA: + new_scheduler = get_vanilla_scheduler(); + goto end; + case SCHEDULER_KIST: + if (!scheduler_can_use_kist()) { +#ifdef HAVE_KIST_SUPPORT + if (!have_logged_kist_suddenly_disabled) { + /* We should only log this once in most cases. If it was the kernel + * losing support for kist that caused scheduler_can_use_kist() to + * return false, then this flag makes sure we only log this message + * once. If it was the consensus that switched from "yes use kist" + * to "no don't use kist", then we still set the flag so we log + * once, but we unset the flag elsewhere if we ever can_use_kist() + * again. + */ + have_logged_kist_suddenly_disabled = 1; + log_notice(LD_SCHED, "Scheduler type KIST has been disabled by " + "the consensus or no kernel support."); + } +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + log_info(LD_SCHED, "Scheduler type KIST not built in"); +#endif /* defined(HAVE_KIST_SUPPORT) */ + continue; + } + /* This flag will only get set in one of two cases: + * 1 - the kernel lost support for kist. In that case, we don't expect to + * ever end up here + * 2 - the consensus went from "yes use kist" to "no don't use kist". + * We might end up here if the consensus changes back to "yes", in which + * case we might want to warn the user again if it goes back to "no" + * yet again. Thus we unset the flag */ + have_logged_kist_suddenly_disabled = 0; + new_scheduler = get_kist_scheduler(); + scheduler_kist_set_full_mode(); + goto end; + case SCHEDULER_KIST_LITE: + new_scheduler = get_kist_scheduler(); + scheduler_kist_set_lite_mode(); + goto end; + case SCHEDULER_NONE: + /* fallthrough */ + default: + /* Our option validation should have caught this. */ + tor_assert_unreached(); } - tor_event_free(run_sched_ev); - run_sched_ev = NULL; + } SMARTLIST_FOREACH_END(type); + + end: + if (new_scheduler == NULL) { + log_err(LD_SCHED, "Tor was unable to select a scheduler type. Please " + "make sure Schedulers is correctly configured with " + "what Tor does support."); + /* We weren't able to choose a scheduler which means that none of the ones + * set in Schedulers are supported or usable. We will respect the user + * wishes of using what it has been configured and don't do a sneaky + * fallback. Because this can be changed at runtime, we have to stop tor + * right now. */ + exit(1); } - if (channels_pending) { - smartlist_free(channels_pending); - channels_pending = NULL; - } + /* Set the chosen scheduler. */ + the_scheduler = new_scheduler; } /** - * Comparison function to use when sorting pending channels + * Helper function called from a few different places. It changes the + * scheduler implementation, if necessary. And if it did, it then tells the + * old one to free its state and the new one to initialize. */ +static void +set_scheduler(void) +{ + const scheduler_t *old_scheduler = the_scheduler; + scheduler_types_t old_scheduler_type = SCHEDULER_NONE; + + /* We keep track of the type in order to log only if the type switched. We + * can't just use the scheduler pointers because KIST and KISTLite share the + * same object. */ + if (the_scheduler) { + old_scheduler_type = the_scheduler->type; + } -MOCK_IMPL(STATIC int, + /* From the options, select the scheduler type to set. */ + select_scheduler(); + tor_assert(the_scheduler); + + /* We look at the pointer difference in case the old sched and new sched + * share the same scheduler object, as is the case with KIST and KISTLite. */ + if (old_scheduler != the_scheduler) { + /* Allow the old scheduler to clean up, if needed. */ + if (old_scheduler && old_scheduler->free_all) { + old_scheduler->free_all(); + } + + /* Initialize the new scheduler. */ + if (the_scheduler->init) { + the_scheduler->init(); + } + } + + /* Finally we notice log if we switched schedulers. We use the type in case + * two schedulers share a scheduler object. */ + if (old_scheduler_type != the_scheduler->type) { + log_notice(LD_CONFIG, "Scheduler type %s has been enabled.", + get_scheduler_type_string(the_scheduler->type)); + } +} + +/***************************************************************************** + * Scheduling system private function definitions + * + * Functions that can only be accessed from scheduler*.c + *****************************************************************************/ + +/** Return the pending channel list. */ +smartlist_t * +get_channels_pending(void) +{ + return channels_pending; +} + +/** Comparison function to use when sorting pending channels. */ +MOCK_IMPL(int, scheduler_compare_channels, (const void *c1_v, const void *c2_v)) { - channel_t *c1 = NULL, *c2 = NULL; + const 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; @@ -211,11 +381,8 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v)) tor_assert(c1_v); tor_assert(c2_v); - c1 = (channel_t *)(c1_v); - c2 = (channel_t *)(c2_v); - - tor_assert(c1); - tor_assert(c2); + c1 = (const channel_t *)(c1_v); + c2 = (const channel_t *)(c2_v); if (c1 != c2) { if (circuitmux_get_policy(c1->cmux) == @@ -242,36 +409,84 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v)) } } -/* - * Scheduler event callback; this should get triggered once per event loop - * if any scheduling work was created during the event loop. - */ +/***************************************************************************** + * Scheduling system global functions + * + * Functions that can be accessed from anywhere in Tor. + *****************************************************************************/ -static void -scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) +/** + * This is how the scheduling system is notified of Tor's configuration + * changing. For example: a SIGHUP was issued. + */ +void +scheduler_conf_changed(void) { - (void)fd; - (void)events; - (void)arg; - log_debug(LD_SCHED, "Scheduler event callback called"); + /* Let the scheduler decide what it should do. */ + set_scheduler(); - tor_assert(run_sched_ev); + /* Then tell the (possibly new) scheduler that we have new options. */ + if (the_scheduler->on_new_options) { + the_scheduler->on_new_options(); + } +} - /* Run the scheduler */ - scheduler_run(); +/** + * Whenever we get a new consensus, this function is called. + */ +void +scheduler_notify_networkstatus_changed(const networkstatus_t *old_c, + const networkstatus_t *new_c) +{ + /* Maybe the consensus param made us change the scheduler. */ + set_scheduler(); - /* Do we have more work to do? */ - if (scheduler_more_work()) scheduler_retrigger(); + /* Then tell the (possibly new) scheduler that we have a new consensus */ + if (the_scheduler->on_new_consensus) { + the_scheduler->on_new_consensus(old_c, new_c); + } } -/** Mark a channel as no longer ready to accept writes */ +/** + * Free everything scheduling-related from main.c. Note this is only called + * when Tor is shutting down, while scheduler_t->free_all() is called both when + * Tor is shutting down and when we are switching schedulers. + */ +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) { + /* We don't have ownership of the objects in this list. */ + smartlist_free(channels_pending); + channels_pending = NULL; + } + + if (the_scheduler && the_scheduler->free_all) { + the_scheduler->free_all(); + } + the_scheduler = NULL; +} + +/** 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_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } /* If it's already in pending, we can put it in waiting_to_write */ if (chan->scheduler_state == SCHED_CHAN_PENDING) { @@ -282,7 +497,7 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan)) */ smartlist_pqueue_remove(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; log_debug(LD_SCHED, @@ -304,17 +519,18 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan)) } } -/** Mark a channel as having waiting cells */ - +/** 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); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } - /* First, check if this one also writeable */ + /* First, check if it's 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 @@ -324,13 +540,15 @@ scheduler_channel_has_waiting_cells,(channel_t *chan)) chan->scheduler_state = SCHED_CHAN_PENDING; smartlist_pqueue_add(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(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; + /* If we made a channel pending, we potentially have scheduling work to + * do. */ + the_scheduler->schedule(); } else { /* * It's not in waiting_for_cells, so it can't become pending; it's @@ -345,250 +563,119 @@ scheduler_channel_has_waiting_cells,(channel_t *chan)) 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(); +/** Add the scheduler event to the set of pending events with next_run being + * the longest time libevent should wait before triggering the event. */ +void +scheduler_ev_add(const struct timeval *next_run) +{ + tor_assert(run_sched_ev); + tor_assert(next_run); + if (BUG(event_add(run_sched_ev, next_run) < 0)) { + log_warn(LD_SCHED, "Adding to libevent failed. Next run time was set to: " + "%ld.%06ld", next_run->tv_sec, (long)next_run->tv_usec); + return; + } } -/** Set up the scheduling system */ +/** Make the scheduler event active with the given flags. */ +void +scheduler_ev_active(int flags) +{ + tor_assert(run_sched_ev); + event_active(run_sched_ev, flags, 1); +} +/* + * Initialize everything scheduling-related from config.c. Note this is only + * called when Tor is starting up, while scheduler_t->init() is called both + * when Tor is starting up and when we are switching schedulers. + */ void scheduler_init(void) { log_debug(LD_SCHED, "Initting scheduler"); - tor_assert(!run_sched_ev); + // Two '!' because we really do want to check if the pointer is non-NULL + IF_BUG_ONCE(!!run_sched_ev) { + log_warn(LD_SCHED, "We should not already have a libevent scheduler event." + "I'll clean the old one up, but this is odd."); + tor_event_free(run_sched_ev); + run_sched_ev = NULL; + } 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; + set_scheduler(); } -/** 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 */ - +/* + * If a channel is going away, this is how the scheduling system is informed + * so it can do any freeing necessary. This ultimately calls + * scheduler_t->on_channel_free() so the current scheduler can release any + * state specific to this channel. + */ 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); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; } - 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 *, readd_chan) { - readd_chan->scheduler_state = SCHED_CHAN_PENDING; - smartlist_pqueue_add(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), - readd_chan); - } SMARTLIST_FOREACH_END(readd_chan); - smartlist_free(to_readd); + if (chan->scheduler_state == SCHED_CHAN_PENDING) { + if (SCHED_BUG(smartlist_pos(channels_pending, chan) == -1, chan)) { + log_warn(LD_SCHED, "Scheduler asked to release channel %" PRIu64 " " + "but it wasn't in channels_pending", + chan->global_identifier); + } else { + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), + chan); } - - 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); + if (the_scheduler->on_channel_free) { + the_scheduler->on_channel_free(chan); + } + chan->scheduler_state = SCHED_CHAN_IDLE; } -#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_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } /* 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. */ + log_debug(LD_SCHED, "chan=%" PRIu64 " became pending", + chan->global_identifier); smartlist_pqueue_add(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(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; + /* We just made a channel pending, we have scheduling work to do. */ + the_scheduler->schedule(); } else { /* * It's not in SCHED_CHAN_WAITING_TO_WRITE, so it can't become pending; @@ -602,137 +689,70 @@ scheduler_channel_wants_writes(channel_t *chan) 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(); +/* Log warn the given channel and extra scheduler context as well. This is + * used by SCHED_BUG() in order to be able to extract as much information as + * we can when we hit a bug. Channel chan can be NULL. */ +void +scheduler_bug_occurred(const channel_t *chan) +{ + char buf[128]; + + if (chan != NULL) { + const size_t outbuf_len = + buf_datalen(TO_CONN(BASE_CHAN_TO_TLS((channel_t *) chan)->conn)->outbuf); + tor_snprintf(buf, sizeof(buf), + "Channel %" PRIu64 " in state %s and scheduler state %d." + " Num cells on cmux: %d. Connection outbuf len: %lu.", + chan->global_identifier, + channel_state_to_string(chan->state), + chan->scheduler_state, circuitmux_num_cells(chan->cmux), + (unsigned long)outbuf_len); + } + + { + char *msg; + /* Rate limit every 60 seconds. If we start seeing this every 60 sec, we + * know something is stuck/wrong. It *should* be loud but not too much. */ + static ratelim_t rlimit = RATELIM_INIT(60); + if ((msg = rate_limit_log(&rlimit, approx_time()))) { + log_warn(LD_BUG, "%s Num pending channels: %d. " + "Channel in pending list: %s.%s", + (chan != NULL) ? buf : "No channel in bug context.", + smartlist_len(channels_pending), + (smartlist_pos(channels_pending, chan) == -1) ? "no" : "yes", + msg); + tor_free(msg); + } + } } -/** - * Notify the scheduler that a channel's position in the pqueue may have - * changed - */ +#ifdef TOR_UNIT_TESTS +/* + * Notify scheduler that a channel's queue position may have changed. + */ void scheduler_touch_channel(channel_t *chan) { - tor_assert(chan); + IF_BUG_ONCE(!chan) { + return; + } 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), + offsetof(channel_t, sched_heap_idx), chan); smartlist_pqueue_add(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(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; -} +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/scheduler.h b/src/or/scheduler.h index e29c13de7e..47c98f096a 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -1,9 +1,9 @@ -/* * Copyright (c) 2013-2017, The Tor Project, Inc. */ +/* * Copyright (c) 2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file scheduler.h - * \brief Header file for scheduler.c + * \brief Header file for scheduler*.c **/ #ifndef TOR_SCHEDULER_H @@ -13,45 +13,205 @@ #include "channel.h" #include "testsupport.h" -/* Global-visibility scheduler functions */ +/** Scheduler type, we build an ordered list with those values from the + * parsed strings in Schedulers. The reason to do such a thing is so we can + * quickly and without parsing strings select the scheduler at anytime. */ +typedef enum { + SCHEDULER_NONE = -1, + SCHEDULER_VANILLA = 1, + SCHEDULER_KIST = 2, + SCHEDULER_KIST_LITE = 3, +} scheduler_types_t; -/* 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)); +/** + * A scheduler implementation is a collection of function pointers. If you + * would like to add a new scheduler called foo, create scheduler_foo.c, + * implement at least the mandatory ones, and implement get_foo_scheduler() + * that returns a complete scheduler_t for your foo scheduler. See + * scheduler_kist.c for an example. + * + * These function pointers SHOULD NOT be used anywhere outside of the + * scheduling source files. The rest of Tor should communicate with the + * scheduling system through the functions near the bottom of this file, and + * those functions will call into the current scheduler implementation as + * necessary. + * + * If your scheduler doesn't need to implement something (for example: it + * doesn't create any state for itself, thus it has nothing to free when Tor + * is shutting down), then set that function pointer to NULL. + */ +typedef struct scheduler_s { + /* Scheduler type. This is used for logging when the scheduler is switched + * during runtime. */ + scheduler_types_t type; -/* 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); + /* (Optional) To be called when we want to prepare a scheduler for use. + * Perhaps Tor just started and we are the lucky chosen scheduler, or + * perhaps Tor is switching to this scheduler. No matter the case, this is + * where we would prepare any state and initialize parameters. You might + * think of this as the opposite of free_all(). */ + void (*init)(void); -/* Notify the scheduler of a channel being closed */ -MOCK_DECL(void,scheduler_release_channel,(channel_t *chan)); + /* (Optional) To be called when we want to tell the scheduler to delete all + * of its state (if any). Perhaps Tor is shutting down or perhaps we are + * switching schedulers. */ + void (*free_all)(void); -/* Notify scheduler of queue size adjustments */ -void scheduler_adjust_queue_size(channel_t *chan, int dir, uint64_t adj); + /* (Mandatory) Libevent controls the main event loop in Tor, and this is + * where we register with libevent the next execution of run_sched_ev [which + * ultimately calls run()]. */ + void (*schedule)(void); -/* Notify scheduler that a channel's queue position may have changed */ -void scheduler_touch_channel(channel_t *chan); + /* (Mandatory) This is the heart of a scheduler! This is where the + * excitement happens! Here libevent has given us the chance to execute, and + * we should do whatever we need to do in order to move some cells from + * their circuit queues to output buffers in an intelligent manner. We + * should do this quickly. When we are done, we'll try to schedule() ourself + * if more work needs to be done to setup the next scheduling run. */ + void (*run)(void); + + /* + * External event not related to the scheduler but that can influence it. + */ + + /* (Optional) To be called whenever Tor finds out about a new consensus. + * First the scheduling system as a whole will react to the new consensus + * and change the scheduler if needed. After that, the current scheduler + * (which might be new) will call this so it has the chance to react to the + * new consensus too. If there's a consensus parameter that your scheduler + * wants to keep an eye on, this is where you should check for it. */ + void (*on_new_consensus)(const networkstatus_t *old_c, + const networkstatus_t *new_c); + + /* (Optional) To be called when a channel is being freed. Sometimes channels + * go away (for example: the relay on the other end is shutting down). If + * the scheduler keeps any channel-specific state and has memory to free + * when channels go away, implement this and free it here. */ + void (*on_channel_free)(const channel_t *); -/* Adjust the watermarks from config file*/ -void scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush); + /* (Optional) To be called whenever Tor is reloading configuration options. + * For example: SIGHUP was issued and Tor is rereading its torrc. A + * scheduler should use this as an opportunity to parse and cache torrc + * options so that it doesn't have to call get_options() all the time. */ + void (*on_new_options)(void); +} scheduler_t; -/* Things only scheduler.c and its test suite should see */ +/***************************************************************************** + * Globally visible scheduler variables/values + * + * These are variables/constants that all of Tor should be able to see. + *****************************************************************************/ +/* Default interval that KIST runs (in ms). */ +#define KIST_SCHED_RUN_INTERVAL_DEFAULT 10 +/* Minimum interval that KIST runs. This value disables KIST. */ +#define KIST_SCHED_RUN_INTERVAL_MIN 0 +/* Maximum interval that KIST runs (in ms). */ +#define KIST_SCHED_RUN_INTERVAL_MAX 100 + +/***************************************************************************** + * Globally visible scheduler functions + * + * These functions are how the rest of Tor communicates with the scheduling + * system. + *****************************************************************************/ + +void scheduler_init(void); +void scheduler_free_all(void); +void scheduler_conf_changed(void); +void scheduler_notify_networkstatus_changed(const networkstatus_t *old_c, + const networkstatus_t *new_c); +MOCK_DECL(void, scheduler_release_channel, (channel_t *chan)); + +/* + * Ways for a channel to interact with the scheduling system. A channel only + * really knows (i) whether or not it has cells it wants to send, and + * (ii) whether or not it would like to write. + */ +void scheduler_channel_wants_writes(channel_t *chan); +MOCK_DECL(void, scheduler_channel_doesnt_want_writes, (channel_t *chan)); +MOCK_DECL(void, scheduler_channel_has_waiting_cells, (channel_t *chan)); + +/***************************************************************************** + * Private scheduler functions + * + * These functions are only visible to the scheduling system, the current + * scheduler implementation, and tests. + *****************************************************************************/ #ifdef SCHEDULER_PRIVATE_ -MOCK_DECL(STATIC int, scheduler_compare_channels, + +/********************************* + * Defined in scheduler.c + *********************************/ + +/* Triggers a BUG() and extra information with chan if available. */ +#define SCHED_BUG(cond, chan) \ + (PREDICT_UNLIKELY(cond) ? \ + ((BUG(cond)) ? (scheduler_bug_occurred(chan), 1) : 0) : 0) + +void scheduler_bug_occurred(const channel_t *chan); + +smartlist_t *get_channels_pending(void); +MOCK_DECL(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); +void scheduler_ev_active(int flags); +void scheduler_ev_add(const struct timeval *next_run); #ifdef TOR_UNIT_TESTS extern smartlist_t *channels_pending; extern struct event *run_sched_ev; -extern uint64_t queue_heuristic; -extern time_t queue_heuristic_timestamp; -#endif -#endif +extern const scheduler_t *the_scheduler; +void scheduler_touch_channel(channel_t *chan); +#endif /* defined(TOR_UNIT_TESTS) */ + +/********************************* + * Defined in scheduler_kist.c + *********************************/ + +#ifdef SCHEDULER_KIST_PRIVATE + +/* Socket table entry which holds information of a channel's socket and kernel + * TCP information. Only used by KIST. */ +typedef struct socket_table_ent_s { + HT_ENTRY(socket_table_ent_s) node; + const channel_t *chan; + /* Amount written this scheduling run */ + uint64_t written; + /* Amount that can be written this scheduling run */ + uint64_t limit; + /* TCP info from the kernel */ + uint32_t cwnd; + uint32_t unacked; + uint32_t mss; + uint32_t notsent; +} socket_table_ent_t; + +typedef HT_HEAD(outbuf_table_s, outbuf_table_ent_s) outbuf_table_t; + +MOCK_DECL(int, channel_should_write_to_kernel, + (outbuf_table_t *table, channel_t *chan)); +MOCK_DECL(void, channel_write_to_kernel, (channel_t *chan)); +MOCK_DECL(void, update_socket_info_impl, (socket_table_ent_t *ent)); + +int scheduler_can_use_kist(void); +void scheduler_kist_set_full_mode(void); +void scheduler_kist_set_lite_mode(void); +scheduler_t *get_kist_scheduler(void); +int kist_scheduler_run_interval(const networkstatus_t *ns); + +#ifdef TOR_UNIT_TESTS +extern int32_t sched_run_interval; +#endif /* TOR_UNIT_TESTS */ + +#endif /* defined(SCHEDULER_KIST_PRIVATE) */ + +/********************************* + * Defined in scheduler_vanilla.c + *********************************/ + +scheduler_t *get_vanilla_scheduler(void); + +#endif /* defined(SCHEDULER_PRIVATE_) */ #endif /* !defined(TOR_SCHEDULER_H) */ diff --git a/src/or/scheduler_kist.c b/src/or/scheduler_kist.c new file mode 100644 index 0000000000..19a8db5170 --- /dev/null +++ b/src/or/scheduler_kist.c @@ -0,0 +1,832 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define SCHEDULER_KIST_PRIVATE + +#include <event2/event.h> + +#include "or.h" +#include "buffers.h" +#include "config.h" +#include "connection.h" +#include "networkstatus.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#include "channeltls.h" +#define SCHEDULER_PRIVATE_ +#include "scheduler.h" + +#define TLS_PER_CELL_OVERHEAD 29 + +#ifdef HAVE_KIST_SUPPORT +/* Kernel interface needed for KIST. */ +#include <netinet/tcp.h> +#include <linux/sockios.h> +#endif /* HAVE_KIST_SUPPORT */ + +/***************************************************************************** + * Data structures and supporting functions + *****************************************************************************/ + +/* Socket_table hash table stuff. The socket_table keeps track of per-socket + * limit information imposed by kist and used by kist. */ + +static uint32_t +socket_table_ent_hash(const socket_table_ent_t *ent) +{ + return (uint32_t)ent->chan->global_identifier; +} + +static unsigned +socket_table_ent_eq(const socket_table_ent_t *a, const socket_table_ent_t *b) +{ + return a->chan == b->chan; +} + +typedef HT_HEAD(socket_table_s, socket_table_ent_s) socket_table_t; + +static socket_table_t socket_table = HT_INITIALIZER(); + +HT_PROTOTYPE(socket_table_s, socket_table_ent_s, node, socket_table_ent_hash, + socket_table_ent_eq) +HT_GENERATE2(socket_table_s, socket_table_ent_s, node, socket_table_ent_hash, + socket_table_ent_eq, 0.6, tor_reallocarray, tor_free_) + +/* outbuf_table hash table stuff. The outbuf_table keeps track of which + * channels have data sitting in their outbuf so the kist scheduler can force + * a write from outbuf to kernel periodically during a run and at the end of a + * run. */ + +typedef struct outbuf_table_ent_s { + HT_ENTRY(outbuf_table_ent_s) node; + channel_t *chan; +} outbuf_table_ent_t; + +static uint32_t +outbuf_table_ent_hash(const outbuf_table_ent_t *ent) +{ + return (uint32_t)ent->chan->global_identifier; +} + +static unsigned +outbuf_table_ent_eq(const outbuf_table_ent_t *a, const outbuf_table_ent_t *b) +{ + return a->chan->global_identifier == b->chan->global_identifier; +} + +HT_PROTOTYPE(outbuf_table_s, outbuf_table_ent_s, node, outbuf_table_ent_hash, + outbuf_table_ent_eq) +HT_GENERATE2(outbuf_table_s, outbuf_table_ent_s, node, outbuf_table_ent_hash, + outbuf_table_ent_eq, 0.6, tor_reallocarray, tor_free_) + +/***************************************************************************** + * Other internal data + *****************************************************************************/ + +/* Store the last time the scheduler was run so we can decide when to next run + * the scheduler based on it. */ +static monotime_t scheduler_last_run; +/* This is a factor for the extra_space calculation in kist per-socket limits. + * It is the number of extra congestion windows we want to write to the kernel. + */ +static double sock_buf_size_factor = 1.0; +/* How often the scheduler runs. */ +STATIC int sched_run_interval = KIST_SCHED_RUN_INTERVAL_DEFAULT; + +#ifdef HAVE_KIST_SUPPORT +/* Indicate if KIST lite mode is on or off. We can disable it at runtime. + * Important to have because of the KISTLite -> KIST possible transition. */ +static unsigned int kist_lite_mode = 0; +/* Indicate if we don't have the kernel support. This can happen if the kernel + * changed and it doesn't recognized the values passed to the syscalls needed + * by KIST. In that case, fallback to the naive approach. */ +static unsigned int kist_no_kernel_support = 0; +#else /* !(defined(HAVE_KIST_SUPPORT)) */ +static unsigned int kist_lite_mode = 1; +#endif /* defined(HAVE_KIST_SUPPORT) */ + +/***************************************************************************** + * Internally called function implementations + *****************************************************************************/ + +/* Little helper function to get the length of a channel's output buffer */ +static inline size_t +channel_outbuf_length(channel_t *chan) +{ + /* In theory, this can not happen because we can not scheduler a channel + * without a connection that has its outbuf initialized. Just in case, bug + * on this so we can understand a bit more why it happened. */ + if (SCHED_BUG(BASE_CHAN_TO_TLS(chan)->conn == NULL, chan)) { + return 0; + } + return buf_datalen(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn)->outbuf); +} + +/* Little helper function for HT_FOREACH_FN. */ +static int +each_channel_write_to_kernel(outbuf_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + channel_write_to_kernel(ent->chan); + return 0; /* Returning non-zero removes the element from the table. */ +} + +/* Free the given outbuf table entry ent. */ +static int +free_outbuf_info_by_ent(outbuf_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + log_debug(LD_SCHED, "Freeing outbuf table entry from chan=%" PRIu64, + ent->chan->global_identifier); + tor_free(ent); + return 1; /* So HT_FOREACH_FN will remove the element */ +} + +/* Free the given socket table entry ent. */ +static int +free_socket_info_by_ent(socket_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + log_debug(LD_SCHED, "Freeing socket table entry from chan=%" PRIu64, + ent->chan->global_identifier); + tor_free(ent); + return 1; /* So HT_FOREACH_FN will remove the element */ +} + +/* Clean up socket_table. Probably because the KIST sched impl is going away */ +static void +free_all_socket_info(void) +{ + HT_FOREACH_FN(socket_table_s, &socket_table, free_socket_info_by_ent, NULL); + HT_CLEAR(socket_table_s, &socket_table); +} + +static socket_table_ent_t * +socket_table_search(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t search, *ent = NULL; + search.chan = chan; + ent = HT_FIND(socket_table_s, table, &search); + return ent; +} + +/* Free a socket entry in table for the given chan. */ +static void +free_socket_info_by_chan(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (!ent) + return; + log_debug(LD_SCHED, "scheduler free socket info for chan=%" PRIu64, + chan->global_identifier); + HT_REMOVE(socket_table_s, table, ent); + free_socket_info_by_ent(ent, NULL); +} + +/* Perform system calls for the given socket in order to calculate kist's + * per-socket limit as documented in the function body. */ +MOCK_IMPL(void, +update_socket_info_impl, (socket_table_ent_t *ent)) +{ +#ifdef HAVE_KIST_SUPPORT + int64_t tcp_space, extra_space; + const tor_socket_t sock = + TO_CONN(BASE_CHAN_TO_TLS((channel_t *) ent->chan)->conn)->s; + struct tcp_info tcp; + socklen_t tcp_info_len = sizeof(tcp); + + if (kist_no_kernel_support || kist_lite_mode) { + goto fallback; + } + + /* Gather information */ + if (getsockopt(sock, SOL_TCP, TCP_INFO, (void *)&(tcp), &tcp_info_len) < 0) { + if (errno == EINVAL) { + /* Oops, this option is not provided by the kernel, we'll have to + * disable KIST entirely. This can happen if tor was built on a machine + * with the support previously or if the kernel was updated and lost the + * support. */ + log_notice(LD_SCHED, "Looks like our kernel doesn't have the support " + "for KIST anymore. We will fallback to the naive " + "approach. Remove KIST from the Schedulers list " + "to disable."); + kist_no_kernel_support = 1; + } + goto fallback; + } + if (ioctl(sock, SIOCOUTQNSD, &(ent->notsent)) < 0) { + if (errno == EINVAL) { + log_notice(LD_SCHED, "Looks like our kernel doesn't have the support " + "for KIST anymore. We will fallback to the naive " + "approach. Remove KIST from the Schedulers list " + "to disable."); + /* Same reason as the above. */ + kist_no_kernel_support = 1; + } + goto fallback; + } + ent->cwnd = tcp.tcpi_snd_cwnd; + ent->unacked = tcp.tcpi_unacked; + ent->mss = tcp.tcpi_snd_mss; + + /* In order to reduce outbound kernel queuing delays and thus improve Tor's + * ability to prioritize circuits, KIST wants to set a socket write limit + * that is near the amount that the socket would be able to immediately send + * into the Internet. + * + * We first calculate how much the socket could send immediately (assuming + * completely full packets) according to the congestion window and the number + * of unacked packets. + * + * Then we add a little extra space in a controlled way. We do this so any + * when the kernel gets ACKs back for data currently sitting in the "TCP + * space", it will already have some more data to send immediately. It will + * not have to wait for the scheduler to run again. The amount of extra space + * is a factor of the current congestion window. With the suggested + * sock_buf_size_factor value of 1.0, we allow at most 2*cwnd bytes to sit in + * the kernel: 1 cwnd on the wire waiting for ACKs and 1 cwnd ready and + * waiting to be sent when those ACKs finally come. + * + * In the below diagram, we see some bytes in the TCP-space (denoted by '*') + * that have be sent onto the wire and are waiting for ACKs. We have a little + * more room in "TCP space" that we can fill with data that will be + * immediately sent. We also see the "extra space" KIST calculates. The sum + * of the empty "TCP space" and the "extra space" is the kist-imposed write + * limit for this socket. + * + * <----------------kernel-outbound-socket-queue----------------| + * <*********---------------------------------------------------| + * |----TCP-space-----|----extra-space-----| + * |------------------| + * ^ ((cwnd - unacked) * mss) bytes + * |--------------------| + * ^ ((cwnd * mss) * factor) bytes + */ + + /* These values from the kernel are uint32_t, they will always fit into a + * int64_t tcp_space variable but if the congestion window cwnd is smaller + * than the unacked packets, the remaining TCP space is set to 0. */ + if (ent->cwnd >= ent->unacked) { + tcp_space = (ent->cwnd - ent->unacked) * (int64_t)(ent->mss); + } else { + tcp_space = 0; + } + + /* The clamp_double_to_int64 makes sure the first part fits into an int64_t. + * In fact, if sock_buf_size_factor is still forced to be >= 0 in config.c, + * then it will be positive for sure. Then we subtract a uint32_t. Getting a + * negative value is OK, see after how it is being handled. */ + extra_space = + clamp_double_to_int64( + (ent->cwnd * (int64_t)ent->mss) * sock_buf_size_factor) - + ent->notsent; + if ((tcp_space + extra_space) < 0) { + /* This means that the "notsent" queue is just too big so we shouldn't put + * more in the kernel for now. */ + ent->limit = 0; + } else { + /* The positive sum of two int64_t will always fit into an uint64_t. + * And we know this will always be positive, since we checked above. */ + ent->limit = (uint64_t)tcp_space + (uint64_t)extra_space; + } + return; + +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + goto fallback; +#endif /* defined(HAVE_KIST_SUPPORT) */ + + fallback: + /* If all of a sudden we don't have kist support, we just zero out all the + * variables for this socket since we don't know what they should be. + * We also effectively allow the socket write as much as it wants to the + * kernel, effectively returning it to vanilla scheduler behavior. Writes + * are still limited by the lower layers of Tor: socket blocking, full + * outbuf, etc. */ + ent->cwnd = ent->unacked = ent->mss = ent->notsent = 0; + ent->limit = INT_MAX; +} + +/* Given a socket that isn't in the table, add it. + * Given a socket that is in the table, re-init values that need init-ing + * every scheduling run + */ +static void +init_socket_info(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (!ent) { + log_debug(LD_SCHED, "scheduler init socket info for chan=%" PRIu64, + chan->global_identifier); + ent = tor_malloc_zero(sizeof(*ent)); + ent->chan = chan; + HT_INSERT(socket_table_s, table, ent); + } + ent->written = 0; +} + +/* Add chan to the outbuf table if it isn't already in it. If it is, then don't + * do anything */ +static void +outbuf_table_add(outbuf_table_t *table, channel_t *chan) +{ + outbuf_table_ent_t search, *ent; + search.chan = chan; + ent = HT_FIND(outbuf_table_s, table, &search); + if (!ent) { + log_debug(LD_SCHED, "scheduler init outbuf info for chan=%" PRIu64, + chan->global_identifier); + ent = tor_malloc_zero(sizeof(*ent)); + ent->chan = chan; + HT_INSERT(outbuf_table_s, table, ent); + } +} + +static void +outbuf_table_remove(outbuf_table_t *table, channel_t *chan) +{ + outbuf_table_ent_t search, *ent; + search.chan = chan; + ent = HT_FIND(outbuf_table_s, table, &search); + if (ent) { + HT_REMOVE(outbuf_table_s, table, ent); + free_outbuf_info_by_ent(ent, NULL); + } +} + +/* Set the scheduler running interval. */ +static void +set_scheduler_run_interval(const networkstatus_t *ns) +{ + int old_sched_run_interval = sched_run_interval; + sched_run_interval = kist_scheduler_run_interval(ns); + if (old_sched_run_interval != sched_run_interval) { + log_info(LD_SCHED, "Scheduler KIST changing its running interval " + "from %" PRId32 " to %" PRId32, + old_sched_run_interval, sched_run_interval); + } +} + +/* Return true iff the channel hasn’t hit its kist-imposed write limit yet */ +static int +socket_can_write(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return 1; // Just return true, saying that kist wouldn't limit the socket + } + + /* We previously calculated a write limit for this socket. In the below + * calculation, first determine how much room is left in bytes. Then divide + * that by the amount of space a cell takes. If there's room for at least 1 + * cell, then KIST will allow the socket to write. */ + int64_t kist_limit_space = + (int64_t) (ent->limit - ent->written) / + (CELL_MAX_NETWORK_SIZE + TLS_PER_CELL_OVERHEAD); + return kist_limit_space > 0; +} + +/* Update the channel's socket kernel information. */ +static void +update_socket_info(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return; // Whelp. Entry didn't exist for some reason so nothing to do. + } + update_socket_info_impl(ent); +} + +/* Increment the channel's socket written value by the number of bytes. */ +static void +update_socket_written(socket_table_t *table, channel_t *chan, size_t bytes) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return; // Whelp. Entry didn't exist so nothing to do. + } + + log_debug(LD_SCHED, "chan=%" PRIu64 " wrote %lu bytes, old was %" PRIi64, + chan->global_identifier, (unsigned long) bytes, ent->written); + + ent->written += bytes; +} + +/* + * A naive KIST impl would write every single cell all the way to the kernel. + * That would take a lot of system calls. A less bad KIST impl would write a + * channel's outbuf to the kernel only when we are switching to a different + * channel. But if we have two channels with equal priority, we end up writing + * one cell for each and bouncing back and forth. This KIST impl avoids that + * by only writing a channel's outbuf to the kernel if it has 8 cells or more + * in it. + */ +MOCK_IMPL(int, channel_should_write_to_kernel, + (outbuf_table_t *table, channel_t *chan)) +{ + outbuf_table_add(table, chan); + /* CELL_MAX_NETWORK_SIZE * 8 because we only want to write the outbuf to the + * kernel if there's 8 or more cells waiting */ + return channel_outbuf_length(chan) > (CELL_MAX_NETWORK_SIZE * 8); +} + +/* Little helper function to write a channel's outbuf all the way to the + * kernel */ +MOCK_IMPL(void, channel_write_to_kernel, (channel_t *chan)) +{ + log_debug(LD_SCHED, "Writing %lu bytes to kernel for chan %" PRIu64, + (unsigned long)channel_outbuf_length(chan), + chan->global_identifier); + connection_handle_write(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn), 0); +} + +/* Return true iff the scheduler has work to perform. */ +static int +have_work(void) +{ + smartlist_t *cp = get_channels_pending(); + IF_BUG_ONCE(!cp) { + return 0; // channels_pending doesn't exist so... no work? + } + return smartlist_len(cp) > 0; +} + +/* Function of the scheduler interface: free_all() */ +static void +kist_free_all(void) +{ + free_all_socket_info(); +} + +/* Function of the scheduler interface: on_channel_free() */ +static void +kist_on_channel_free(const channel_t *chan) +{ + free_socket_info_by_chan(&socket_table, chan); +} + +/* Function of the scheduler interface: on_new_consensus() */ +static void +kist_scheduler_on_new_consensus(const networkstatus_t *old_c, + const networkstatus_t *new_c) +{ + (void) old_c; + (void) new_c; + + set_scheduler_run_interval(new_c); +} + +/* Function of the scheduler interface: on_new_options() */ +static void +kist_scheduler_on_new_options(void) +{ + sock_buf_size_factor = get_options()->KISTSockBufSizeFactor; + + /* Calls kist_scheduler_run_interval which calls get_options(). */ + set_scheduler_run_interval(NULL); +} + +/* Function of the scheduler interface: init() */ +static void +kist_scheduler_init(void) +{ + /* When initializing the scheduler, the last run could be 0 because it is + * declared static or a value in the past that was set when it was last + * used. In both cases, we want to initialize it to now so we don't risk + * using the value 0 which doesn't play well with our monotonic time + * interface. + * + * One side effect is that the first scheduler run will be at the next tick + * that is in now + 10 msec (KIST_SCHED_RUN_INTERVAL_DEFAULT) by default. */ + monotime_get(&scheduler_last_run); + + kist_scheduler_on_new_options(); + IF_BUG_ONCE(sched_run_interval == 0) { + log_warn(LD_SCHED, "We are initing the KIST scheduler and noticed the " + "KISTSchedRunInterval is telling us to not use KIST. That's " + "weird! We'll continue using KIST, but at %" PRId32 "ms.", + KIST_SCHED_RUN_INTERVAL_DEFAULT); + sched_run_interval = KIST_SCHED_RUN_INTERVAL_DEFAULT; + } +} + +/* Function of the scheduler interface: schedule() */ +static void +kist_scheduler_schedule(void) +{ + struct monotime_t now; + struct timeval next_run; + int64_t diff; + + if (!have_work()) { + return; + } + monotime_get(&now); + + /* If time is really monotonic, we can never have now being smaller than the + * last scheduler run. The scheduler_last_run at first is set to 0. + * Unfortunately, not all platforms guarantee monotonic time so we log at + * info level but don't make it more noisy. */ + diff = monotime_diff_msec(&scheduler_last_run, &now); + if (diff < 0) { + log_info(LD_SCHED, "Monotonic time between now and last run of scheduler " + "is negative: %" PRId64 ". Setting diff to 0.", diff); + diff = 0; + } + if (diff < sched_run_interval) { + next_run.tv_sec = 0; + /* Takes 1000 ms -> us. This will always be valid because diff can NOT be + * negative and can NOT be bigger than sched_run_interval so values can + * only go from 1000 usec (diff set to interval - 1) to 100000 usec (diff + * set to 0) for the maximum allowed run interval (100ms). */ + next_run.tv_usec = (int) ((sched_run_interval - diff) * 1000); + /* Re-adding an event reschedules it. It does not duplicate it. */ + scheduler_ev_add(&next_run); + } else { + scheduler_ev_active(EV_TIMEOUT); + } +} + +/* Function of the scheduler interface: run() */ +static void +kist_scheduler_run(void) +{ + /* Define variables */ + channel_t *chan = NULL; // current working channel + /* The last distinct chan served in a sched loop. */ + channel_t *prev_chan = NULL; + int flush_result; // temporarily store results from flush calls + /* Channels to be re-adding to pending at the end */ + smartlist_t *to_readd = NULL; + smartlist_t *cp = get_channels_pending(); + + outbuf_table_t outbuf_table = HT_INITIALIZER(); + + /* For each pending channel, collect new kernel information */ + SMARTLIST_FOREACH_BEGIN(cp, const channel_t *, pchan) { + init_socket_info(&socket_table, pchan); + update_socket_info(&socket_table, pchan); + } SMARTLIST_FOREACH_END(pchan); + + log_debug(LD_SCHED, "Running the scheduler. %d channels pending", + smartlist_len(cp)); + + /* The main scheduling loop. Loop until there are no more pending channels */ + while (smartlist_len(cp) > 0) { + /* get best channel */ + chan = smartlist_pqueue_pop(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx)); + if (SCHED_BUG(!chan, NULL)) { + /* Some-freaking-how a NULL got into the channels_pending. That should + * never happen, but it should be harmless to ignore it and keep looping. + */ + continue; + } + outbuf_table_add(&outbuf_table, chan); + + /* if we have switched to a new channel, consider writing the previous + * channel's outbuf to the kernel. */ + if (!prev_chan) { + prev_chan = chan; + } + if (prev_chan != chan) { + if (channel_should_write_to_kernel(&outbuf_table, prev_chan)) { + channel_write_to_kernel(prev_chan); + outbuf_table_remove(&outbuf_table, prev_chan); + } + prev_chan = chan; + } + + /* Only flush and write if the per-socket limit hasn't been hit */ + if (socket_can_write(&socket_table, chan)) { + /* flush to channel queue/outbuf */ + flush_result = (int)channel_flush_some_cells(chan, 1); // 1 for num cells + /* XXX: While flushing cells, it is possible that the connection write + * fails leading to the channel to be closed which triggers a release + * and free its entry in the socket table. And because of a engineering + * design issue, the error is not propagated back so we don't get an + * error at this point. So before we continue, make sure the channel is + * open and if not just ignore it. See #23751. */ + if (!CHANNEL_IS_OPEN(chan)) { + /* Channel isn't open so we put it back in IDLE mode. It is either + * renegotiating its TLS session or about to be released. */ + chan->scheduler_state = SCHED_CHAN_IDLE; + continue; + } + /* flush_result has the # cells flushed */ + if (flush_result > 0) { + update_socket_written(&socket_table, chan, flush_result * + (CELL_MAX_NETWORK_SIZE + TLS_PER_CELL_OVERHEAD)); + } else { + /* XXX: This can happen because tor sometimes does flush in an + * opportunistic way cells from the circuit to the outbuf so the + * channel can end up here without having anything to flush nor needed + * to write to the kernel. Hopefully we'll fix that soon but for now + * we have to handle this case which happens kind of often. */ + log_debug(LD_SCHED, + "We didn't flush anything on a chan that we think " + "can write and wants to write. The channel's state is '%s' " + "and in scheduler state %d. We're going to mark it as " + "waiting_for_cells (as that's most likely the issue) and " + "stop scheduling it this round.", + channel_state_to_string(chan->state), + chan->scheduler_state); + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + continue; + } + } + + /* Decide what to do with the channel now */ + + if (!channel_more_to_flush(chan) && + !socket_can_write(&socket_table, chan)) { + + /* Case 1: no more cells to send, and cannot write */ + + /* + * You might think we should put the channel in SCHED_CHAN_IDLE. And + * you're probably correct. While implementing KIST, we found that the + * scheduling system would sometimes lose track of channels when we did + * that. We suspect it has to do with the difference between "can't + * write because socket/outbuf is full" and KIST's "can't write because + * we've arbitrarily decided that that's enough for now." Sometimes + * channels run out of cells at the same time they hit their + * kist-imposed write limit and maybe the rest of Tor doesn't put the + * channel back in pending when it is supposed to. + * + * This should be investigated again. It is as simple as changing + * SCHED_CHAN_WAITING_FOR_CELLS to SCHED_CHAN_IDLE and seeing if Tor + * starts having serious throughput issues. Best done in shadow/chutney. + */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_for_cells", + chan->global_identifier); + } else if (!channel_more_to_flush(chan)) { + + /* Case 2: no more cells to send, but still open for writes */ + + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_for_cells", + chan->global_identifier); + } else if (!socket_can_write(&socket_table, chan)) { + + /* Case 3: cells to send, but cannot write */ + + /* + * We want to write, but can't. If we left the channel in + * channels_pending, we would never exit the scheduling loop. We need to + * add it to a temporary list of channels to be added to channels_pending + * after the scheduling loop is over. They can hopefully be taken care of + * in the next scheduling round. + */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + if (!to_readd) { + to_readd = smartlist_new(); + } + smartlist_add(to_readd, chan); + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_to_write", + chan->global_identifier); + } else { + + /* Case 4: cells to send, and still open for writes */ + + chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), chan); + } + } /* End of main scheduling loop */ + + /* Write the outbuf of any channels that still have data */ + HT_FOREACH_FN(outbuf_table_s, &outbuf_table, each_channel_write_to_kernel, + NULL); + /* We are done with it. */ + HT_FOREACH_FN(outbuf_table_s, &outbuf_table, free_outbuf_info_by_ent, NULL); + HT_CLEAR(outbuf_table_s, &outbuf_table); + + log_debug(LD_SCHED, "len pending=%d, len to_readd=%d", + smartlist_len(cp), + (to_readd ? smartlist_len(to_readd) : -1)); + + /* Re-add any channels we need to */ + if (to_readd) { + SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) { + readd_chan->scheduler_state = SCHED_CHAN_PENDING; + if (!smartlist_contains(cp, readd_chan)) { + smartlist_pqueue_add(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), readd_chan); + } + } SMARTLIST_FOREACH_END(readd_chan); + smartlist_free(to_readd); + } + + monotime_get(&scheduler_last_run); +} + +/***************************************************************************** + * Externally called function implementations not called through scheduler_t + *****************************************************************************/ + +/* Stores the kist scheduler function pointers. */ +static scheduler_t kist_scheduler = { + .type = SCHEDULER_KIST, + .free_all = kist_free_all, + .on_channel_free = kist_on_channel_free, + .init = kist_scheduler_init, + .on_new_consensus = kist_scheduler_on_new_consensus, + .schedule = kist_scheduler_schedule, + .run = kist_scheduler_run, + .on_new_options = kist_scheduler_on_new_options, +}; + +/* Return the KIST scheduler object. If it didn't exists, return a newly + * allocated one but init() is not called. */ +scheduler_t * +get_kist_scheduler(void) +{ + return &kist_scheduler; +} + +/* Check the torrc (and maybe consensus) for the configured KIST scheduler run + * interval. + * - If torrc > 0, then return the positive torrc value (should use KIST, and + * should use the set value) + * - If torrc == 0, then look in the consensus for what the value should be. + * - If == 0, then return 0 (don't use KIST) + * - If > 0, then return the positive consensus value + * - If consensus doesn't say anything, return 10 milliseconds, default. + */ +int +kist_scheduler_run_interval(const networkstatus_t *ns) +{ + int run_interval = get_options()->KISTSchedRunInterval; + + if (run_interval != 0) { + log_debug(LD_SCHED, "Found KISTSchedRunInterval=%" PRId32 " in torrc. " + "Using that.", run_interval); + return run_interval; + } + + log_debug(LD_SCHED, "KISTSchedRunInterval=0, turning to the consensus."); + + /* Will either be the consensus value or the default. Note that 0 can be + * returned which means the consensus wants us to NOT use KIST. */ + return networkstatus_get_param(ns, "KISTSchedRunInterval", + KIST_SCHED_RUN_INTERVAL_DEFAULT, + KIST_SCHED_RUN_INTERVAL_MIN, + KIST_SCHED_RUN_INTERVAL_MAX); +} + +/* Set KISTLite mode that is KIST without kernel support. */ +void +scheduler_kist_set_lite_mode(void) +{ + kist_lite_mode = 1; + kist_scheduler.type = SCHEDULER_KIST_LITE; + log_info(LD_SCHED, + "Setting KIST scheduler without kernel support (KISTLite mode)"); +} + +/* Set KIST mode that is KIST with kernel support. */ +void +scheduler_kist_set_full_mode(void) +{ + kist_lite_mode = 0; + kist_scheduler.type = SCHEDULER_KIST; + log_info(LD_SCHED, + "Setting KIST scheduler with kernel support (KIST mode)"); +} + +#ifdef HAVE_KIST_SUPPORT + +/* Return true iff the scheduler subsystem should use KIST. */ +int +scheduler_can_use_kist(void) +{ + if (kist_no_kernel_support) { + /* We have no kernel support so we can't use KIST. */ + return 0; + } + + /* We do have the support, time to check if we can get the interval that the + * consensus can be disabling. */ + int run_interval = kist_scheduler_run_interval(NULL); + log_debug(LD_SCHED, "Determined KIST sched_run_interval should be " + "%" PRId32 ". Can%s use KIST.", + run_interval, (run_interval > 0 ? "" : " not")); + return run_interval > 0; +} + +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + +int +scheduler_can_use_kist(void) +{ + return 0; +} + +#endif /* defined(HAVE_KIST_SUPPORT) */ + diff --git a/src/or/scheduler_vanilla.c b/src/or/scheduler_vanilla.c new file mode 100644 index 0000000000..303b3dbba8 --- /dev/null +++ b/src/or/scheduler_vanilla.c @@ -0,0 +1,197 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include <event2/event.h> + +#include "or.h" +#include "config.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#define SCHEDULER_PRIVATE_ +#include "scheduler.h" + +/***************************************************************************** + * Other internal data + *****************************************************************************/ + +/* Maximum cells to flush in a single call to channel_flush_some_cells(); */ +#define MAX_FLUSH_CELLS 1000 + +/***************************************************************************** + * Externally called function implementations + *****************************************************************************/ + +/* Return true iff the scheduler has work to perform. */ +static int +have_work(void) +{ + smartlist_t *cp = get_channels_pending(); + IF_BUG_ONCE(!cp) { + return 0; // channels_pending doesn't exist so... no work? + } + return smartlist_len(cp) > 0; +} + +/** Re-trigger the scheduler in a way safe to use from the callback */ + +static void +vanilla_scheduler_schedule(void) +{ + if (!have_work()) { + return; + } + + /* Activate our event so it can process channels. */ + scheduler_ev_active(EV_TIMEOUT); +} + +static void +vanilla_scheduler_run(void) +{ + int n_cells, n_chans_before, n_chans_after; + ssize_t flushed, flushed_this_time; + smartlist_t *cp = get_channels_pending(); + smartlist_t *to_readd = NULL; + channel_t *chan = NULL; + + log_debug(LD_SCHED, "We have a chance to run the scheduler"); + + n_chans_before = smartlist_len(cp); + + while (smartlist_len(cp) > 0) { + /* Pop off a channel */ + chan = smartlist_pqueue_pop(cp, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx)); + IF_BUG_ONCE(!chan) { + /* Some-freaking-how a NULL got into the channels_pending. That should + * never happen, but it should be harmless to ignore it and keep looping. + */ + continue; + } + + /* 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) { + flushed_this_time = + channel_flush_some_cells(chan, + MIN(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 *, readd_chan) { + readd_chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(cp, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), + readd_chan); + } SMARTLIST_FOREACH_END(readd_chan); + smartlist_free(to_readd); + } + + n_chans_after = smartlist_len(cp); + log_debug(LD_SCHED, "Scheduler handled %d of %d pending channels", + n_chans_before - n_chans_after, n_chans_before); +} + +/* Stores the vanilla scheduler function pointers. */ +static scheduler_t vanilla_scheduler = { + .type = SCHEDULER_VANILLA, + .free_all = NULL, + .on_channel_free = NULL, + .init = NULL, + .on_new_consensus = NULL, + .schedule = vanilla_scheduler_schedule, + .run = vanilla_scheduler_run, + .on_new_options = NULL, +}; + +scheduler_t * +get_vanilla_scheduler(void) +{ + return &vanilla_scheduler; +} + diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 25ca0611cd..b3f62a8fd8 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1333,13 +1333,7 @@ sr_act_post_consensus(const networkstatus_t *consensus) } /* Prepare our state so that it's ready for the next voting period. */ - { - voting_schedule_t *voting_schedule = - get_voting_schedule(options,time(NULL), LOG_NOTICE); - time_t interval_starts = voting_schedule->interval_starts; - sr_state_update(interval_starts); - voting_schedule_free(voting_schedule); - } + sr_state_update(dirvote_get_next_valid_after_time()); } /* Initialize shared random subsystem. This MUST be called early in the boot @@ -1390,6 +1384,52 @@ sr_get_previous_for_control(void) return srv_str; } +/* Return current shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_current(const networkstatus_t *ns) +{ + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.current_srv; + } + return NULL; +} + +/* Return previous shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_previous(const networkstatus_t *ns) +{ + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.previous_srv; + } + return NULL; +} + #ifdef TOR_UNIT_TESTS /* Set the global value of number of SRV agreements so the test can play @@ -1401,5 +1441,5 @@ set_num_srv_agreements(int32_t value) num_srv_agreements_from_vote = value; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 1f027c70e0..c0992489cb 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -130,6 +130,9 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp, char *sr_get_current_for_control(void); char *sr_get_previous_for_control(void); +const sr_srv_t *sr_get_current(const networkstatus_t *ns); +const sr_srv_t *sr_get_previous(const networkstatus_t *ns); + #ifdef SHARED_RANDOM_PRIVATE /* Encode */ @@ -157,7 +160,7 @@ STATIC int should_keep_commit(const sr_commit_t *commit, sr_phase_t phase); STATIC void save_commit_during_reveal_phase(const sr_commit_t *commit); -#endif /* SHARED_RANDOM_PRIVATE */ +#endif /* defined(SHARED_RANDOM_PRIVATE) */ #ifdef TOR_UNIT_TESTS @@ -165,5 +168,5 @@ void set_num_srv_agreements(int32_t value); #endif /* TOR_UNIT_TESTS */ -#endif /* TOR_SHARED_RANDOM_H */ +#endif /* !defined(TOR_SHARED_RANDOM_H) */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 89d2e8d7f6..ae904cfda3 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -40,10 +40,14 @@ static const char dstate_commit_key[] = "Commit"; static const char dstate_prev_srv_key[] = "SharedRandPreviousValue"; static const char dstate_cur_srv_key[] = "SharedRandCurrentValue"; +/** dummy instance of sr_disk_state_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(sr_disk_state_t); + /* These next two are duplicates or near-duplicates from config.c */ #define VAR(name, conftype, member, initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(sr_disk_state_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(sr_disk_state_t, member), \ + initvalue CONF_TEST_MEMBERS(sr_disk_state_t, conftype, member) } /* As VAR, but the option name and member name are the same. */ #define V(member, conftype, initvalue) \ VAR(#member, conftype, member, initvalue) @@ -70,21 +74,22 @@ static config_var_t state_vars[] = { V(SharedRandValues, LINELIST_V, NULL), VAR("SharedRandPreviousValue",LINELIST_S, SharedRandValues, NULL), VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; /* "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ static config_var_t state_extra_var = { "__extra", CONFIG_TYPE_LINELIST, - STRUCT_OFFSET(sr_disk_state_t, ExtraLines), NULL + offsetof(sr_disk_state_t, ExtraLines), NULL + CONF_TEST_MEMBERS(sr_disk_state_t, LINELIST, ExtraLines) }; /* Configuration format of sr_disk_state_t. */ static const config_format_t state_format = { sizeof(sr_disk_state_t), SR_DISK_STATE_MAGIC, - STRUCT_OFFSET(sr_disk_state_t, magic_), + offsetof(sr_disk_state_t, magic_), NULL, NULL, state_vars, @@ -133,27 +138,56 @@ get_voting_interval(void) /* Given the time <b>now</b>, return the start time of the current round of * the SR protocol. For example, if it's 23:47:08, the current round thus * started at 23:47:00 for a voting interval of 10 seconds. */ -static time_t -get_start_time_of_current_round(time_t now) +STATIC time_t +get_start_time_of_current_round(void) { const or_options_t *options = get_options(); int voting_interval = get_voting_interval(); - voting_schedule_t *new_voting_schedule = - get_voting_schedule(options, now, LOG_INFO); - tor_assert(new_voting_schedule); - /* First, get the start time of the next round */ - time_t next_start = new_voting_schedule->interval_starts; + time_t next_start = dirvote_get_next_valid_after_time(); /* Now roll back next_start by a voting interval to find the start time of the current round. */ time_t curr_start = dirvote_get_start_of_next_interval( next_start - voting_interval - 1, voting_interval, options->TestingV3AuthVotingStartOffset); + return curr_start; +} - voting_schedule_free(new_voting_schedule); +/** Return the start time of the current SR protocol run. For example, if the + * time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this + * function should return 23/06/2017 00:00:00. */ +time_t +sr_state_get_start_time_of_current_protocol_run(time_t now) +{ + int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + int voting_interval = get_voting_interval(); + /* Find the time the current round started. */ + time_t beginning_of_current_round = get_start_time_of_current_round(); - return curr_start; + /* Get current SR protocol round */ + int current_round = (now / voting_interval) % total_rounds; + + /* Get start time by subtracting the time elapsed from the beginning of the + protocol run */ + time_t time_elapsed_since_start_of_run = current_round * voting_interval; + return beginning_of_current_round - time_elapsed_since_start_of_run; +} + +/** Return the time (in seconds) it takes to complete a full SR protocol phase + * (e.g. the commit phase). */ +unsigned int +sr_state_get_phase_duration(void) +{ + return SHARED_RANDOM_N_ROUNDS * get_voting_interval(); +} + +/** Return the time (in seconds) it takes to complete a full SR protocol run */ +unsigned int +sr_state_get_protocol_run_duration(void) +{ + int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + return total_protocol_rounds * get_voting_interval(); } /* Return the time we should expire the state file created at <b>now</b>. @@ -167,7 +201,7 @@ get_state_valid_until_time(time_t now) voting_interval = get_voting_interval(); /* Find the time the current round started. */ - beginning_of_current_round = get_start_time_of_current_round(now); + beginning_of_current_round = get_start_time_of_current_round(); /* Find how many rounds are left till the end of the protocol run */ current_round = (now / voting_interval) % total_rounds; @@ -1329,7 +1363,7 @@ sr_state_init(int save_to_disk, int read_from_disk) /* We have a state in memory, let's make sure it's updated for the current * and next voting round. */ { - time_t valid_after = get_next_valid_after_time(now); + time_t valid_after = dirvote_get_next_valid_after_time(); sr_state_update(valid_after); } return 0; @@ -1355,5 +1389,5 @@ get_sr_state(void) return sr_state; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 3526ad47d3..866725c435 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -77,7 +77,7 @@ typedef struct sr_state_t { typedef struct sr_disk_state_t { uint32_t magic_; /* Version of the protocol. */ - uint32_t Version; + int Version; /* Version of our running tor. */ char *TorVersion; /* Creation time of this state */ @@ -121,11 +121,16 @@ int sr_state_is_initialized(void); void sr_state_save(void); void sr_state_free(void); +time_t sr_state_get_start_time_of_current_protocol_run(time_t now); +unsigned int sr_state_get_phase_duration(void); +unsigned int sr_state_get_protocol_run_duration(void); + #ifdef SHARED_RANDOM_STATE_PRIVATE STATIC int disk_state_load_from_disk_impl(const char *fname); STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after); +STATIC time_t get_start_time_of_current_round(void); STATIC time_t get_state_valid_until_time(time_t now); STATIC const char *get_phase_str(sr_phase_t phase); @@ -134,14 +139,14 @@ STATIC void new_protocol_run(time_t valid_after); STATIC void state_rotate_srv(void); STATIC int is_phase_transition(sr_phase_t next_phase); -#endif /* SHARED_RANDOM_STATE_PRIVATE */ +#endif /* defined(SHARED_RANDOM_STATE_PRIVATE) */ #ifdef TOR_UNIT_TESTS STATIC void set_sr_phase(sr_phase_t phase); STATIC sr_state_t *get_sr_state(void); -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* TOR_SHARED_RANDOM_STATE_H */ +#endif /* !defined(TOR_SHARED_RANDOM_STATE_H) */ diff --git a/src/or/statefile.c b/src/or/statefile.c index d0606b3012..97bd9cac36 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -34,6 +34,7 @@ #include "config.h" #include "confparse.h" #include "connection.h" +#include "control.h" #include "entrynodes.h" #include "hibernate.h" #include "rephist.h" @@ -53,10 +54,14 @@ static config_abbrev_t state_abbrevs_[] = { { NULL, NULL, 0, 0}, }; +/** dummy instance of or_state_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(or_state_t); + /*XXXX these next two are duplicates or near-duplicates from config.c */ #define VAR(name,conftype,member,initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(or_state_t, member), \ + initvalue CONF_TEST_MEMBERS(or_state_t, conftype, member) } /** As VAR, but the option name and member name are the same. */ #define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) @@ -85,6 +90,8 @@ static config_var_t state_vars_[] = { VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), V(TransportProxies, LINELIST_V, NULL), + V(HidServRevCounter, LINELIST, NULL), + V(BWHistoryReadEnds, ISOTIME, NULL), V(BWHistoryReadInterval, UINT, "900"), V(BWHistoryReadValues, CSV, ""), @@ -113,7 +120,8 @@ static config_var_t state_vars_[] = { V(CircuitBuildAbandonedCount, UINT, "0"), VAR("CircuitBuildTimeBin", LINELIST_S, BuildtimeHistogram, NULL), VAR("BuildtimeHistogram", LINELIST_V, BuildtimeHistogram, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + + END_OF_CONFIG_VARS }; #undef VAR @@ -131,14 +139,15 @@ static int or_state_validate_cb(void *old_options, void *options, /** "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ static config_var_t state_extra_var = { - "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL + "__extra", CONFIG_TYPE_LINELIST, offsetof(or_state_t, ExtraLines), NULL + CONF_TEST_MEMBERS(or_state_t, LINELIST, ExtraLines) }; /** Configuration format for or_state_t. */ static const config_format_t state_format = { sizeof(or_state_t), OR_STATE_MAGIC, - STRUCT_OFFSET(or_state_t, magic_), + offsetof(or_state_t, magic_), state_abbrevs_, NULL, state_vars_, @@ -402,10 +411,15 @@ or_state_load(void) log_info(LD_GENERAL, "Loaded state from \"%s\"", fname); /* Warn the user if their clock has been set backwards, * they could be tricked into using old consensuses */ - time_t apparent_skew = new_state->LastWritten - time(NULL); - if (apparent_skew > 0) + time_t apparent_skew = time(NULL) - new_state->LastWritten; + if (apparent_skew < 0) { + /* Initialize bootstrap event reporting because we might call + * clock_skew_warning() before the bootstrap state is + * initialized, causing an assertion failure. */ + control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0); clock_skew_warning(NULL, (long)apparent_skew, 1, LD_GENERAL, "local state file", fname); + } } else { log_info(LD_GENERAL, "Initialized state"); } @@ -657,8 +671,6 @@ save_transport_to_state(const char *transport, *next = line = tor_malloc_zero(sizeof(config_line_t)); line->key = tor_strdup("TransportProxy"); tor_asprintf(&line->value, "%s %s", transport, fmt_addrport(addr, port)); - - next = &(line->next); } if (!get_options()->AvoidDiskWrites) diff --git a/src/or/statefile.h b/src/or/statefile.h index 10c09324bc..574afb3622 100644 --- a/src/or/statefile.h +++ b/src/or/statefile.h @@ -24,5 +24,5 @@ STATIC void or_state_free(or_state_t *state); STATIC or_state_t *or_state_new(void); #endif -#endif +#endif /* !defined(TOR_STATEFILE_H) */ diff --git a/src/or/status.h b/src/or/status.h index c1a0033ce0..49da6abc0f 100644 --- a/src/or/status.h +++ b/src/or/status.h @@ -14,5 +14,5 @@ STATIC char *secs_to_uptime(long secs); STATIC char *bytes_to_usage(uint64_t bytes); #endif -#endif +#endif /* !defined(TOR_STATUS_H) */ diff --git a/src/or/torcert.c b/src/or/torcert.c index 658e620ca5..befb39d6e8 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -76,29 +76,38 @@ tor_cert_sign_impl(const ed25519_keypair_t *signing_key, ed25519_signature_t signature; if (ed25519_sign(&signature, encoded, real_len-ED25519_SIG_LEN, signing_key)<0) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Can't sign certificate"); goto err; + /* LCOV_EXCL_STOP */ } memcpy(sig, signature.sig, ED25519_SIG_LEN); torcert = tor_cert_parse(encoded, real_len); if (! torcert) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Generated a certificate we cannot parse"); goto err; + /* LCOV_EXCL_STOP */ } if (tor_cert_checksig(torcert, &signing_key->pubkey, now) < 0) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Generated a certificate whose signature we can't check"); goto err; + /* LCOV_EXCL_STOP */ } tor_free(encoded); goto done; + /* LCOV_EXCL_START */ err: tor_cert_free(torcert); torcert = NULL; + /* LCOV_EXCL_STOP */ + done: ed25519_cert_free(cert); tor_free(encoded); @@ -356,12 +365,12 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, * * Return 0 on success, negative on failure. */ -int -rsa_ed25519_crosscert_check(const uint8_t *crosscert, - const size_t crosscert_len, - const crypto_pk_t *rsa_id_key, - const ed25519_public_key_t *master_key, - const time_t reject_if_expired_before) +MOCK_IMPL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)) { rsa_ed_crosscert_t *cc = NULL; int rv; @@ -393,7 +402,7 @@ rsa_ed25519_crosscert_check(const uint8_t *crosscert, } const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc); - const uint64_t expiration_time = expiration_date * 3600; + const uint64_t expiration_time = ((uint64_t)expiration_date) * 3600; if (reject_if_expired_before < 0 || expiration_time < (uint64_t)reject_if_expired_before) { @@ -675,8 +684,10 @@ tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out) if (base64_encode(ed_cert_b64, ed_cert_b64_len, (const char *) cert->encoded, cert->encoded_len, BASE64_ENCODE_MULTILINE) < 0) { + /* LCOV_EXCL_START */ log_err(LD_BUG, "Couldn't base64-encode ed22519 cert!"); goto err; + /* LCOV_EXCL_STOP */ } /* Put everything together in a NUL terminated string. */ diff --git a/src/or/torcert.h b/src/or/torcert.h index 51f7665f1e..c77ae2089d 100644 --- a/src/or/torcert.h +++ b/src/or/torcert.h @@ -75,11 +75,12 @@ ssize_t tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, const crypto_pk_t *rsa_key, time_t expires, uint8_t **cert); -int rsa_ed25519_crosscert_check(const uint8_t *crosscert, - const size_t crosscert_len, - const crypto_pk_t *rsa_id_key, - const ed25519_public_key_t *master_key, - const time_t reject_if_expired_before); +MOCK_DECL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)); or_handshake_certs_t *or_handshake_certs_new(void); void or_handshake_certs_free(or_handshake_certs_t *certs); @@ -100,5 +101,5 @@ void or_handshake_certs_check_both(int severity, int tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out); -#endif +#endif /* !defined(TORCERT_H_INCLUDED) */ diff --git a/src/or/transports.c b/src/or/transports.c index 31849a8d15..5fb24e11a0 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -527,12 +527,12 @@ launch_managed_proxy(managed_proxy_t *mp) (const char **)mp->argv, env, &mp->process_handle); -#else +#else /* !(defined(_WIN32)) */ retval = tor_spawn_background(mp->argv[0], (const char **)mp->argv, env, &mp->process_handle); -#endif +#endif /* defined(_WIN32) */ process_environment_free(env); @@ -1094,8 +1094,6 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) transport = transport_new(&tor_addr, port, method_name, PROXY_NONE, args_string); - if (!transport) - goto err; smartlist_add(mp->transports, transport); @@ -1186,8 +1184,6 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) } transport = transport_new(&tor_addr, port, method_name, socks_ver, NULL); - if (!transport) - goto err; smartlist_add(mp->transports, transport); diff --git a/src/or/transports.h b/src/or/transports.h index 44a9626e50..e368e447c3 100644 --- a/src/or/transports.h +++ b/src/or/transports.h @@ -133,7 +133,7 @@ STATIC char* get_pt_proxy_uri(void); STATIC void free_execve_args(char **arg); -#endif +#endif /* defined(PT_PRIVATE) */ -#endif +#endif /* !defined(TOR_TRANSPORTS_H) */ |