aboutsummaryrefslogtreecommitdiff
path: root/src/or
diff options
context:
space:
mode:
Diffstat (limited to 'src/or')
-rw-r--r--src/or/addressmap.c8
-rw-r--r--src/or/addressmap.h4
-rw-r--r--src/or/bridges.c65
-rw-r--r--src/or/bridges.h3
-rw-r--r--src/or/buffers.c2192
-rw-r--r--src/or/buffers.h109
-rw-r--r--src/or/channel.c75
-rw-r--r--src/or/channel.h12
-rw-r--r--src/or/channelpadding.c2
-rw-r--r--src/or/channelpadding.h2
-rw-r--r--src/or/channeltls.c51
-rw-r--r--src/or/channeltls.h6
-rw-r--r--src/or/circpathbias.c4
-rw-r--r--src/or/circpathbias.h2
-rw-r--r--src/or/circuitbuild.c613
-rw-r--r--src/or/circuitbuild.h15
-rw-r--r--src/or/circuitlist.c118
-rw-r--r--src/or/circuitlist.h9
-rw-r--r--src/or/circuitmux.c12
-rw-r--r--src/or/circuitmux.h2
-rw-r--r--src/or/circuitmux_ewma.c6
-rw-r--r--src/or/circuitmux_ewma.h2
-rw-r--r--src/or/circuitstats.c41
-rw-r--r--src/or/circuitstats.h8
-rw-r--r--src/or/circuituse.c277
-rw-r--r--src/or/circuituse.h10
-rw-r--r--src/or/command.c23
-rw-r--r--src/or/command.h2
-rw-r--r--src/or/config.c491
-rw-r--r--src/or/config.h11
-rw-r--r--src/or/confparse.h76
-rw-r--r--src/or/connection.c156
-rw-r--r--src/or/connection.h18
-rw-r--r--src/or/connection_edge.c693
-rw-r--r--src/or/connection_edge.h19
-rw-r--r--src/or/connection_or.c30
-rw-r--r--src/or/connection_or.h2
-rw-r--r--src/or/conscache.c14
-rw-r--r--src/or/conscache.h2
-rw-r--r--src/or/consdiff.h4
-rw-r--r--src/or/consdiffmgr.c2
-rw-r--r--src/or/consdiffmgr.h4
-rw-r--r--src/or/control.c342
-rw-r--r--src/or/control.h19
-rw-r--r--src/or/cpuworker.c4
-rw-r--r--src/or/cpuworker.h2
-rw-r--r--src/or/dircollate.c2
-rw-r--r--src/or/dircollate.h4
-rw-r--r--src/or/directory.c593
-rw-r--r--src/or/directory.h105
-rw-r--r--src/or/dirserv.c36
-rw-r--r--src/or/dirserv.h4
-rw-r--r--src/or/dirvote.c114
-rw-r--r--src/or/dirvote.h18
-rw-r--r--src/or/dns.c39
-rw-r--r--src/or/dns.h4
-rw-r--r--src/or/dns_structs.h2
-rw-r--r--src/or/dnsserv.c4
-rw-r--r--src/or/dnsserv.h2
-rw-r--r--src/or/entrynodes.c71
-rw-r--r--src/or/entrynodes.h27
-rw-r--r--src/or/ext_orport.c28
-rw-r--r--src/or/ext_orport.h4
-rw-r--r--src/or/fp_pair.h2
-rw-r--r--src/or/geoip.c9
-rw-r--r--src/or/geoip.h4
-rw-r--r--src/or/hibernate.c2
-rw-r--r--src/or/hibernate.h4
-rw-r--r--src/or/hs_cache.c576
-rw-r--r--src/or/hs_cache.h68
-rw-r--r--src/or/hs_cell.c948
-rw-r--r--src/or/hs_cell.h122
-rw-r--r--src/or/hs_circuit.c1208
-rw-r--r--src/or/hs_circuit.h76
-rw-r--r--src/or/hs_circuitmap.c72
-rw-r--r--src/or/hs_circuitmap.h12
-rw-r--r--src/or/hs_client.c1609
-rw-r--r--src/or/hs_client.h92
-rw-r--r--src/or/hs_common.c1467
-rw-r--r--src/or/hs_common.h206
-rw-r--r--src/or/hs_config.c590
-rw-r--r--src/or/hs_config.h24
-rw-r--r--src/or/hs_descriptor.c436
-rw-r--r--src/or/hs_descriptor.h54
-rw-r--r--src/or/hs_ident.c126
-rw-r--r--src/or/hs_ident.h141
-rw-r--r--src/or/hs_intropoint.c16
-rw-r--r--src/or/hs_intropoint.h26
-rw-r--r--src/or/hs_ntor.c50
-rw-r--r--src/or/hs_ntor.h26
-rw-r--r--src/or/hs_service.c3430
-rw-r--r--src/or/hs_service.h351
-rw-r--r--src/or/include.am47
-rw-r--r--src/or/keypin.h4
-rw-r--r--src/or/main.c114
-rw-r--r--src/or/main.h4
-rw-r--r--src/or/microdesc.c2
-rw-r--r--src/or/networkstatus.c142
-rw-r--r--src/or/networkstatus.h19
-rw-r--r--src/or/nodelist.c485
-rw-r--r--src/or/nodelist.h32
-rw-r--r--src/or/ntmain.c9
-rw-r--r--src/or/ntmain.h4
-rw-r--r--src/or/onion.c2
-rw-r--r--src/or/onion.h2
-rw-r--r--src/or/onion_fast.h2
-rw-r--r--src/or/onion_ntor.h4
-rw-r--r--src/or/onion_tap.c7
-rw-r--r--src/or/onion_tap.h2
-rw-r--r--src/or/or.h178
-rw-r--r--src/or/parsecommon.c3
-rw-r--r--src/or/parsecommon.h5
-rw-r--r--src/or/periodic.h2
-rw-r--r--src/or/policies.c15
-rw-r--r--src/or/policies.h4
-rw-r--r--src/or/proto_cell.c84
-rw-r--r--src/or/proto_cell.h17
-rw-r--r--src/or/proto_control0.c26
-rw-r--r--src/or/proto_control0.h14
-rw-r--r--src/or/proto_ext_or.c40
-rw-r--r--src/or/proto_ext_or.h17
-rw-r--r--src/or/proto_http.c171
-rw-r--r--src/or/proto_http.h24
-rw-r--r--src/or/proto_socks.c710
-rw-r--r--src/or/proto_socks.h20
-rw-r--r--src/or/protover.h11
-rw-r--r--src/or/reasons.c54
-rw-r--r--src/or/reasons.h3
-rw-r--r--src/or/relay.c73
-rw-r--r--src/or/relay.h4
-rw-r--r--src/or/rendcache.c8
-rw-r--r--src/or/rendcache.h6
-rw-r--r--src/or/rendclient.c425
-rw-r--r--src/or/rendclient.h6
-rw-r--r--src/or/rendcommon.c19
-rw-r--r--src/or/rendcommon.h13
-rw-r--r--src/or/rendmid.c1
-rw-r--r--src/or/rendmid.h2
-rw-r--r--src/or/rendservice.c603
-rw-r--r--src/or/rendservice.h34
-rw-r--r--src/or/rephist.c105
-rw-r--r--src/or/rephist.h5
-rw-r--r--src/or/replaycache.h6
-rw-r--r--src/or/router.c62
-rw-r--r--src/or/router.h4
-rw-r--r--src/or/routerkeys.c106
-rw-r--r--src/or/routerkeys.h3
-rw-r--r--src/or/routerlist.c108
-rw-r--r--src/or/routerlist.h7
-rw-r--r--src/or/routerparse.c39
-rw-r--r--src/or/routerparse.h4
-rw-r--r--src/or/routerset.c2
-rw-r--r--src/or/routerset.h4
-rw-r--r--src/or/scheduler.c862
-rw-r--r--src/or/scheduler.h214
-rw-r--r--src/or/scheduler_kist.c832
-rw-r--r--src/or/scheduler_vanilla.c197
-rw-r--r--src/or/shared_random.c56
-rw-r--r--src/or/shared_random.h7
-rw-r--r--src/or/shared_random_state.c68
-rw-r--r--src/or/shared_random_state.h13
-rw-r--r--src/or/statefile.c30
-rw-r--r--src/or/statefile.h2
-rw-r--r--src/or/status.h2
-rw-r--r--src/or/torcert.c25
-rw-r--r--src/or/torcert.h13
-rw-r--r--src/or/transports.c8
-rw-r--r--src/or/transports.h4
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,
+ &ocirc->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(&ocirc->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) */